diff --git a/docs/Fields.md b/docs/Fields.md index 8521bf85f44..7b60f4b6072 100644 --- a/docs/Fields.md +++ b/docs/Fields.md @@ -597,4 +597,39 @@ You can find components for react-admin in third-party repositories. - [OoDeLally/react-admin-clipboard-list-field](https://github.com/OoDeLally/react-admin-clipboard-list-field): a quick and customizable copy-to-clipboard field. - [MrHertal/react-admin-json-view](https://github.com/MrHertal/react-admin-json-view): JSON field and input for react-admin. -- [alexgschwend/react-admin-color-picker](https://github.com/alexgschwend/react-admin-color-picker): a color field \ No newline at end of file +- [alexgschwend/react-admin-color-picker](https://github.com/alexgschwend/react-admin-color-picker): a color field + +## TypeScript + +All field components accept a generic type that describes the record. This lets TypeScript validate that the `source` prop targets an actual field of the record: + +```tsx +import * as React from "react"; +import { Show, SimpleShowLayout, TextField, DateField, RichTextField } from 'react-admin'; + +// Note that you shouldn't extend RaRecord for this to work +type Post = { + id: number; + title: string; + teaser: string; + body: string; + published_at: string; +} + +export const PostShow = () => ( + + + source="title" /> + source="teaser" /> + {/* Here TS will show an error because a teasr field does not exist */} + source="teasr" /> + source="body" /> + label="Publication date" source="published_at" /> + + +); +``` + +**Limitation**: You must not extend `RaRecord` for this to work. This is because `RaRecord` extends `Record` and TypeScript would not be able to infer your types properties. + +Specifying the record type will also allow your IDE to provide auto-completion for both the `source` and `sortBy` prop. Note that the `sortBy` prop also accepts any string. diff --git a/docs/js/prism.js b/docs/js/prism.js index 9c9f4f90f79..f1cb9fa23a5 100644 --- a/docs/js/prism.js +++ b/docs/js/prism.js @@ -1,9 +1,11 @@ -/* PrismJS 1.15.0 -https://prismjs.com/download.html?#themes=prism&languages=markup+css+clike+javascript+diff+jsx */ -var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-([\w-]+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,disableWorkerMessageHandler:_self.Prism&&_self.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof r?new r(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){if(m&&b!=t.length-1){h.lastIndex=k;var _=h.exec(e);if(!_)break;for(var j=_.index+(d?_[1].length:0),P=_.index+_[0].length,A=b,x=k,O=t.length;O>A&&(P>x||!t[A].type&&!t[A-1].greedy);++A)x+=t[A].length,j>=x&&(++b,k=x);if(t[b]instanceof s)continue;I=A-b,w=e.slice(k,x),_.index-=k}else{h.lastIndex=0;var _=h.exec(w),I=1}if(_){d&&(p=_[1]?_[1].length:0);var j=_.index+p,_=_[0].slice(p),P=j+_.length,N=w.slice(0,j),S=w.slice(P),C=[b,I];N&&(++b,k+=N.length,C.push(N));var E=new s(u,f?n.tokenize(_,f):_,y,_,m);if(C.push(E),S&&C.push(S),Array.prototype.splice.apply(t,C),1!=I&&n.matchGrammar(e,t,r,b,k,!0,u),i)break}else if(i)break}}}}},tokenize:function(e,t){var r=[e],a=t.rest;if(a){for(var l in a)t[l]=a[l];delete t.rest}return n.matchGrammar(e,r,t,0,0,!1),r},hooks:{all:{},add:function(e,t){var r=n.hooks.all;r[e]=r[e]||[],r[e].push(t)},run:function(e,t){var r=n.hooks.all[e];if(r&&r.length)for(var a,l=0;a=r[l++];)a(t)}}},r=n.Token=function(e,t,n,r,a){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length,this.greedy=!!a};if(r.stringify=function(e,t,a){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return r.stringify(n,t,e)}).join("");var l={type:e.type,content:r.stringify(e.content,t,a),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:a};if(e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(n.disableWorkerMessageHandler||_self.addEventListener("message",function(e){var t=JSON.parse(e.data),r=t.language,a=t.code,l=t.immediateClose;_self.postMessage(n.highlight(a,n.languages[r],r)),l&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return a&&(n.filename=a.src,n.manual||a.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); -Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/(^|[^\\])["']/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; -Prism.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(?:;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^{}\s][^{};]*?(?=\s*\{)/,string:{pattern:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.languages.css,Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\s\S]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css",greedy:!0}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); -Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(?:true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,"function":/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\()/i,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[[^\]\r\n]+]|\\.|[^\/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=\s*(?:function\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i,alias:"function"},constant:/\b[A-Z][A-Z\d_]*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,greedy:!0,inside:{interpolation:{pattern:/\${[^}]+}/,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}}}),Prism.languages.javascript["template-string"].inside.interpolation.inside.rest=Prism.languages.javascript,Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript",greedy:!0}}),Prism.languages.js=Prism.languages.javascript; -Prism.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d+.*$/m],deleted:/^[-<].*$/m,inserted:/^[+>].*$/m,diff:{pattern:/^!(?!!).+$/m,alias:"important"}}; -!function(t){var n=t.util.clone(t.languages.javascript);t.languages.jsx=t.languages.extend("markup",n),t.languages.jsx.tag.pattern=/<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^}]*\}|[^{}])*\}|[^{}])+\}))?|\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}))*\s*\/?)?>/i,t.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/i,t.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i,t.languages.insertBefore("inside","attr-name",{spread:{pattern:/\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}/,inside:{punctuation:/\.{3}|[{}.]/,"attr-value":/\w+/}}},t.languages.jsx.tag),t.languages.insertBefore("inside","attr-value",{script:{pattern:/=(\{(?:\{(?:\{[^}]*\}|[^}])*\}|[^}])+\})/i,inside:{"script-punctuation":{pattern:/^=(?={)/,alias:"punctuation"},rest:t.languages.jsx},alias:"language-javascript"}},t.languages.jsx.tag);var e=function(t){return t?"string"==typeof t?t:"string"==typeof t.content?t.content:t.content.map(e).join(""):""},a=function(n){for(var s=[],g=0;g0&&s[s.length-1].tagName===e(o.content[0].content[1])&&s.pop():"/>"===o.content[o.content.length-1].content||s.push({tagName:e(o.content[0].content[1]),openedBraces:0}):s.length>0&&"punctuation"===o.type&&"{"===o.content?s[s.length-1].openedBraces++:s.length>0&&s[s.length-1].openedBraces>0&&"punctuation"===o.type&&"}"===o.content?s[s.length-1].openedBraces--:i=!0),(i||"string"==typeof o)&&s.length>0&&0===s[s.length-1].openedBraces){var p=e(o);g0&&("string"==typeof n[g-1]||"plain-text"===n[g-1].type)&&(p=e(n[g-1])+p,n.splice(g-1,1),g--),n[g]=new t.Token("plain-text",p,null,p)}o.content&&"string"!=typeof o.content&&a(o.content)}};t.hooks.add("after-tokenize",function(t){("jsx"===t.language||"tsx"===t.language)&&a(t.tokens)})}(Prism); \ No newline at end of file +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+diff+jsx+tsx+typescript */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; +!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; +!function(e){e.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var n={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(n).forEach((function(a){var i=n[a],r=[];/^\w+$/.test(a)||r.push(/\w+/.exec(a)[0]),"diff"===a&&r.push("bold"),e.languages.diff[a]={pattern:RegExp("^(?:["+i+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:r,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:n})}(Prism); +!function(t){var n=t.util.clone(t.languages.javascript),e="(?:\\{*\\.{3}(?:[^{}]|)*\\})";function a(t,n){return t=t.replace(//g,(function(){return"(?:\\s|//.*(?!.)|/\\*(?:[^*]|\\*(?!/))\\*/)"})).replace(//g,(function(){return"(?:\\{(?:\\{(?:\\{[^{}]*\\}|[^{}])*\\}|[^{}])*\\})"})).replace(//g,(function(){return e})),RegExp(t,n)}e=a(e).source,t.languages.jsx=t.languages.extend("markup",n),t.languages.jsx.tag.pattern=a("+(?:[\\w.:$-]+(?:=(?:\"(?:\\\\[^]|[^\\\\\"])*\"|'(?:\\\\[^]|[^\\\\'])*'|[^\\s{'\"/>=]+|))?|))**/?)?>"),t.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,t.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,t.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,t.languages.jsx.tag.inside.comment=n.comment,t.languages.insertBefore("inside","attr-name",{spread:{pattern:a(""),inside:t.languages.jsx}},t.languages.jsx.tag),t.languages.insertBefore("inside","special-attr",{script:{pattern:a("="),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:t.languages.jsx}}},t.languages.jsx.tag);var s=function(t){return t?"string"==typeof t?t:"string"==typeof t.content?t.content:t.content.map(s).join(""):""},g=function(n){for(var e=[],a=0;a0&&e[e.length-1].tagName===s(o.content[0].content[1])&&e.pop():"/>"===o.content[o.content.length-1].content||e.push({tagName:s(o.content[0].content[1]),openedBraces:0}):e.length>0&&"punctuation"===o.type&&"{"===o.content?e[e.length-1].openedBraces++:e.length>0&&e[e.length-1].openedBraces>0&&"punctuation"===o.type&&"}"===o.content?e[e.length-1].openedBraces--:i=!0),(i||"string"==typeof o)&&e.length>0&&0===e[e.length-1].openedBraces){var r=s(o);a0&&("string"==typeof n[a-1]||"plain-text"===n[a-1].type)&&(r=s(n[a-1])+r,n.splice(a-1,1),a--),n[a]=new t.Token("plain-text",r,null,r)}o.content&&"string"!=typeof o.content&&g(o.content)}};t.hooks.add("after-tokenize",(function(t){"jsx"!==t.language&&"tsx"!==t.language||g(t.tokens)}))}(Prism); +!function(e){e.languages.typescript=e.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var s=e.languages.extend("typescript",{});delete s["class-name"],e.languages.typescript["class-name"].inside=s,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:s}}}}),e.languages.ts=e.languages.typescript}(Prism); +!function(e){var a=e.util.clone(e.languages.typescript);e.languages.tsx=e.languages.extend("jsx",a),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"];var t=e.languages.tsx.tag;t.pattern=RegExp("(^|[^\\w$]|(?= { - const record = useRecordContext(); + const record = useRecordContext(); if (!record) return null; return ( { }; const handleDeleteTag = (id: Identifier) => { - const tags: Identifier[] = record.tags.filter( - (tagId: Identifier) => tagId !== id - ); + const tags = record.tags.filter(tagId => tagId !== id); update('contacts', { id: record.id, data: { tags }, @@ -72,7 +70,7 @@ export const TagsListEdit = () => { }; const handleAddTag = (id: Identifier) => { - const tags: Identifier[] = [...record.tags, id]; + const tags = [...record.tags, id]; update('contacts', { id: record.id, data: { tags }, diff --git a/examples/crm/src/dataGenerator/types.ts b/examples/crm/src/dataGenerator/types.ts index 2828b5fe5d8..32dbfa626d9 100644 --- a/examples/crm/src/dataGenerator/types.ts +++ b/examples/crm/src/dataGenerator/types.ts @@ -1,5 +1,5 @@ import { RaRecord } from 'react-admin'; -import { Company, Contact, ContactNote, Deal, Tag } from '../types'; +import { Company, Contact, ContactNote, Deal, Sale, Tag } from '../types'; export interface Db { companies: Company[]; @@ -7,7 +7,7 @@ export interface Db { contactNotes: ContactNote[]; deals: Deal[]; dealNotes: RaRecord[]; - sales: RaRecord[]; + sales: Sale[]; tags: Tag[]; tasks: RaRecord[]; } diff --git a/examples/crm/src/types.ts b/examples/crm/src/types.ts index fb4f64ac0ef..edc8b825aaf 100644 --- a/examples/crm/src/types.ts +++ b/examples/crm/src/types.ts @@ -1,4 +1,4 @@ -import { RaRecord, Identifier } from 'react-admin'; +import { Identifier, RaRecord } from 'react-admin'; export interface Sale extends RaRecord { first_name: string; @@ -38,6 +38,8 @@ export interface Contact extends RaRecord { gender: string; sales_id: Identifier; nb_notes: number; + status: string; + background: string; } export interface ContactNote extends RaRecord { @@ -59,6 +61,7 @@ export interface Deal extends RaRecord { amount: number; created_at: string; updated_at: string; + start_at: string; sales_id: Identifier; index: number; nb_notes: number; diff --git a/examples/demo/src/products/ProductReferenceField.tsx b/examples/demo/src/products/ProductReferenceField.tsx index 584cd8f81a8..7ed39f7068e 100644 --- a/examples/demo/src/products/ProductReferenceField.tsx +++ b/examples/demo/src/products/ProductReferenceField.tsx @@ -7,7 +7,7 @@ interface Props { const ProductReferenceField = ( props: Props & - Omit, 'reference' | 'children'> + Omit ) => ( { - const record = useRecordContext(); + const record = useRecordContext(); const createPath = useCreatePath(); if (!record) { return null; diff --git a/examples/demo/src/types.ts b/examples/demo/src/types.ts index cf0c653c501..c9a30c01d4c 100644 --- a/examples/demo/src/types.ts +++ b/examples/demo/src/types.ts @@ -1,4 +1,4 @@ -import { RaRecord, Identifier } from 'react-admin'; +import { Identifier, RaRecord } from 'react-admin'; export type ThemeName = 'light' | 'dark'; @@ -35,6 +35,7 @@ export interface Customer extends RaRecord { groups: string[]; nb_commands: number; total_spent: number; + email: string; } export type OrderStatus = 'ordered' | 'delivered' | 'cancelled'; @@ -44,14 +45,22 @@ export interface Order extends RaRecord { basket: BasketItem[]; date: Date; total: number; + total_ex_taxes: number; + delivery_fees: number; + tax_rate: number; + taxes: number; + customer_id: Identifier; + reference: string; } -export interface BasketItem { +export type BasketItem = { product_id: Identifier; quantity: number; -} +}; -export interface Invoice extends RaRecord {} +export interface Invoice extends RaRecord { + date: Date; +} export type ReviewStatus = 'accepted' | 'pending' | 'rejected'; @@ -60,6 +69,7 @@ export interface Review extends RaRecord { status: ReviewStatus; customer_id: Identifier; product_id: Identifier; + comment: string; } declare global { diff --git a/examples/demo/src/visitors/FullNameField.tsx b/examples/demo/src/visitors/FullNameField.tsx index 5c2137c692b..c0a2c2f4686 100644 --- a/examples/demo/src/visitors/FullNameField.tsx +++ b/examples/demo/src/visitors/FullNameField.tsx @@ -38,7 +38,7 @@ const FullNameField = (props: Props) => { }; FullNameField.defaultProps = { - source: 'last_name', + source: 'last_name' as const, label: 'resources.customers.fields.name', }; diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts index d8df1d073e9..bdf711e9a3e 100644 --- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts +++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts @@ -5,11 +5,13 @@ import { useGetManyAggregate } from '../../dataProvider'; import { ListControllerResult, useList } from '../list'; import { useNotify } from '../../notification'; -export interface UseReferenceArrayFieldControllerParams { +export interface UseReferenceArrayFieldControllerParams< + RecordType extends RaRecord = RaRecord +> { filter?: any; page?: number; perPage?: number; - record?: RaRecord; + record?: RecordType; reference: string; resource: string; sort?: SortPayload; @@ -43,8 +45,11 @@ const defaultSort = { field: null, order: null }; * * @returns {ListControllerResult} The reference props */ -export const useReferenceArrayFieldController = ( - props: UseReferenceArrayFieldControllerParams +export const useReferenceArrayFieldController = < + RecordType extends RaRecord = RaRecord, + ReferenceRecordType extends RaRecord = RaRecord +>( + props: UseReferenceArrayFieldControllerParams ): ListControllerResult => { const { filter = defaultFilter, @@ -64,7 +69,9 @@ export const useReferenceArrayFieldController = ( return emptyArray; }, [value, source]); - const { data, error, isLoading, isFetching, refetch } = useGetManyAggregate( + const { data, error, isLoading, isFetching, refetch } = useGetManyAggregate< + ReferenceRecordType + >( reference, { ids }, { @@ -88,7 +95,7 @@ export const useReferenceArrayFieldController = ( } ); - const listProps = useList({ + const listProps = useList({ data, error, filter, diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts index 1619c4c4d42..7fff1d2b7c0 100644 --- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts +++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts @@ -5,18 +5,20 @@ import isEqual from 'lodash/isEqual'; import { useSafeSetState, removeEmpty } from '../../util'; import { useGetManyReference } from '../../dataProvider'; import { useNotify } from '../../notification'; -import { RaRecord, SortPayload } from '../../types'; +import { Identifier, RaRecord, SortPayload } from '../../types'; import { ListControllerResult } from '../list'; import usePaginationState from '../usePaginationState'; import { useRecordSelection } from '../list/useRecordSelection'; import useSortState from '../useSortState'; import { useResourceContext } from '../../core'; -export interface UseReferenceManyFieldControllerParams { +export interface UseReferenceManyFieldControllerParams< + RecordType extends RaRecord = RaRecord +> { filter?: any; page?: number; perPage?: number; - record?: RaRecord; + record?: RecordType; reference: string; resource?: string; sort?: SortPayload; @@ -52,9 +54,12 @@ const defaultFilter = {}; * * @returns {ListControllerResult} The reference many props */ -export const useReferenceManyFieldController = ( - props: UseReferenceManyFieldControllerParams -): ListControllerResult => { +export const useReferenceManyFieldController = < + RecordType extends RaRecord = RaRecord, + ReferenceRecordType extends RaRecord = RaRecord +>( + props: UseReferenceManyFieldControllerParams +): ListControllerResult => { const { reference, record, @@ -147,11 +152,11 @@ export const useReferenceManyFieldController = ( isFetching, isLoading, refetch, - } = useGetManyReference( + } = useGetManyReference( reference, { target, - id: get(record, source), + id: get(record, source) as Identifier, pagination: { page, perPage }, sort, filter: filterValues, diff --git a/packages/ra-core/src/controller/list/useRecordSelection.ts b/packages/ra-core/src/controller/list/useRecordSelection.ts index 572dff04420..46816922f6f 100644 --- a/packages/ra-core/src/controller/list/useRecordSelection.ts +++ b/packages/ra-core/src/controller/list/useRecordSelection.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { useStore, useRemoveFromStore } from '../../store'; -import { Identifier } from '../../types'; +import { RaRecord } from '../../types'; /** * Get the list of selected items for a resource, and callbacks to change the selection @@ -10,14 +10,14 @@ import { Identifier } from '../../types'; * * @returns {Object} Destructure as [selectedIds, { select, toggle, clearSelection }]. */ -export const useRecordSelection = ( +export const useRecordSelection = ( resource: string ): [ - Identifier[], + RecordType['id'][], { - select: (ids: Identifier[]) => void; - unselect: (ids: Identifier[]) => void; - toggle: (id: Identifier) => void; + select: (ids: RecordType['id'][]) => void; + unselect: (ids: RecordType['id'][]) => void; + toggle: (id: RecordType['id']) => void; clearSelection: () => void; } ] => { @@ -27,18 +27,18 @@ export const useRecordSelection = ( const selectionModifiers = useMemo( () => ({ - select: (idsToAdd: Identifier[]) => { + select: (idsToAdd: RecordType['id'][]) => { if (!idsToAdd) return; setIds([...idsToAdd]); }, - unselect(idsToRemove: Identifier[]) { + unselect(idsToRemove: RecordType['id'][]) { if (!idsToRemove || idsToRemove.length === 0) return; setIds(ids => { if (!Array.isArray(ids)) return []; return ids.filter(id => !idsToRemove.includes(id)); }); }, - toggle: (id: Identifier) => { + toggle: (id: RecordType['id']) => { if (typeof id === 'undefined') return; setIds(ids => { if (!Array.isArray(ids)) return [...ids]; diff --git a/packages/ra-core/src/controller/record/useRecordContext.ts b/packages/ra-core/src/controller/record/useRecordContext.ts index 10e54b0e8d9..8d88a6e0a80 100644 --- a/packages/ra-core/src/controller/record/useRecordContext.ts +++ b/packages/ra-core/src/controller/record/useRecordContext.ts @@ -1,5 +1,6 @@ import { useContext } from 'react'; import { RecordContext } from './RecordContext'; +import { RaRecord } from '../../types'; /** * Hook to read the record from a RecordContext. @@ -30,7 +31,7 @@ import { RecordContext } from './RecordContext'; * @returns {RaRecord} A record object */ export const useRecordContext = < - RecordType extends Record = Record + RecordType extends RaRecord | Omit = RaRecord >( props?: UseRecordContextParams ): RecordType | undefined => { @@ -42,7 +43,7 @@ export const useRecordContext = < }; export interface UseRecordContextParams< - RecordType extends Record = Record + RecordType extends RaRecord | Omit = RaRecord > { record?: RecordType; [key: string]: any; diff --git a/packages/ra-core/src/controller/useReference.ts b/packages/ra-core/src/controller/useReference.ts index 825d7da1004..4da25d1d039 100644 --- a/packages/ra-core/src/controller/useReference.ts +++ b/packages/ra-core/src/controller/useReference.ts @@ -44,7 +44,7 @@ export interface UseReferenceResult { * * @returns {UseReferenceResult} The reference record */ -export const useReference = ({ +export const useReference = ({ reference, id, options = {}, diff --git a/packages/ra-core/src/dataProvider/useCreate.ts b/packages/ra-core/src/dataProvider/useCreate.ts index c1304e51ccb..3b3c7c248f1 100644 --- a/packages/ra-core/src/dataProvider/useCreate.ts +++ b/packages/ra-core/src/dataProvider/useCreate.ts @@ -122,7 +122,7 @@ export const useCreate = < const create = ( callTimeResource: string = resource, - callTimeParams: Partial> = {}, + callTimeParams: Partial>> = {}, createOptions: MutateOptions< RecordType, MutationError, @@ -151,7 +151,7 @@ export const useCreate = < export interface UseCreateMutateParams { resource?: string; - data?: Partial; + data?: Partial>; meta?: any; } diff --git a/packages/ra-core/src/dataProvider/useGetMany.ts b/packages/ra-core/src/dataProvider/useGetMany.ts index d637defe79d..402e8dcff76 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.ts +++ b/packages/ra-core/src/dataProvider/useGetMany.ts @@ -113,5 +113,5 @@ export const useGetMany = ( }; export type UseGetManyHookValue< - RecordType extends RaRecord = RaRecord + RecordType extends RaRecord = any > = UseQueryResult; diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts index ff04f139e16..eab3f64bf21 100644 --- a/packages/ra-core/src/types.ts +++ b/packages/ra-core/src/types.ts @@ -1,5 +1,4 @@ import { ReactNode, ReactElement, ComponentType } from 'react'; - import { WithPermissionsChildrenParams } from './auth/WithPermissions'; import { AuthActionType } from './auth/types'; @@ -186,10 +185,10 @@ export interface GetManyReferenceResult { }; } -export interface UpdateParams { - id: T['id']; - data: Partial; - previousData: T; +export interface UpdateParams { + id: RecordType['id']; + data: Partial; + previousData: RecordType; meta?: any; } export interface UpdateResult { diff --git a/packages/ra-ui-materialui/package.json b/packages/ra-ui-materialui/package.json index 8f99b422f2b..ce4e2f0f691 100644 --- a/packages/ra-ui-materialui/package.json +++ b/packages/ra-ui-materialui/package.json @@ -34,6 +34,7 @@ "expect": "^27.4.6", "file-api": "~0.10.4", "history": "^5.1.0", + "hotscript": "^1.0.12", "ignore-styles": "~5.0.1", "ra-core": "^4.10.2", "ra-i18n-polyglot": "^4.10.2", diff --git a/packages/ra-ui-materialui/src/detail/EditGuesser.tsx b/packages/ra-ui-materialui/src/detail/EditGuesser.tsx index c7efb51dc3f..56da557db73 100644 --- a/packages/ra-ui-materialui/src/detail/EditGuesser.tsx +++ b/packages/ra-ui-materialui/src/detail/EditGuesser.tsx @@ -7,13 +7,16 @@ import { useResourceContext, useEditContext, getElementsFromRecords, + RaRecord, } from 'ra-core'; import { EditProps } from '../types'; import { EditView } from './EditView'; import { editFieldTypes } from './editFieldTypes'; -export const EditGuesser = (props: EditProps) => { +export const EditGuesser = ( + props: EditProps +) => { const { resource, id, @@ -26,7 +29,7 @@ export const EditGuesser = (props: EditProps) => { ...rest } = props; return ( - resource={resource} id={id} mutationMode={mutationMode} diff --git a/packages/ra-ui-materialui/src/field/ArrayField.tsx b/packages/ra-ui-materialui/src/field/ArrayField.tsx index 824fe50a72a..ac2f8a3a721 100644 --- a/packages/ra-ui-materialui/src/field/ArrayField.tsx +++ b/packages/ra-ui-materialui/src/field/ArrayField.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { memo, ReactNode } from 'react'; +import { ReactNode } from 'react'; import get from 'lodash/get'; import { ListContextProvider, @@ -9,7 +9,8 @@ import { FilterPayload, } from 'ra-core'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; /** * Renders an embedded array of objects. @@ -74,30 +75,35 @@ import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; * * @see useListContext */ -export const ArrayField = memo(props => { +const ArrayFieldImpl = < + RecordType extends Record = Record +>( + props: ArrayFieldProps +) => { const { children, resource, source, perPage, sort, filter } = props; const record = useRecordContext(props); - const data = get(record, source, emptyArray) || emptyArray; + const data = + (get(record, source, emptyArray) as Record[]) || + emptyArray; const listContext = useList({ data, resource, perPage, sort, filter }); return ( {children} ); -}); - -// @ts-ignore -ArrayField.propTypes = { - ...fieldPropTypes, }; +ArrayFieldImpl.propTypes = { ...fieldPropTypes }; +ArrayFieldImpl.displayName = 'ArrayFieldImpl'; + +export const ArrayField = genericMemo(ArrayFieldImpl); -export interface ArrayFieldProps extends PublicFieldProps, InjectedFieldProps { +export interface ArrayFieldProps< + RecordType extends Record = Record +> extends FieldProps { children: ReactNode; perPage?: number; sort?: SortPayload; filter?: FilterPayload; } -ArrayField.displayName = 'ArrayField'; - const emptyArray = []; diff --git a/packages/ra-ui-materialui/src/field/BooleanField.stories.tsx b/packages/ra-ui-materialui/src/field/BooleanField.stories.tsx index 4071fe6caf7..79a0b3aafb4 100644 --- a/packages/ra-ui-materialui/src/field/BooleanField.stories.tsx +++ b/packages/ra-ui-materialui/src/field/BooleanField.stories.tsx @@ -22,3 +22,35 @@ export const Basic = (props: BooleanFieldProps) => { export const NoFalseIcon = () => ; export const NoTrueIcon = () => ; + +type Post = { + id: number; + published: boolean; + deep: { + reported: boolean; + }; + title: string; +}; + +export const Typed = () => { + const [published, setPublished] = React.useState(true); + return ( + + setPublished(e.target.checked)} + /> + + record={{ + id: 1, + published, + deep: { reported: false }, + title: '', + }} + source="deep.reported" + sortBy="published" + /> + + ); +}; diff --git a/packages/ra-ui-materialui/src/field/BooleanField.tsx b/packages/ra-ui-materialui/src/field/BooleanField.tsx index 2ee6c8296e2..83816d494bc 100644 --- a/packages/ra-ui-materialui/src/field/BooleanField.tsx +++ b/packages/ra-ui-materialui/src/field/BooleanField.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { styled } from '@mui/material/styles'; -import { memo, FunctionComponent } from 'react'; import { SvgIconComponent } from '@mui/icons-material'; import PropTypes from 'prop-types'; import get from 'lodash/get'; @@ -8,73 +7,75 @@ import DoneIcon from '@mui/icons-material/Done'; import ClearIcon from '@mui/icons-material/Clear'; import { Tooltip, Typography, TypographyProps } from '@mui/material'; import { useTranslate, useRecordContext } from 'ra-core'; - -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; +import { FieldProps, fieldPropTypes } from './types'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -export const BooleanField: FunctionComponent = memo( - props => { - const { - className, - emptyText, - source, - valueLabelTrue, - valueLabelFalse, - TrueIcon = DoneIcon, - FalseIcon = ClearIcon, - looseValue = false, - ...rest - } = props; - const record = useRecordContext(props); - const translate = useTranslate(); - - const value = get(record, source); - const isTruthyValue = value === true || (looseValue && value); - let ariaLabel = value ? valueLabelTrue : valueLabelFalse; +const BooleanFieldImpl = < + RecordType extends Record = Record +>( + props: BooleanFieldProps +) => { + const { + className, + emptyText, + source, + valueLabelTrue, + valueLabelFalse, + TrueIcon = DoneIcon, + FalseIcon = ClearIcon, + looseValue = false, + ...rest + } = props; + const record = useRecordContext(props); + const translate = useTranslate(); - if (!ariaLabel) { - ariaLabel = isTruthyValue ? 'ra.boolean.true' : 'ra.boolean.false'; - } + const value = get(record, source); + const isTruthyValue = value === true || (looseValue && value); + let ariaLabel = value ? valueLabelTrue : valueLabelFalse; - if (looseValue || value === false || value === true) { - return ( - - - {isTruthyValue ? ( - TrueIcon ? ( - - ) : ( - <> - ) - ) : FalseIcon ? ( - - ) : ( - <> - )} - - - ); - } + if (!ariaLabel) { + ariaLabel = isTruthyValue ? 'ra.boolean.true' : 'ra.boolean.false'; + } + if (looseValue || value === false || value === true) { return ( - - {emptyText && translate(emptyText, { _: emptyText })} - + + {isTruthyValue ? ( + TrueIcon ? ( + + ) : ( + <> + ) + ) : FalseIcon ? ( + + ) : ( + <> + )} + + ); } -); -BooleanField.propTypes = { + return ( + + {emptyText && translate(emptyText, { _: emptyText })} + + ); +}; + +BooleanFieldImpl.propTypes = { // @ts-ignore ...Typography.propTypes, ...fieldPropTypes, @@ -84,12 +85,13 @@ BooleanField.propTypes = { FalseIcon: PropTypes.elementType, looseValue: PropTypes.bool, }; +BooleanFieldImpl.displayName = 'BooleanFieldImpl'; -BooleanField.displayName = 'BooleanField'; +export const BooleanField = genericMemo(BooleanFieldImpl); -export interface BooleanFieldProps - extends PublicFieldProps, - InjectedFieldProps, +export interface BooleanFieldProps< + RecordType extends Record = Record +> extends FieldProps, Omit { valueLabelTrue?: string; valueLabelFalse?: string; diff --git a/packages/ra-ui-materialui/src/field/ChipField.tsx b/packages/ra-ui-materialui/src/field/ChipField.tsx index 8b21111ca46..baed073cc55 100644 --- a/packages/ra-ui-materialui/src/field/ChipField.tsx +++ b/packages/ra-ui-materialui/src/field/ChipField.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { styled } from '@mui/material/styles'; -import { memo, FC } from 'react'; import get from 'lodash/get'; import Chip, { ChipProps } from '@mui/material/Chip'; import Typography from '@mui/material/Typography'; @@ -8,11 +7,16 @@ import clsx from 'clsx'; import { useRecordContext, useTranslate } from 'ra-core'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; -export const ChipField: FC = memo(props => { +const ChipFieldImpl = < + RecordType extends Record = Record +>( + props: ChipFieldProps +) => { const { className, source, emptyText, ...rest } = props; - const record = useRecordContext(props); + const record = useRecordContext(props); const value = get(record, source); const translate = useTranslate(); @@ -36,19 +40,20 @@ export const ChipField: FC = memo(props => { {...sanitizeFieldRestProps(rest)} /> ); -}); +}; -ChipField.propTypes = { +ChipFieldImpl.propTypes = { // @ts-ignore - ...ChipField.propTypes, + ...Chip.propTypes, ...fieldPropTypes, }; +ChipFieldImpl.displayName = 'ChipFieldImpl'; -ChipField.displayName = 'ChipField'; +export const ChipField = genericMemo(ChipFieldImpl); -export interface ChipFieldProps - extends PublicFieldProps, - InjectedFieldProps, +export interface ChipFieldProps< + RecordType extends Record = Record +> extends FieldProps, Omit {} const PREFIX = 'RaChipField'; diff --git a/packages/ra-ui-materialui/src/field/DateField.tsx b/packages/ra-ui-materialui/src/field/DateField.tsx index 36217130b7d..b9af166cec0 100644 --- a/packages/ra-ui-materialui/src/field/DateField.tsx +++ b/packages/ra-ui-materialui/src/field/DateField.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { memo, FC } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import { Typography, TypographyProps } from '@mui/material'; import { useRecordContext, useTranslate } from 'ra-core'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; /** * Display a date value as a locale string. @@ -32,7 +32,11 @@ import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; * // renders the record { id: 1234, new Date('2012-11-07') } as * mercredi 7 novembre 2012 */ -export const DateField: FC = memo(props => { +const DateFieldImpl = < + RecordType extends Record = Record +>( + props: DateFieldProps +) => { const { className, emptyText, @@ -51,7 +55,7 @@ export const DateField: FC = memo(props => { ); } - const record = useRecordContext(props); + const record = useRecordContext(props); if (!record) { return null; } @@ -70,7 +74,13 @@ export const DateField: FC = memo(props => { ) : null; } - const date = value instanceof Date ? value : new Date(value); + const date = + value instanceof Date + ? value + : typeof value === 'string' || typeof value === 'number' + ? new Date(value) + : undefined; + let dateOptions = options; if ( typeof value === 'string' && @@ -108,9 +118,9 @@ export const DateField: FC = memo(props => { {dateString} ); -}); +}; -DateField.propTypes = { +DateFieldImpl.propTypes = { // @ts-ignore ...Typography.propTypes, ...fieldPropTypes, @@ -122,12 +132,13 @@ DateField.propTypes = { showTime: PropTypes.bool, showDate: PropTypes.bool, }; +DateFieldImpl.displayName = 'DateFieldImpl'; -DateField.displayName = 'DateField'; +export const DateField = genericMemo(DateFieldImpl); -export interface DateFieldProps - extends PublicFieldProps, - InjectedFieldProps, +export interface DateFieldProps< + RecordType extends Record = Record +> extends FieldProps, Omit { locales?: Intl.LocalesArgument; options?: Intl.DateTimeFormatOptions; diff --git a/packages/ra-ui-materialui/src/field/EmailField.tsx b/packages/ra-ui-materialui/src/field/EmailField.tsx index 1918adea562..ba02a22cb35 100644 --- a/packages/ra-ui-materialui/src/field/EmailField.tsx +++ b/packages/ra-ui-materialui/src/field/EmailField.tsx @@ -1,14 +1,18 @@ import * as React from 'react'; -import { memo, FC } from 'react'; import get from 'lodash/get'; import Typography from '@mui/material/Typography'; import { Link, LinkProps } from '@mui/material'; import { useRecordContext, useTranslate } from 'ra-core'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; -export const EmailField: FC = memo(props => { +const EmailFieldImpl = < + RecordType extends Record = Record +>( + props: EmailFieldProps +) => { const { className, source, emptyText, ...rest } = props; const record = useRecordContext(props); const value = get(record, source); @@ -38,14 +42,16 @@ export const EmailField: FC = memo(props => { {value} ); -}); +}; -EmailField.propTypes = fieldPropTypes; -EmailField.displayName = 'EmailField'; +EmailFieldImpl.propTypes = fieldPropTypes; +EmailFieldImpl.displayName = 'EmailFieldImpl'; -export interface EmailFieldProps - extends PublicFieldProps, - InjectedFieldProps, +export const EmailField = genericMemo(EmailFieldImpl); + +export interface EmailFieldProps< + RecordType extends Record = Record +> extends FieldProps, Omit {} // useful to prevent click bubbling in a Datagrid with rowClick diff --git a/packages/ra-ui-materialui/src/field/FileField.tsx b/packages/ra-ui-materialui/src/field/FileField.tsx index 69084219901..1c055bb9f9c 100644 --- a/packages/ra-ui-materialui/src/field/FileField.tsx +++ b/packages/ra-ui-materialui/src/field/FileField.tsx @@ -6,7 +6,7 @@ import Typography from '@mui/material/Typography'; import { useRecordContext, useTranslate } from 'ra-core'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; import { SxProps } from '@mui/system'; import { Link } from '@mui/material'; @@ -23,7 +23,11 @@ import { Link } from '@mui/material'; * Presentation * */ -export const FileField = (props: FileFieldProps) => { +export const FileField = < + RecordType extends Record = Record +>( + props: FileFieldProps +) => { const { className, emptyText, @@ -82,12 +86,12 @@ export const FileField = (props: FileFieldProps) => { ); } - const titleValue = get(record, title) || title; + const titleValue = get(record, title)?.toString() || title; return ( { ); }; -export interface FileFieldProps extends PublicFieldProps, InjectedFieldProps { +export interface FileFieldProps< + RecordType extends Record = Record +> extends FieldProps { src?: string; title?: string; target?: string; diff --git a/packages/ra-ui-materialui/src/field/FunctionField.tsx b/packages/ra-ui-materialui/src/field/FunctionField.tsx index c9e06114407..298ee1e779a 100644 --- a/packages/ra-ui-materialui/src/field/FunctionField.tsx +++ b/packages/ra-ui-materialui/src/field/FunctionField.tsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import Typography, { TypographyProps } from '@mui/material/Typography'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; /** * Field using a render function @@ -50,8 +50,7 @@ FunctionField.propTypes = { export interface FunctionFieldProps< RecordType extends Record = Record -> extends PublicFieldProps, - InjectedFieldProps, +> extends FieldProps, Omit { render: (record?: RecordType, source?: string) => any; } diff --git a/packages/ra-ui-materialui/src/field/ImageField.tsx b/packages/ra-ui-materialui/src/field/ImageField.tsx index 5cd746479b3..2ca3e1facb1 100644 --- a/packages/ra-ui-materialui/src/field/ImageField.tsx +++ b/packages/ra-ui-materialui/src/field/ImageField.tsx @@ -6,10 +6,14 @@ import get from 'lodash/get'; import { useRecordContext, useTranslate } from 'ra-core'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; import { SxProps } from '@mui/system'; -export const ImageField = (props: ImageFieldProps) => { +export const ImageField = < + RecordType extends Record = Record +>( + props: ImageFieldProps +) => { const { className, emptyText, source, src, title, ...rest } = props; const record = useRecordContext(props); const sourceValue = get(record, source); @@ -58,14 +62,14 @@ export const ImageField = (props: ImageFieldProps) => { ); } - const titleValue = get(record, title) || title; + const titleValue = get(record, title)?.toString() || title; return ( {titleValue} @@ -104,7 +108,9 @@ const Root = styled(Box, { }, }); -export interface ImageFieldProps extends PublicFieldProps, InjectedFieldProps { +export interface ImageFieldProps< + RecordType extends Record = Record +> extends FieldProps { src?: string; title?: string; sx?: SxProps; diff --git a/packages/ra-ui-materialui/src/field/NumberField.tsx b/packages/ra-ui-materialui/src/field/NumberField.tsx index f66fed3afa4..169e2638946 100644 --- a/packages/ra-ui-materialui/src/field/NumberField.tsx +++ b/packages/ra-ui-materialui/src/field/NumberField.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { memo, FC } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import Typography, { TypographyProps } from '@mui/material/Typography'; import { useRecordContext, useTranslate } from 'ra-core'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; /** * Display a numeric value as a locale string. @@ -36,7 +36,11 @@ import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; * // renders the record { id: 1234, price: 25.99 } as * 25,99 $US */ -export const NumberField: FC = memo(props => { +const NumberFieldImpl = < + RecordType extends Record = Record +>( + props: NumberFieldProps +) => { const { className, emptyText, @@ -46,7 +50,7 @@ export const NumberField: FC = memo(props => { textAlign, ...rest } = props; - const record = useRecordContext(props); + const record = useRecordContext(props); const translate = useTranslate(); if (!record) { @@ -74,19 +78,14 @@ export const NumberField: FC = memo(props => { className={className} {...sanitizeFieldRestProps(rest)} > - {hasNumberFormat ? value.toLocaleString(locales, options) : value} + {hasNumberFormat && typeof value === 'number' + ? value.toLocaleString(locales, options) + : value} ); -}); - -// what? TypeScript loses the displayName if we don't set it explicitly -NumberField.displayName = 'NumberField'; - -NumberField.defaultProps = { - textAlign: 'right', }; -NumberField.propTypes = { +NumberFieldImpl.propTypes = { // @ts-ignore ...Typography.propTypes, ...fieldPropTypes, @@ -97,9 +96,17 @@ NumberField.propTypes = { options: PropTypes.object, }; -export interface NumberFieldProps - extends PublicFieldProps, - InjectedFieldProps, +// what? TypeScript loses the displayName if we don't set it explicitly +NumberFieldImpl.displayName = 'NumberFieldImpl'; +NumberFieldImpl.defaultProps = { + textAlign: 'right', +}; + +export const NumberField = genericMemo(NumberFieldImpl); + +export interface NumberFieldProps< + RecordType extends Record = Record +> extends FieldProps, Omit { locales?: string | string[]; options?: object; diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx index fb51b85e396..f2ca437e403 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx @@ -11,11 +11,12 @@ import { ResourceContextProvider, useRecordContext, useResourceDefinition, + RaRecord, } from 'ra-core'; import { styled } from '@mui/material/styles'; import { SxProps } from '@mui/system'; -import { fieldPropTypes, PublicFieldProps, InjectedFieldProps } from './types'; +import { fieldPropTypes, FieldProps } from './types'; import { LinearProgress } from '../layout'; import { SingleFieldList } from '../list/SingleFieldList'; import { ChipField } from './ChipField'; @@ -76,7 +77,12 @@ import { ChipField } from './ChipField'; * ... * */ -export const ReferenceArrayField: FC = props => { +export const ReferenceArrayField = < + RecordType extends RaRecord = RaRecord, + ReferenceRecordType extends RaRecord = RaRecord +>( + props: ReferenceArrayFieldProps +) => { const { filter, page = 1, @@ -87,7 +93,10 @@ export const ReferenceArrayField: FC = props => { source, } = props; const record = useRecordContext(props); - const controllerProps = useReferenceArrayFieldController({ + const controllerProps = useReferenceArrayFieldController< + RecordType, + ReferenceRecordType + >({ filter, page, perPage, @@ -119,16 +128,15 @@ ReferenceArrayField.propTypes = { source: PropTypes.string.isRequired, }; -export interface ReferenceArrayFieldProps - extends PublicFieldProps, - InjectedFieldProps { +export interface ReferenceArrayFieldProps< + RecordType extends RaRecord = RaRecord +> extends FieldProps { children?: ReactNode; filter?: FilterPayload; page?: number; pagination?: ReactElement; perPage?: number; reference: string; - resource?: string; sort?: SortPayload; sx?: SxProps; } diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.tsx index 5c5196f19d0..0ac9a1ba207 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceField.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { FC, memo, ReactNode } from 'react'; +import { ReactNode } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import { Typography, SxProps } from '@mui/material'; @@ -11,19 +11,20 @@ import { LinkToType, ResourceContextProvider, RecordContextProvider, - RaRecord, useRecordContext, useCreatePath, Identifier, useGetRecordRepresentation, useResourceDefinition, useTranslate, + RaRecord, } from 'ra-core'; +import { UseQueryOptions } from 'react-query'; import { LinearProgress } from '../layout'; import { Link } from '../Link'; -import { PublicFieldProps, fieldPropTypes, InjectedFieldProps } from './types'; -import { UseQueryOptions } from 'react-query'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; /** * Fetch reference record, and render its representation, or delegate rendering to child component. @@ -56,9 +57,14 @@ import { UseQueryOptions } from 'react-query'; * In previous versions of React-Admin, the prop `linkType` was used. It is now deprecated and replaced with `link`. However * backward-compatibility is still kept */ -export const ReferenceField: FC = props => { +export const ReferenceField = < + RecordType extends Record = Record, + ReferenceRecordType extends RaRecord = RaRecord +>( + props: ReferenceFieldProps +) => { const { source, emptyText, ...rest } = props; - const record = useRecordContext(props); + const record = useRecordContext(props); const id = get(record, source); const translate = useTranslate(); @@ -69,11 +75,11 @@ export const ReferenceField: FC = props => { ) : null ) : ( - {...rest} emptyText={emptyText} record={record} - id={id} + id={id as Identifier} /> ); }; @@ -103,16 +109,18 @@ ReferenceField.defaultProps = { link: 'edit', }; -export interface ReferenceFieldProps - extends PublicFieldProps, - InjectedFieldProps { +export interface ReferenceFieldProps< + RecordType extends Record = Record, + ReferenceRecordType extends RaRecord = RaRecord +> extends Omit, 'source'>, + Required, 'source'>> { children?: ReactNode; - queryOptions?: UseQueryOptions & { meta?: any }; + queryOptions?: UseQueryOptions & { + meta?: any; + }; reference: string; - resource?: string; - source: string; translateChoice?: Function | boolean; - link?: LinkToType; + link?: LinkToType; sx?: SxProps; } @@ -120,37 +128,31 @@ export interface ReferenceFieldProps * This intermediate component is made necessary by the useReference hook, * which cannot be called conditionally when get(record, source) is empty. */ -export const NonEmptyReferenceField: FC< - Omit & { id: Identifier } -> = ({ children, id, record, reference, link, queryOptions, ...props }) => { - const createPath = useCreatePath(); - const resourceDefinition = useResourceDefinition({ resource: reference }); - - const resourceLinkPath = - link === false || - (link === 'edit' && !resourceDefinition.hasEdit) || - (link === 'show' && !resourceDefinition.hasShow) - ? false - : createPath({ - resource: reference, - id, - type: - typeof link === 'function' - ? link(record, reference) - : link, - }); - +export const NonEmptyReferenceField = < + RecordType extends Record = Record, + ReferenceRecordType extends RaRecord = RaRecord +>({ + children, + id, + reference, + queryOptions, + link, + ...props +}: Omit, 'source'> & { + id: Identifier; +}) => { return ( - reference={reference} {...props} - {...useReference({ + {...useReference({ reference, id, options: queryOptions, })} - resourceLinkPath={resourceLinkPath} + resourceLinkPath={link} > {children} @@ -161,7 +163,11 @@ export const NonEmptyReferenceField: FC< // useful to prevent click bubbling in a datagrid with rowClick const stopPropagation = e => e.stopPropagation(); -export const ReferenceFieldView: FC = props => { +export const ReferenceFieldView = < + RecordType extends Record = Record +>( + props: ReferenceFieldViewProps +) => { const { children, className, @@ -175,6 +181,8 @@ export const ReferenceFieldView: FC = props => { } = props; const getRecordRepresentation = useGetRecordRepresentation(reference); const translate = useTranslate(); + const createPath = useCreatePath(); + const resourceDefinition = useResourceDefinition({ resource: reference }); if (error) { return ( @@ -197,17 +205,31 @@ export const ReferenceFieldView: FC = props => { ) : null; } + const link = + resourceLinkPath === false || + (resourceLinkPath === 'edit' && !resourceDefinition.hasEdit) || + (resourceLinkPath === 'show' && !resourceDefinition.hasShow) + ? false + : createPath({ + resource: reference, + id: referenceRecord.id, + type: + typeof resourceLinkPath === 'function' + ? resourceLinkPath(referenceRecord, reference) + : resourceLinkPath, + }); + let child = children || ( {getRecordRepresentation(referenceRecord)} ); - return resourceLinkPath ? ( + return link ? ( @@ -230,27 +252,30 @@ ReferenceFieldView.propTypes = { reference: PropTypes.string, referenceRecord: PropTypes.any, resource: PropTypes.string, + // @ts-ignore resourceLinkPath: PropTypes.oneOfType([ PropTypes.string, - PropTypes.oneOf([false]), - ]) as React.Validator, + PropTypes.bool, + PropTypes.func, + ]).isRequired, source: PropTypes.string, translateChoice: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), }; -export interface ReferenceFieldViewProps - extends PublicFieldProps, - InjectedFieldProps, +export interface ReferenceFieldViewProps< + RecordType extends Record = Record, + ReferenceRecordType extends RaRecord = RaRecord +> extends FieldProps, UseReferenceResult { + children?: ReactNode; reference: string; resource?: string; translateChoice?: Function | boolean; - resourceLinkPath?: string | false; - children?: ReactNode; + resourceLinkPath?: LinkToType; sx?: SxProps; } -const PureReferenceFieldView = memo(ReferenceFieldView); +const PureReferenceFieldView = genericMemo(ReferenceFieldView); const PREFIX = 'RaReferenceField'; diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx index a81bc720e04..b5d3f84130f 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx @@ -5,11 +5,12 @@ import { useTimeout, useCreatePath, SortPayload, + RaRecord, } from 'ra-core'; import { Typography, TypographyProps, CircularProgress } from '@mui/material'; import ErrorIcon from '@mui/icons-material/Error'; -import { PublicFieldProps, InjectedFieldProps } from './types'; +import { FieldProps } from './types'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; import { Link } from '../Link'; @@ -27,7 +28,9 @@ import { Link } from '../Link'; * @example // Display the number of comments for the current post, with a custom Typography variant * */ -export const ReferenceManyCount = (props: ReferenceManyCountProps) => { +export const ReferenceManyCount = ( + props: ReferenceManyCountProps +) => { const { reference, target, @@ -43,7 +46,9 @@ export const ReferenceManyCount = (props: ReferenceManyCountProps) => { const oneSecondHasPassed = useTimeout(timeout); const createPath = useCreatePath(); - const { isLoading, error, total } = useReferenceManyFieldController({ + const { isLoading, error, total } = useReferenceManyFieldController< + RecordType + >({ filter, sort, page: 1, @@ -94,17 +99,13 @@ export const ReferenceManyCount = (props: ReferenceManyCountProps) => { ); }; -export interface ReferenceManyCountProps - extends PublicFieldProps, - InjectedFieldProps, +export interface ReferenceManyCountProps + extends FieldProps, Omit { reference: string; target: string; sort?: SortPayload; filter?: any; - label?: string; link?: boolean; - resource?: string; - source?: string; timeout?: number; } diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx index c1fa5409c8a..eeb23296ba3 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx @@ -8,9 +8,10 @@ import { ListControllerResult, ResourceContextProvider, useRecordContext, + RaRecord, } from 'ra-core'; -import { PublicFieldProps, fieldPropTypes, InjectedFieldProps } from './types'; +import { fieldPropTypes, FieldProps } from './types'; /** * Render related records to the current one. @@ -58,7 +59,12 @@ import { PublicFieldProps, fieldPropTypes, InjectedFieldProps } from './types'; * ... * */ -export const ReferenceManyField = (props: ReferenceManyFieldProps) => { +export const ReferenceManyField = < + RecordType extends RaRecord = RaRecord, + ReferenceRecordType extends RaRecord = RaRecord +>( + props: ReferenceManyFieldProps +) => { const { children, filter, @@ -73,7 +79,10 @@ export const ReferenceManyField = (props: ReferenceManyFieldProps) => { } = props; const record = useRecordContext(props); - const controllerProps = useReferenceManyFieldController({ + const controllerProps = useReferenceManyFieldController< + RecordType, + ReferenceRecordType + >({ filter, page, perPage, @@ -95,9 +104,9 @@ export const ReferenceManyField = (props: ReferenceManyFieldProps) => { ); }; -export interface ReferenceManyFieldProps - extends PublicFieldProps, - InjectedFieldProps { +export interface ReferenceManyFieldProps< + RecordType extends Record = Record +> extends FieldProps { children: ReactNode; filter?: FilterPayload; page?: number; diff --git a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx index fede818a999..697efbfdda9 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx @@ -13,7 +13,7 @@ import { RaRecord, } from 'ra-core'; -import { PublicFieldProps, fieldPropTypes, InjectedFieldProps } from './types'; +import { fieldPropTypes, FieldProps } from './types'; import { ReferenceFieldView } from './ReferenceField'; /** @@ -26,8 +26,11 @@ import { ReferenceFieldView } from './ReferenceField'; * * */ -export const ReferenceOneField = ( - props: ReferenceOneFieldProps +export const ReferenceOneField = < + RecordType extends RaRecord = RaRecord, + ReferenceRecordType extends RaRecord = RaRecord +>( + props: ReferenceOneFieldProps ) => { const { children, @@ -40,7 +43,7 @@ export const ReferenceOneField = ( link = false, queryOptions, } = props; - const record = useRecordContext(props); + const record = useRecordContext(props); const createPath = useCreatePath(); const translate = useTranslate(); @@ -50,7 +53,7 @@ export const ReferenceOneField = ( referenceRecord, error, refetch, - } = useReferenceOneFieldController({ + } = useReferenceOneFieldController({ record, reference, source, @@ -95,9 +98,10 @@ export const ReferenceOneField = ( ); }; -export interface ReferenceOneFieldProps - extends PublicFieldProps, - InjectedFieldProps { +export interface ReferenceOneFieldProps< + RecordType extends RaRecord = RaRecord, + ReferenceRecordType extends RaRecord = RaRecord +> extends FieldProps { children?: ReactNode; reference: string; target: string; @@ -105,7 +109,7 @@ export interface ReferenceOneFieldProps filter?: any; link?: LinkToType; queryOptions?: UseQueryOptions<{ - data: RecordType[]; + data: ReferenceRecordType[]; total: number; }> & { meta?: any }; } diff --git a/packages/ra-ui-materialui/src/field/RichTextField.tsx b/packages/ra-ui-materialui/src/field/RichTextField.tsx index 597aefabff3..014b0934083 100644 --- a/packages/ra-ui-materialui/src/field/RichTextField.tsx +++ b/packages/ra-ui-materialui/src/field/RichTextField.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { FC, memo } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import Typography, { TypographyProps } from '@mui/material/Typography'; @@ -7,7 +6,8 @@ import { useRecordContext } from 'ra-core'; import purify from 'dompurify'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { InjectedFieldProps, PublicFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; /** * Render an HTML string as rich text @@ -24,49 +24,54 @@ import { InjectedFieldProps, PublicFieldProps, fieldPropTypes } from './types'; * @example // remove all tags and output text only * */ -export const RichTextField: FC = memo( - props => { - const { - className, - emptyText, - source, - stripTags = false, - purifyOptions, - ...rest - } = props; - const record = useRecordContext(props); - const value = get(record, source); +const RichTextFieldImpl = < + RecordType extends Record = Record +>( + props: RichTextFieldProps +) => { + const { + className, + emptyText, + source, + stripTags = false, + purifyOptions, + ...rest + } = props; + const record = useRecordContext(props); + const value = get(record, source)?.toString(); - return ( - - {value == null && emptyText ? ( - emptyText - ) : stripTags ? ( - removeTags(value) - ) : ( - - )} - - ); - } -); + return ( + + {value == null && emptyText ? ( + emptyText + ) : stripTags ? ( + removeTags(value) + ) : ( + + )} + + ); +}; -RichTextField.propTypes = { +RichTextFieldImpl.propTypes = { // @ts-ignore ...Typography.propTypes, ...fieldPropTypes, stripTags: PropTypes.bool, purifyOptions: PropTypes.any, }; +RichTextFieldImpl.displayName = 'RichTextFieldImpl'; + +export const RichTextField = genericMemo(RichTextFieldImpl); // We only support the case when sanitize() returns a string // hence we need to force the RETURN_DOM_FRAGMENT and RETURN_DOM @@ -76,15 +81,13 @@ export type PurifyOptions = purify.Config & { RETURN_DOM?: false | undefined; }; -export interface RichTextFieldProps - extends PublicFieldProps, - InjectedFieldProps, +export interface RichTextFieldProps< + RecordType extends Record = Record +> extends FieldProps, Omit { stripTags?: boolean; purifyOptions?: PurifyOptions; } -RichTextField.displayName = 'RichTextField'; - export const removeTags = (input: string) => input ? input.replace(/<[^>]+>/gm, '') : ''; diff --git a/packages/ra-ui-materialui/src/field/SelectField.tsx b/packages/ra-ui-materialui/src/field/SelectField.tsx index 6c4d4beb552..ce535d9667e 100644 --- a/packages/ra-ui-materialui/src/field/SelectField.tsx +++ b/packages/ra-ui-materialui/src/field/SelectField.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { memo, FC } from 'react'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import { @@ -11,7 +10,8 @@ import { import { Typography, TypographyProps } from '@mui/material'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; /** * Display a value in an enumeration @@ -75,7 +75,11 @@ import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; * * **Tip**: sets `translateChoice` to false by default. */ -export const SelectField: FC = memo(props => { +const SelectFieldImpl = < + RecordType extends Record = Record +>( + props: SelectFieldProps +) => { const { className, emptyText, @@ -122,15 +126,9 @@ export const SelectField: FC = memo(props => { {choiceText} ); -}); - -SelectField.defaultProps = { - optionText: 'name', - optionValue: 'id', - translateChoice: true, }; -SelectField.propTypes = { +SelectFieldImpl.propTypes = { // @ts-ignore ...Typography.propTypes, ...fieldPropTypes, @@ -144,10 +142,17 @@ SelectField.propTypes = { translateChoice: PropTypes.bool, }; -export interface SelectFieldProps - extends ChoicesProps, - PublicFieldProps, - InjectedFieldProps, - Omit {} +SelectFieldImpl.defaultProps = { + optionText: 'name', + optionValue: 'id', + translateChoice: true, +}; +SelectFieldImpl.displayName = 'SelectFieldImpl'; -SelectField.displayName = 'SelectField'; +export const SelectField = genericMemo(SelectFieldImpl); + +export interface SelectFieldProps< + RecordType extends Record = Record +> extends ChoicesProps, + FieldProps, + Omit {} diff --git a/packages/ra-ui-materialui/src/field/TextField.tsx b/packages/ra-ui-materialui/src/field/TextField.tsx index 518f9752eb4..6902122cf9b 100644 --- a/packages/ra-ui-materialui/src/field/TextField.tsx +++ b/packages/ra-ui-materialui/src/field/TextField.tsx @@ -1,16 +1,21 @@ import * as React from 'react'; -import { memo, FC, ElementType } from 'react'; +import { ElementType } from 'react'; import get from 'lodash/get'; import Typography, { TypographyProps } from '@mui/material/Typography'; import { useRecordContext } from 'ra-core'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; -export const TextField: FC = memo(props => { +const TextFieldImpl = < + RecordType extends Record = Record +>( + props: TextFieldProps +) => { const { className, source, emptyText, ...rest } = props; const record = useRecordContext(props); - const value = get(record, source); + const value = get(record, source)?.toString(); return ( = memo(props => { : value || emptyText} ); -}); - -// what? TypeScript loses the displayName if we don't set it explicitly -TextField.displayName = 'TextField'; +}; -TextField.propTypes = { +TextFieldImpl.propTypes = { // @ts-ignore ...Typography.propTypes, ...fieldPropTypes, }; -export interface TextFieldProps - extends PublicFieldProps, - InjectedFieldProps, +// what? TypeScript loses the displayName if we don't set it explicitly +TextFieldImpl.displayName = 'TextFieldImpl'; + +export const TextField = genericMemo(TextFieldImpl); + +export interface TextFieldProps< + RecordType extends Record = Record +> extends FieldProps, Omit { // TypographyProps do not expose the component props, see https://github.com/mui/material-ui/issues/19512 component?: ElementType; diff --git a/packages/ra-ui-materialui/src/field/UrlField.tsx b/packages/ra-ui-materialui/src/field/UrlField.tsx index 68e762a1805..d3230448af2 100644 --- a/packages/ra-ui-materialui/src/field/UrlField.tsx +++ b/packages/ra-ui-materialui/src/field/UrlField.tsx @@ -1,12 +1,17 @@ import * as React from 'react'; -import { AnchorHTMLAttributes, memo, FC } from 'react'; +import { AnchorHTMLAttributes } from 'react'; import get from 'lodash/get'; import { sanitizeFieldRestProps } from './sanitizeFieldRestProps'; import { Typography, Link } from '@mui/material'; import { useRecordContext, useTranslate } from 'ra-core'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; +import { genericMemo } from './genericMemo'; -export const UrlField: FC = memo(props => { +const UrlFieldImpl = < + RecordType extends Record = Record +>( + props: UrlFieldProps +) => { const { className, emptyText, source, ...rest } = props; const record = useRecordContext(props); const value = get(record, source); @@ -36,14 +41,16 @@ export const UrlField: FC = memo(props => { {value} ); -}); +}; -UrlField.propTypes = fieldPropTypes; -UrlField.displayName = 'UrlField'; +UrlFieldImpl.propTypes = fieldPropTypes; +UrlFieldImpl.displayName = 'UrlFieldImpl'; -export interface UrlFieldProps - extends PublicFieldProps, - InjectedFieldProps, +export const UrlField = genericMemo(UrlFieldImpl); + +export interface UrlFieldProps< + RecordType extends Record = Record +> extends FieldProps, AnchorHTMLAttributes {} // useful to prevent click bubbling in a Datagrid with rowClick diff --git a/packages/ra-ui-materialui/src/field/WrapperField.tsx b/packages/ra-ui-materialui/src/field/WrapperField.tsx index deab76cb23c..138bb2b67f5 100644 --- a/packages/ra-ui-materialui/src/field/WrapperField.tsx +++ b/packages/ra-ui-materialui/src/field/WrapperField.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { ReactNode } from 'react'; -import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; +import { FieldProps, fieldPropTypes } from './types'; /** * A field rendering its children. @@ -23,16 +23,18 @@ import { PublicFieldProps, InjectedFieldProps, fieldPropTypes } from './types'; * * ); */ -export const WrapperField = ({ children }: WrapperFieldProps) => ( - <>{children} -); +export const WrapperField = < + RecordType extends Record = Record +>({ + children, +}: WrapperFieldProps) => <>{children}; WrapperField.displayName = 'WrapperField'; WrapperField.propTypes = fieldPropTypes; -export interface WrapperFieldProps - extends PublicFieldProps, - InjectedFieldProps { +export interface WrapperFieldProps< + RecordType extends Record = Record +> extends FieldProps { children: ReactNode; } diff --git a/packages/ra-ui-materialui/src/field/genericMemo.ts b/packages/ra-ui-materialui/src/field/genericMemo.ts new file mode 100644 index 00000000000..21cd94162f7 --- /dev/null +++ b/packages/ra-ui-materialui/src/field/genericMemo.ts @@ -0,0 +1,21 @@ +import { FunctionComponent, memo } from 'react'; + +/** + * A version of React.memo that preserves the original component type allowing it to accept generics. + * See {@link https://stackoverflow.com/a/70890101} + */ +export const genericMemo: (component: T) => T = < + T extends FunctionComponent +>( + component: T +) => { + const result = (memo(component) as unknown) as T; + + // We have to set the propTypes, defaultProps and displayName on both the field implementation and the memoized version. + // On the implementation so that the memoized version can pick them up and users may reference the defaultProps in their components. + // On the memoized version so that components that inspect their children props may read them. + result.propTypes = component.propTypes; + result.defaultProps = component.defaultProps; + result.displayName = component.displayName?.replace('Impl', ''); + return result; +}; diff --git a/packages/ra-ui-materialui/src/field/index.ts b/packages/ra-ui-materialui/src/field/index.ts index c68d3736618..aef0eef461a 100644 --- a/packages/ra-ui-materialui/src/field/index.ts +++ b/packages/ra-ui-materialui/src/field/index.ts @@ -24,4 +24,4 @@ export * from './TranslatableFieldsTabContent'; export * from './UrlField'; export * from './WrapperField'; -export type { PublicFieldProps, InjectedFieldProps, FieldProps }; +export type { FieldProps, InjectedFieldProps, PublicFieldProps }; diff --git a/packages/ra-ui-materialui/src/field/types.ts b/packages/ra-ui-materialui/src/field/types.ts index 071be799c79..85cac5ed951 100644 --- a/packages/ra-ui-materialui/src/field/types.ts +++ b/packages/ra-ui-materialui/src/field/types.ts @@ -1,19 +1,48 @@ import { ReactElement } from 'react'; -import { RaRecord } from 'ra-core'; import PropTypes from 'prop-types'; import { TableCellProps } from '@mui/material/TableCell'; +import { Call, Objects } from 'hotscript'; type TextAlign = TableCellProps['align']; type SortOrder = 'ASC' | 'DESC'; +type AnyString = string & {}; -export interface FieldProps - extends PublicFieldProps, - InjectedFieldProps {} +export interface FieldProps< + RecordType extends Record = Record +> { + sortBy?: Call | AnyString; + sortByOrder?: SortOrder; + source?: Call extends never + ? AnyString + : Call; + label?: string | ReactElement | boolean; + sortable?: boolean; + className?: string; + cellClassName?: string; + headerClassName?: string; + /* + * @deprecated this property is not used anymore + */ + formClassName?: string; + textAlign?: TextAlign; + emptyText?: string; + fullWidth?: boolean; + record?: RecordType; + resource?: string; +} -export interface PublicFieldProps { - sortBy?: string; +/** + * @deprecated use FieldProps instead + */ +export interface PublicFieldProps< + RecordType extends Record = Record, + SortByType = unknown +> { + sortBy?: unknown extends SortByType + ? Call + : SortByType; sortByOrder?: SortOrder; - source?: string; + source?: Call; label?: string | ReactElement | boolean; sortable?: boolean; className?: string; @@ -26,8 +55,13 @@ export interface PublicFieldProps { textAlign?: TextAlign; emptyText?: string; fullWidth?: boolean; + record?: RecordType; + resource?: string; } +/** + * @deprecated use FieldProps instead + */ export interface InjectedFieldProps { record?: RecordType; resource?: string; diff --git a/packages/ra-ui-materialui/src/types.ts b/packages/ra-ui-materialui/src/types.ts index 561945205ed..98f649b248d 100644 --- a/packages/ra-ui-materialui/src/types.ts +++ b/packages/ra-ui-materialui/src/types.ts @@ -12,7 +12,7 @@ import { import { UseQueryOptions, UseMutationOptions } from 'react-query'; export interface EditProps< - RecordType extends RaRecord = any, + RecordType extends RaRecord = RaRecord, MutationOptionsError = unknown > { actions?: ReactElement | false; @@ -20,7 +20,7 @@ export interface EditProps< className?: string; component?: ElementType; disableAuthentication?: boolean; - id?: Identifier; + id?: RecordType['id']; mutationMode?: MutationMode; queryOptions?: UseQueryOptions & { meta?: any }; mutationOptions?: UseMutationOptions< diff --git a/yarn.lock b/yarn.lock index 2311edfd168..e636b203988 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13324,6 +13324,13 @@ __metadata: languageName: node linkType: hard +"hotscript@npm:^1.0.12": + version: 1.0.12 + resolution: "hotscript@npm:1.0.12" + checksum: 3ab48ec3405bda539ed5438e177914f65be0facb1acd1aa725ee716216ca87d2d1916f2b6ac58d8b7e94ad517a349c46fd7c45f24fe9f7fedf4799f12cb410d4 + languageName: node + linkType: hard + "html-encoding-sniffer@npm:^2.0.1": version: 2.0.1 resolution: "html-encoding-sniffer@npm:2.0.1" @@ -19153,6 +19160,7 @@ __metadata: expect: ^27.4.6 file-api: ~0.10.4 history: ^5.1.0 + hotscript: ^1.0.12 ignore-styles: ~5.0.1 inflection: ~1.12.0 jsonexport: ^3.2.0