@namroodinc/liquid-editorial 0.1.2 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/AGENTS.md +11 -0
  2. package/README.md +13 -10
  3. package/UPGRADING.md +77 -0
  4. package/dist/chunks/{EntryEditor-cjDk4vdH.cjs → EntryEditor-C32lKKnV.cjs} +2 -2
  5. package/dist/chunks/{EntryEditor-cjDk4vdH.cjs.map → EntryEditor-C32lKKnV.cjs.map} +1 -1
  6. package/dist/chunks/EntryEditor-xVuiqtFo.js +181 -0
  7. package/dist/chunks/{EntryEditor-BF808OZR.js.map → EntryEditor-xVuiqtFo.js.map} +1 -1
  8. package/dist/chunks/RichTextEditor-CSpjsL1z.js +357 -0
  9. package/dist/chunks/RichTextEditor-CSpjsL1z.js.map +1 -0
  10. package/dist/chunks/RichTextEditor-DC_0oN1s.cjs +2 -0
  11. package/dist/chunks/RichTextEditor-DC_0oN1s.cjs.map +1 -0
  12. package/dist/components/AssetGrid/index.js.map +1 -1
  13. package/dist/components/EntryEditor/index.cjs +1 -1
  14. package/dist/components/EntryEditor/index.js +3 -3
  15. package/dist/components/EntryPicker/index.cjs +1 -1
  16. package/dist/components/EntryPicker/index.cjs.map +1 -1
  17. package/dist/components/EntryPicker/index.js +4 -4
  18. package/dist/components/EntryPicker/index.js.map +1 -1
  19. package/dist/components/Field/index.js.map +1 -1
  20. package/dist/components/ImageCropper/index.js.map +1 -1
  21. package/dist/components/Modal/index.cjs +1 -1
  22. package/dist/components/Modal/index.cjs.map +1 -1
  23. package/dist/components/Modal/index.js +2 -2
  24. package/dist/components/Modal/index.js.map +1 -1
  25. package/dist/components/RichTextEditor/index.cjs +1 -1
  26. package/dist/components/RichTextEditor/index.d.cts.map +1 -1
  27. package/dist/components/RichTextEditor/index.d.ts.map +1 -1
  28. package/dist/components/RichTextEditor/index.js +3 -1
  29. package/dist/components/Table/index.cjs +1 -1
  30. package/dist/components/Table/index.cjs.map +1 -1
  31. package/dist/components/Table/index.d.cts +5 -1
  32. package/dist/components/Table/index.d.cts.map +1 -1
  33. package/dist/components/Table/index.d.ts +5 -1
  34. package/dist/components/Table/index.d.ts.map +1 -1
  35. package/dist/components/Table/index.js +74 -55
  36. package/dist/components/Table/index.js.map +1 -1
  37. package/dist/components/Table/style.css +4 -0
  38. package/dist/components/Time/index.js.map +1 -1
  39. package/dist/index.cjs +1 -1
  40. package/dist/index.js +13 -13
  41. package/package.json +3 -1
  42. package/dist/chunks/EntryEditor-BF808OZR.js +0 -181
  43. package/dist/chunks/RichTextEditor-DRt-kibg.js +0 -248
  44. package/dist/chunks/RichTextEditor-DRt-kibg.js.map +0 -1
  45. package/dist/chunks/RichTextEditor-tFkISOvq.cjs +0 -2
  46. package/dist/chunks/RichTextEditor-tFkISOvq.cjs.map +0 -1
package/AGENTS.md ADDED
@@ -0,0 +1,11 @@
1
+ # Liquid Editorial agent guidance
2
+
3
+ ## Contributing to this library
4
+
5
+ Add a decision document for every change going forward.
6
+
7
+ Every releasable change set must update the `Unreleased` section in `UPGRADING.md` with changes, adoption steps, compatibility notes, and verification steps for agents in consuming projects. Related edits share that section. Leave version increments to `.github/workflows/release.yml`: every push to `main` automatically bumps the patch version, versions the upgrade notes, validates, commits the release files, and publishes to npm. Keep the manifest and lockfile versions aligned and include this file and `UPGRADING.md` in the package.
8
+
9
+ ## Using the installed package
10
+
11
+ For agents working in a consuming project: read `UPGRADING.md` beside this file in the installed package. Review every entry between the project's previous package version and the newly installed version, apply the relevant adoption steps in the consuming project, and run its verification checks. The contributor rules above apply to this library's source repository.
package/README.md CHANGED
@@ -10,6 +10,8 @@ npm install @namroodinc/liquid-editorial
10
10
 
11
11
  React and React DOM 18.2 through 19 are supported peer dependencies.
12
12
 
13
+ When updating the package, see [the versioned upgrade guide](UPGRADING.md) for adoption and compatibility notes. Agents can start with `AGENTS.md` in the installed package.
14
+
13
15
  ## Use
14
16
 
15
17
  Import the complete component stylesheet once near your application entry point:
@@ -65,6 +67,12 @@ Use `defaultValue` for an uncontrolled editor, `errors` for app-specific message
65
67
 
66
68
  `RichTextEditor` also supports `format="json"` with `value`/`defaultValue` and an `onChange` callback receiving the document. HTML remains its default format. Unsupported JSON content produces an error and cannot be edited until corrected.
67
69
 
70
+ Links open a URL modal from the toolbar or by clicking an existing body link. Save preserves selected text and formatting; with no selection, it inserts the URL as linked text. Existing links offer Remove link in the modal.
71
+
72
+ ## Table states
73
+
74
+ Empty `TableBody` children automatically show “No data found.” across the table; override the text with `Table.emptyMessage`. Use `isEmpty` when custom row components determine whether there are results, or set it to `false` to suppress the message while the UI loads data. Loading indicators and skeleton rows are composed by the consuming UI. Standalone `TableEmpty` uses the same default message and supports a custom `title`. See the [upgrade guide example](UPGRADING.md#013).
75
+
68
76
  ## Themes and fonts
69
77
 
70
78
  Set `data-theme` on the document root to `light`, `dark`, `cherry-glaze`, or `black-hawaii`. The default font tokens use system stacks and can be overridden by the consumer without downloading bundled font files:
@@ -91,7 +99,7 @@ Storybook is the component catalogue and documentation surface. Stories and test
91
99
 
92
100
  ## Publishing
93
101
 
94
- Publishing a GitHub Release runs [the release workflow](.github/workflows/release.yml): it checks the release version, runs `npm run validate`, then publishes `@namroodinc/liquid-editorial` to npm. The release tag must be `v` followed by the exact version in both `package.json` and `package-lock.json`. Stable versions publish to `latest`; versions such as `0.2.0-beta.1` must be marked as GitHub prereleases and publish to `next`. Branch pushes and draft releases run no publishing step.
102
+ Merge a PR into `main` to publish a release. Every push to `main` runs [the release workflow](.github/workflows/release.yml), which installs dependencies, bumps the patch version, moves the `Unreleased` upgrade notes under that version, and runs `npm run validate`. It then commits the version files and guide back to `main` and publishes `@namroodinc/liquid-editorial` to npm under `latest`. All commands run in GitHub Actions; contributors do not run local release commands or create GitHub Releases or tags. Other branches and pull requests do not publish.
95
103
 
96
104
  ### One-time npm setup
97
105
 
@@ -111,18 +119,13 @@ npm trust github @namroodinc/liquid-editorial --repo namroodinc/liquid-editorial
111
119
 
112
120
  npm requires two-factor verification for this setup. The workflow subsequently authenticates through OIDC without an `NPM_TOKEN` secret. Complete the first workflow publish within two days of adding the trusted publisher to avoid configuration expiry. See [npm's trusted publishing guide](https://docs.npmjs.com/trusted-publishers/) for the current setup requirements.
113
121
 
114
- ### Create a release
122
+ ### Release a change
115
123
 
116
- For the next patch release after `0.1.0`:
124
+ Add the change's adoption, compatibility, and verification notes under `Unreleased` in `UPGRADING.md`, then merge the PR to `main`. The workflow handles the version bump; for example, `0.1.4` becomes `0.1.5`. Pull the generated version commit before continuing local work.
117
125
 
118
- ```sh
119
- npm version patch --no-git-tag-version
120
- git add package.json package-lock.json
121
- git commit -m "chore: release v0.1.1"
122
- git push origin main
123
- ```
126
+ Follow **Publish to npm** in GitHub Actions to check the result. Runs are serialized, and the bot's version commit uses `GITHUB_TOKEN`, which does not trigger another workflow run.
124
127
 
125
- On GitHub, publish a Release with tag `v0.1.1` targeting that commit. GitHub can create the tag when publishing the release. For later releases, use the version produced by `npm version`; for a prerelease, use a version such as `0.2.0-beta.1` and select **Set as a pre-release**. Publishing an already-published npm version fails, so every release needs a new version.
128
+ Failures stop the workflow. If validation or the version push fails, npm publishing does not run. If publishing fails after the version commit is pushed, that commit stays on `main`; rerunning the workflow bumps to the next patch version. The workflow does not roll back versions or create GitHub Releases or tags.
126
129
 
127
130
  ## License
128
131
 
package/UPGRADING.md ADDED
@@ -0,0 +1,77 @@
1
+ # Liquid Editorial upgrades
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.5
6
+
7
+ ### Changes
8
+
9
+ - Pushes to `main`, including merged PRs, now automatically bump the patch version and publish the package to npm after validation.
10
+ - The release workflow moves these notes under the new version and commits the version files and guide back to `main`.
11
+
12
+ ### Adoption
13
+
14
+ Library contributors add upgrade guidance here and merge their changes to `main`. GitHub Actions handles versioning, validation, and publishing; there are no local npm release commands or manual GitHub Releases to create. Pull the generated version commit before continuing local work. Consuming applications can update the dependency as usual.
15
+
16
+ ### Compatibility
17
+
18
+ The workflow publishes stable patch releases under `latest`. Creating a GitHub Release no longer triggers publishing. Component APIs are unchanged.
19
+
20
+ ### Verification
21
+
22
+ Check the `Publish to npm` workflow in GitHub Actions and confirm that the new version appears on the npm package page. The workflow runs the existing full validation before committing and publishing.
23
+
24
+ ## 0.1.3
25
+
26
+ ### Changes
27
+
28
+ - Empty tables automatically show “No data found.” with an optional message override. Loading UI remains composed by the consuming application.
29
+ - Rich text links use a URL modal with Save and Cancel. Both the toolbar and a click on a body link open the modal; existing links also offer Remove link.
30
+ - The installed package includes `AGENTS.md` and this versioned upgrade guide for agents working in consuming projects.
31
+
32
+ ### Adoption
33
+
34
+ Use the shared empty message while keeping loading indicators in your application:
35
+
36
+ ```tsx
37
+ <TableFrame>
38
+ <Table emptyMessage="No matching articles."
39
+ isEmpty={!isLoading && articles.length === 0} aria-busy={isLoading}>
40
+ <TableHead>
41
+ <TableRow>
42
+ <TableHeaderCell>Title</TableHeaderCell>
43
+ <TableHeaderCell align="end">Status</TableHeaderCell>
44
+ </TableRow>
45
+ </TableHead>
46
+ <TableBody>
47
+ {articles.map((article) => (
48
+ <TableRow key={article.id}>
49
+ <TableCell emphasis="primary">{article.title}</TableCell>
50
+ <TableCell align="end">{article.status}</TableCell>
51
+ </TableRow>
52
+ ))}
53
+ </TableBody>
54
+ </Table>
55
+ </TableFrame>
56
+ ```
57
+
58
+ The example suppresses the empty message while the application loads data. Compose loading indicators or skeleton rows in your UI as needed; the table renders the body children you provide. Native attributes such as `aria-busy` are forwarded unchanged. Use the library's `TableBody` to receive the automatic empty state.
59
+
60
+ An empty body now shows the default message automatically. Use `emptyMessage` to replace its text, or keep composing a custom row containing `TableEmpty` for descriptions and actions. When a custom row component renders the rows internally, pass `isEmpty={articles.length === 0}` because its rendered children cannot be inferred. `isEmpty={false}` explicitly allows a blank body. Standalone `TableEmpty` also defaults its optional `title` to “No data found.”
61
+
62
+ RTE consumers receive the modal automatically. Select text and choose Add link, or position the cursor in a link and choose Edit link. Clicking an existing body link opens Edit link for the full link, including formatted spans. Save changes only the URL and preserves the link text and formatting. With an empty cursor, Add link inserts the URL as linked text. Remove link keeps the text. Empty or TipTap-disallowed URLs show an error; relative URLs retain their existing support.
63
+
64
+ ### Compatibility
65
+
66
+ Existing table props, native attributes, refs, populated rows, and application-defined loading rows remain supported. Empty bodies now display a message unless explicitly overridden; pass `isEmpty={false}` to retain a blank body, including while data is loading. Components that supply native `tbody` instead of `TableBody` keep managing their own body states. Existing `TableEmpty` descriptions and actions continue to work. No loading or column-count props are added to `Table`.
67
+
68
+ The RTE's HTML and JSON props and `onChange` payloads are unchanged. Opening, typing in, and cancelling the modal emit no content changes. The toolbar's previous immediate Remove link action is now Edit link, with removal available in the modal. Tests that depended on `window.prompt` or the previous toolbar label need updating. A disabled editor cannot open the dialog; replacing its document or disabling it closes an open dialog without applying its draft.
69
+
70
+ The existing aggregate and component CSS imports include the required table and modal styles; continue importing `@namroodinc/liquid-editorial/styles.css` for the complete stylesheet.
71
+
72
+ ### Verify in the consuming project
73
+
74
+ - During initial loading and refetching, check your application's loading UI and confirm the empty message stays hidden until results are known.
75
+ - Check loaded rows, zero results, filtered zero results, custom empty messages, and custom row/header components.
76
+ - Add, edit, remove, and cancel links in both standalone RTEs and `EntryEditor` fields. Check text/formatting preservation, keyboard submission, focus, and that a containing form is not submitted by Save.
77
+ - Run the project's type checks, tests, and build against `0.1.3`.
@@ -1,2 +1,2 @@
1
- const e=require("./RichTextEditor-tFkISOvq.cjs"),t=require("../components/EntryPicker/index.cjs"),n=require("../components/Field/index.cjs"),r=require("../components/NumberField/index.cjs"),i=require("../components/Switch/index.cjs"),a=require("../components/TextField/index.cjs");let o=require("react"),s=require("react/jsx-runtime");var c=new Set([`shortText`,`longText`,`richText`,`number`,`boolean`,`dateTime`,`asset`,`reference`]);function l(e){let t=new Set;for(let n of e.fields){if(!n.id?.trim()||t.has(n.id))throw Error(`Content model field IDs must be nonempty and unique.`);if(!c.has(n.type))throw Error(`Unsupported content field type: ${n.type}`);t.add(n.id)}}function u(e){if(typeof e!=`string`)return;let t=/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?(?:Z|\+00:00)$/.exec(e);if(!t)return;let n=`${t[1]}:${t[2]??`00`}.${(t[3]??``).padEnd(3,`0`)}Z`,r=new Date(n);if(Number.isFinite(r.getTime())&&r.toISOString()===n)return n}function d(e,t){return!!e&&typeof e==`object`&&t in e&&typeof e[t]==`string`&&e[t].trim().length>0}function f(e){return!!e.text?.trim()||e.type===`horizontalRule`||!!e.content?.some(f)}function p(t,n){return l(t),Object.fromEntries(t.fields.flatMap(t=>{let r=Object.hasOwn(n,t.id)?n[t.id]:void 0,i;if(r===void 0)t.required&&(i=`${t.label} is required.`);else switch(t.type){case`shortText`:case`longText`:typeof r==`string`?t.required&&!r.trim()&&(i=`${t.label} is required.`):i=`Enter text.`;break;case`richText`:i=e.n(r),!i&&t.required&&!f(r)&&(i=`${t.label} is required.`);break;case`number`:(typeof r!=`number`||!Number.isFinite(r))&&(i=`Enter a finite number.`);break;case`boolean`:typeof r!=`boolean`&&(i=`Choose a boolean value.`);break;case`dateTime`:u(r)||(i=`Enter a valid UTC date and time.`);break;case`asset`:case`reference`:{let e=t.type===`asset`?`assetId`:`entryId`;(t.multiple?!Array.isArray(r)||!r.every(t=>d(t,e)):!d(r,e))?i=t.multiple?`Choose a list of ${t.type===`asset`?`assets`:`entries`}.`:`Choose an ${t.type===`asset`?`asset`:`entry`}.`:t.required&&Array.isArray(r)&&r.length===0&&(i=`${t.label} is required.`);break}}return i?[[t.id,i]]:[]}))}function m(e,t){let n=e.type===`asset`?`assetId`:`entryId`;return(Array.isArray(t)?t:t===void 0?[]:[t]).flatMap(e=>{if(!e||typeof e!=`object`||!(n in e))return[];let t=e[n];return typeof t==`string`?[t]:[]})}function h({model:c,value:l,defaultValue:d={},onValueChange:f,pickerItems:h={},errors:g={},isDisabled:_=!1,showAllErrors:v=!1,className:y,...b}){let[x,S]=(0,o.useState)(d),[C,w]=(0,o.useState)(()=>new Set),T=l??x,E=p(c,T),D=(e,t)=>{if(_)return;let n={...T};t===void 0?delete n[e]:n[e]=t,l===void 0&&S(n),w(t=>new Set(t).add(e)),f?.(n)};return(0,s.jsx)(`div`,{className:[`ds-entry-editor`,y].filter(Boolean).join(` `),...b,children:c.fields.map(o=>{let c=Object.hasOwn(T,o.id)?T[o.id]:void 0,l=o.type===`asset`||o.type===`reference`,d=o.type===`dateTime`?`${o.label} (UTC)`:o.label,f=g[o.id]??(v||C.has(o.id)?E[o.id]:void 0);return(0,s.jsx)(n.Field,{label:d,hint:o.hint,error:f,isRequired:o.required,isGroup:l,children:({required:n,...l})=>{switch(o.type){case`shortText`:case`longText`:{let e=o.type===`shortText`?a.TextField:a.TextArea;return(0,s.jsx)(e,{...l,required:n,disabled:_,placeholder:o.placeholder,value:typeof c==`string`?c:``,onChange:e=>D(o.id,e.target.value)})}case`number`:return(0,s.jsx)(r.NumberField,{...l,required:n,isDisabled:_,placeholder:o.placeholder,step:`any`,hasSteppers:!1,value:typeof c==`number`&&Number.isFinite(c)?c:``,onValueChange:e=>{(e===``||Number.isFinite(e))&&D(o.id,e===``?void 0:e)}});case`boolean`:return(0,s.jsx)(i.Switch,{...l,isDisabled:_,isSelected:c===!0,onSelectedChange:e=>D(o.id,e)});case`dateTime`:return(0,s.jsx)(a.TextField,{...l,required:n,disabled:_,type:`datetime-local`,step:`0.001`,value:u(c)?.slice(0,-1)??``,onChange:e=>D(o.id,u(`${e.target.value}Z`))});case`richText`:return(0,s.jsx)(e.t,{...l,label:o.label,isRequired:n,isDisabled:_,placeholder:o.placeholder,format:`json`,value:c===void 0?e.r:c,onChange:e=>D(o.id,e)});case`asset`:case`reference`:return(0,s.jsx)(`div`,{...l,role:`group`,children:(0,s.jsx)(t.EntryPicker,{items:h[o.id]??[],value:m(o,c),label:o.type===`asset`?`Assets`:`Entries`,addLabel:`Choose ${o.label}`,mode:o.multiple?`multiple`:`single`,isDisabled:_,onValueChange:e=>{if(o.type===`asset`){let t=e.map(e=>({assetId:e}));D(o.id,o.multiple?t:t[0])}else{let t=e.map(e=>({entryId:e}));D(o.id,o.multiple?t:t[0])}}})})}}},o.id)})})}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return h}});
2
- //# sourceMappingURL=EntryEditor-cjDk4vdH.cjs.map
1
+ const e=require("../components/Field/index.cjs"),t=require("../components/TextField/index.cjs"),n=require("./RichTextEditor-DC_0oN1s.cjs"),r=require("../components/EntryPicker/index.cjs"),i=require("../components/NumberField/index.cjs"),a=require("../components/Switch/index.cjs");let o=require("react"),s=require("react/jsx-runtime");var c=new Set([`shortText`,`longText`,`richText`,`number`,`boolean`,`dateTime`,`asset`,`reference`]);function l(e){let t=new Set;for(let n of e.fields){if(!n.id?.trim()||t.has(n.id))throw Error(`Content model field IDs must be nonempty and unique.`);if(!c.has(n.type))throw Error(`Unsupported content field type: ${n.type}`);t.add(n.id)}}function u(e){if(typeof e!=`string`)return;let t=/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?(?:Z|\+00:00)$/.exec(e);if(!t)return;let n=`${t[1]}:${t[2]??`00`}.${(t[3]??``).padEnd(3,`0`)}Z`,r=new Date(n);if(Number.isFinite(r.getTime())&&r.toISOString()===n)return n}function d(e,t){return!!e&&typeof e==`object`&&t in e&&typeof e[t]==`string`&&e[t].trim().length>0}function f(e){return!!e.text?.trim()||e.type===`horizontalRule`||!!e.content?.some(f)}function p(e,t){return l(e),Object.fromEntries(e.fields.flatMap(e=>{let r=Object.hasOwn(t,e.id)?t[e.id]:void 0,i;if(r===void 0)e.required&&(i=`${e.label} is required.`);else switch(e.type){case`shortText`:case`longText`:typeof r==`string`?e.required&&!r.trim()&&(i=`${e.label} is required.`):i=`Enter text.`;break;case`richText`:i=n.n(r),!i&&e.required&&!f(r)&&(i=`${e.label} is required.`);break;case`number`:(typeof r!=`number`||!Number.isFinite(r))&&(i=`Enter a finite number.`);break;case`boolean`:typeof r!=`boolean`&&(i=`Choose a boolean value.`);break;case`dateTime`:u(r)||(i=`Enter a valid UTC date and time.`);break;case`asset`:case`reference`:{let t=e.type===`asset`?`assetId`:`entryId`;(e.multiple?!Array.isArray(r)||!r.every(e=>d(e,t)):!d(r,t))?i=e.multiple?`Choose a list of ${e.type===`asset`?`assets`:`entries`}.`:`Choose an ${e.type===`asset`?`asset`:`entry`}.`:e.required&&Array.isArray(r)&&r.length===0&&(i=`${e.label} is required.`);break}}return i?[[e.id,i]]:[]}))}function m(e,t){let n=e.type===`asset`?`assetId`:`entryId`;return(Array.isArray(t)?t:t===void 0?[]:[t]).flatMap(e=>{if(!e||typeof e!=`object`||!(n in e))return[];let t=e[n];return typeof t==`string`?[t]:[]})}function h({model:c,value:l,defaultValue:d={},onValueChange:f,pickerItems:h={},errors:g={},isDisabled:_=!1,showAllErrors:v=!1,className:y,...b}){let[x,S]=(0,o.useState)(d),[C,w]=(0,o.useState)(()=>new Set),T=l??x,E=p(c,T),D=(e,t)=>{if(_)return;let n={...T};t===void 0?delete n[e]:n[e]=t,l===void 0&&S(n),w(t=>new Set(t).add(e)),f?.(n)};return(0,s.jsx)(`div`,{className:[`ds-entry-editor`,y].filter(Boolean).join(` `),...b,children:c.fields.map(o=>{let c=Object.hasOwn(T,o.id)?T[o.id]:void 0,l=o.type===`asset`||o.type===`reference`,d=o.type===`dateTime`?`${o.label} (UTC)`:o.label,f=g[o.id]??(v||C.has(o.id)?E[o.id]:void 0);return(0,s.jsx)(e.Field,{label:d,hint:o.hint,error:f,isRequired:o.required,isGroup:l,children:({required:e,...l})=>{switch(o.type){case`shortText`:case`longText`:{let n=o.type===`shortText`?t.TextField:t.TextArea;return(0,s.jsx)(n,{...l,required:e,disabled:_,placeholder:o.placeholder,value:typeof c==`string`?c:``,onChange:e=>D(o.id,e.target.value)})}case`number`:return(0,s.jsx)(i.NumberField,{...l,required:e,isDisabled:_,placeholder:o.placeholder,step:`any`,hasSteppers:!1,value:typeof c==`number`&&Number.isFinite(c)?c:``,onValueChange:e=>{(e===``||Number.isFinite(e))&&D(o.id,e===``?void 0:e)}});case`boolean`:return(0,s.jsx)(a.Switch,{...l,isDisabled:_,isSelected:c===!0,onSelectedChange:e=>D(o.id,e)});case`dateTime`:return(0,s.jsx)(t.TextField,{...l,required:e,disabled:_,type:`datetime-local`,step:`0.001`,value:u(c)?.slice(0,-1)??``,onChange:e=>D(o.id,u(`${e.target.value}Z`))});case`richText`:return(0,s.jsx)(n.t,{...l,label:o.label,isRequired:e,isDisabled:_,placeholder:o.placeholder,format:`json`,value:c===void 0?n.r:c,onChange:e=>D(o.id,e)});case`asset`:case`reference`:return(0,s.jsx)(`div`,{...l,role:`group`,children:(0,s.jsx)(r.EntryPicker,{items:h[o.id]??[],value:m(o,c),label:o.type===`asset`?`Assets`:`Entries`,addLabel:`Choose ${o.label}`,mode:o.multiple?`multiple`:`single`,isDisabled:_,onValueChange:e=>{if(o.type===`asset`){let t=e.map(e=>({assetId:e}));D(o.id,o.multiple?t:t[0])}else{let t=e.map(e=>({entryId:e}));D(o.id,o.multiple?t:t[0])}}})})}}},o.id)})})}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return h}});
2
+ //# sourceMappingURL=EntryEditor-C32lKKnV.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntryEditor-cjDk4vdH.cjs","names":["documentError","useState","Field","TextField","TextArea","NumberField","Switch","RichTextEditor","emptyDocument","EntryPicker","require_RichTextEditor","require_components_EntryPicker_index","require_components_Field_index","require_components_NumberField_index","require_components_Switch_index","require_components_TextField_index","react","react_jsx_runtime","fieldTypes","assertModel","ids","field","model","parseUtcDateTime","value","match","canonical","date","isLink","key","hasRichText","node","validateEntryValues","values","error","item","selectedIds","link","id","EntryEditor","rest","internal","setInternal","defaultValue","changed","setChanged","current","validation","commit","isDisabled","next","previous","onValueChange","className","stored","isPicker","label","errors","showAllErrors","accessibility","Control","event","required","pickerItems","links"],"sources":["../../src/components/EntryEditor/model.ts","../../src/components/EntryEditor/index.tsx"],"sourcesContent":["import type { JSONContent } from \"@tiptap/react\";\nimport { documentError } from \"../RichTextEditor/document\";\nimport type { RichTextDocument } from \"../RichTextEditor\";\n\ninterface ContentFieldBase {\n id: string;\n label: string;\n hint?: string;\n placeholder?: string;\n required?: boolean;\n}\n\nexport type ContentField = ContentFieldBase & (\n | { type: \"shortText\" | \"longText\" | \"richText\" | \"number\" | \"boolean\" | \"dateTime\"; multiple?: never }\n | { type: \"asset\" | \"reference\"; multiple?: boolean }\n);\n\nexport interface ContentModel {\n fields: readonly ContentField[];\n}\n\nexport interface AssetValue { assetId: string }\nexport interface ReferenceValue { entryId: string }\nexport type EntryFieldValue = string | number | boolean | RichTextDocument | AssetValue | ReferenceValue | AssetValue[] | ReferenceValue[];\nexport type EntryValues = Record<string, EntryFieldValue | undefined>;\nexport type EntryErrors = Partial<Record<string, string>>;\n\nconst fieldTypes = new Set([\"shortText\", \"longText\", \"richText\", \"number\", \"boolean\", \"dateTime\", \"asset\", \"reference\"]);\n\nfunction assertModel(model: ContentModel) {\n const ids = new Set<string>();\n for (const field of model.fields) {\n if (!field.id?.trim() || ids.has(field.id)) throw new Error(\"Content model field IDs must be nonempty and unique.\");\n if (!fieldTypes.has(field.type)) throw new Error(`Unsupported content field type: ${field.type}`);\n ids.add(field.id);\n }\n}\n\n/** Accept UTC ISO strings without rolling invalid dates into the next month. */\nexport function parseUtcDateTime(value: unknown): string | undefined {\n if (typeof value !== \"string\") return;\n const match = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})(?::(\\d{2})(?:\\.(\\d{1,3}))?)?(?:Z|\\+00:00)$/.exec(value);\n if (!match) return;\n const canonical = `${match[1]}:${match[2] ?? \"00\"}.${(match[3] ?? \"\").padEnd(3, \"0\")}Z`;\n const date = new Date(canonical);\n if (Number.isFinite(date.getTime()) && date.toISOString() === canonical) return canonical;\n}\n\nfunction isLink(value: unknown, key: \"assetId\" | \"entryId\"): boolean {\n return !!value && typeof value === \"object\" && key in value &&\n typeof (value as Record<string, unknown>)[key] === \"string\" &&\n ((value as Record<string, string>)[key]).trim().length > 0;\n}\n\nfunction hasRichText(node: JSONContent): boolean {\n return !!node.text?.trim() || node.type === \"horizontalRule\" || !!node.content?.some(hasRichText);\n}\n\n/** Validate data loaded from JSON as well as values emitted by EntryEditor. */\nexport function validateEntryValues(model: ContentModel, values: Record<string, unknown>): EntryErrors {\n assertModel(model);\n return Object.fromEntries(model.fields.flatMap((field): [string, string][] => {\n const value = Object.hasOwn(values, field.id) ? values[field.id] : undefined;\n let error: string | undefined;\n if (value === undefined) {\n if (field.required) error = `${field.label} is required.`;\n } else {\n switch (field.type) {\n case \"shortText\":\n case \"longText\":\n if (typeof value !== \"string\") error = \"Enter text.\";\n else if (field.required && !value.trim()) error = `${field.label} is required.`;\n break;\n case \"richText\":\n error = documentError(value);\n if (!error && field.required && !hasRichText(value as RichTextDocument)) error = `${field.label} is required.`;\n break;\n case \"number\":\n if (typeof value !== \"number\" || !Number.isFinite(value)) error = \"Enter a finite number.\";\n break;\n case \"boolean\":\n if (typeof value !== \"boolean\") error = \"Choose a boolean value.\";\n break;\n case \"dateTime\":\n if (!parseUtcDateTime(value)) error = \"Enter a valid UTC date and time.\";\n break;\n case \"asset\":\n case \"reference\": {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n if (field.multiple ? !Array.isArray(value) || !value.every((item) => isLink(item, key)) : !isLink(value, key)) {\n error = field.multiple\n ? `Choose a list of ${field.type === \"asset\" ? \"assets\" : \"entries\"}.`\n : `Choose an ${field.type === \"asset\" ? \"asset\" : \"entry\"}.`;\n } else if (field.required && Array.isArray(value) && value.length === 0) error = `${field.label} is required.`;\n break;\n }\n }\n }\n return error ? [[field.id, error]] : [];\n }));\n}\n","import { useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { EntryPicker, type EntryItem } from \"../EntryPicker\";\nimport { Field } from \"../Field\";\nimport { NumberField } from \"../NumberField\";\nimport { RichTextEditor, type RichTextDocument } from \"../RichTextEditor\";\nimport { emptyDocument } from \"../RichTextEditor/document\";\nimport { Switch } from \"../Switch\";\nimport { TextArea, TextField } from \"../TextField\";\nimport { parseUtcDateTime, validateEntryValues, type ContentField, type ContentModel, type EntryErrors, type EntryFieldValue, type EntryValues } from \"./model\";\nimport \"./style.css\";\n\nexport { validateEntryValues } from \"./model\";\nexport type { AssetValue, ContentField, ContentModel, EntryErrors, EntryFieldValue, EntryValues, ReferenceValue } from \"./model\";\n\nexport interface EntryEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\"> {\n model: ContentModel;\n value?: EntryValues;\n defaultValue?: EntryValues;\n onValueChange?: (values: EntryValues) => void;\n /** Available assets or entries, keyed by content field ID. */\n pickerItems?: Record<string, EntryItem[]>;\n /** App-provided messages take precedence over built-in validation. */\n errors?: EntryErrors;\n isDisabled?: boolean;\n /** Reveal validation for untouched fields, for example after a submit attempt. */\n showAllErrors?: boolean;\n}\n\nfunction selectedIds(field: ContentField, value: EntryFieldValue | undefined): string[] {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n const links = Array.isArray(value) ? value : value === undefined ? [] : [value];\n return links.flatMap((link) => {\n if (!link || typeof link !== \"object\" || !(key in link)) return [];\n const id = (link as unknown as Record<string, unknown>)[key];\n return typeof id === \"string\" ? [id] : [];\n });\n}\n\n/** An ordered set of content inputs; submission and persistence belong to the app. */\nexport function EntryEditor({\n model,\n value,\n defaultValue = {},\n onValueChange,\n pickerItems = {},\n errors = {},\n isDisabled = false,\n showAllErrors = false,\n className,\n ...rest\n}: EntryEditorProps) {\n const [internal, setInternal] = useState<EntryValues>(defaultValue);\n const [changed, setChanged] = useState<Set<string>>(() => new Set());\n const current = value ?? internal;\n const validation = validateEntryValues(model, current);\n\n const commit = (id: string, next: EntryFieldValue | undefined) => {\n if (isDisabled) return;\n const values = { ...current };\n if (next === undefined) delete values[id];\n else values[id] = next;\n if (value === undefined) setInternal(values);\n setChanged((previous) => new Set(previous).add(id));\n onValueChange?.(values);\n };\n\n return (\n <div className={[\"ds-entry-editor\", className].filter(Boolean).join(\" \")} {...rest}>\n {model.fields.map((field) => {\n const stored = Object.hasOwn(current, field.id) ? current[field.id] : undefined;\n const isPicker = field.type === \"asset\" || field.type === \"reference\";\n const label = field.type === \"dateTime\" ? `${field.label} (UTC)` : field.label;\n const error = errors[field.id] ?? (showAllErrors || changed.has(field.id) ? validation[field.id] : undefined);\n return (\n <Field key={field.id} label={label} hint={field.hint} error={error} isRequired={field.required} isGroup={isPicker}>\n {({ required, ...accessibility }): ReactNode => {\n switch (field.type) {\n case \"shortText\":\n case \"longText\": {\n const Control = field.type === \"shortText\" ? TextField : TextArea;\n return <Control {...accessibility} required={required} disabled={isDisabled} placeholder={field.placeholder}\n value={typeof stored === \"string\" ? stored : \"\"} onChange={(event) => commit(field.id, event.target.value)} />;\n }\n case \"number\":\n return <NumberField {...accessibility} required={required} isDisabled={isDisabled} placeholder={field.placeholder} step=\"any\"\n hasSteppers={false} value={typeof stored === \"number\" && Number.isFinite(stored) ? stored : \"\"}\n onValueChange={(next) => { if (next === \"\" || Number.isFinite(next)) commit(field.id, next === \"\" ? undefined : next); }} />;\n case \"boolean\":\n return <Switch {...accessibility} isDisabled={isDisabled} isSelected={stored === true}\n onSelectedChange={(next) => commit(field.id, next)} />;\n case \"dateTime\":\n return <TextField {...accessibility} required={required} disabled={isDisabled} type=\"datetime-local\" step=\"0.001\"\n value={parseUtcDateTime(stored)?.slice(0, -1) ?? \"\"}\n onChange={(event) => commit(field.id, parseUtcDateTime(`${event.target.value}Z`))} />;\n case \"richText\":\n return <RichTextEditor {...accessibility} label={field.label} isRequired={required} isDisabled={isDisabled}\n placeholder={field.placeholder} format=\"json\" value={stored === undefined ? emptyDocument : stored as RichTextDocument}\n onChange={(next) => commit(field.id, next)} />;\n case \"asset\":\n case \"reference\":\n return <div {...accessibility} role=\"group\">\n <EntryPicker items={pickerItems[field.id] ?? []} value={selectedIds(field, stored)}\n label={field.type === \"asset\" ? \"Assets\" : \"Entries\"} addLabel={`Choose ${field.label}`}\n mode={field.multiple ? \"multiple\" : \"single\"} isDisabled={isDisabled}\n onValueChange={(ids) => {\n if (field.type === \"asset\") {\n const links = ids.map((assetId) => ({ assetId }));\n commit(field.id, field.multiple ? links : links[0]);\n } else {\n const links = ids.map((entryId) => ({ entryId }));\n commit(field.id, field.multiple ? links : links[0]);\n }\n }} />\n </div>;\n }\n }}\n </Field>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EntryEditor-C32lKKnV.cjs","names":["documentError","useState","Field","TextField","TextArea","NumberField","Switch","RichTextEditor","emptyDocument","EntryPicker","require_components_Field_index","require_components_TextField_index","require_RichTextEditor","require_components_EntryPicker_index","require_components_NumberField_index","require_components_Switch_index","react","react_jsx_runtime","fieldTypes","assertModel","ids","field","model","parseUtcDateTime","value","match","canonical","date","isLink","key","hasRichText","node","validateEntryValues","values","error","item","selectedIds","link","id","EntryEditor","rest","internal","setInternal","defaultValue","changed","setChanged","current","validation","commit","isDisabled","next","previous","onValueChange","className","stored","isPicker","label","errors","showAllErrors","accessibility","Control","event","required","pickerItems","links"],"sources":["../../src/components/EntryEditor/model.ts","../../src/components/EntryEditor/index.tsx"],"sourcesContent":["import type { JSONContent } from \"@tiptap/react\";\nimport { documentError } from \"../RichTextEditor/document\";\nimport type { RichTextDocument } from \"../RichTextEditor\";\n\ninterface ContentFieldBase {\n id: string;\n label: string;\n hint?: string;\n placeholder?: string;\n required?: boolean;\n}\n\nexport type ContentField = ContentFieldBase & (\n | { type: \"shortText\" | \"longText\" | \"richText\" | \"number\" | \"boolean\" | \"dateTime\"; multiple?: never }\n | { type: \"asset\" | \"reference\"; multiple?: boolean }\n);\n\nexport interface ContentModel {\n fields: readonly ContentField[];\n}\n\nexport interface AssetValue { assetId: string }\nexport interface ReferenceValue { entryId: string }\nexport type EntryFieldValue = string | number | boolean | RichTextDocument | AssetValue | ReferenceValue | AssetValue[] | ReferenceValue[];\nexport type EntryValues = Record<string, EntryFieldValue | undefined>;\nexport type EntryErrors = Partial<Record<string, string>>;\n\nconst fieldTypes = new Set([\"shortText\", \"longText\", \"richText\", \"number\", \"boolean\", \"dateTime\", \"asset\", \"reference\"]);\n\nfunction assertModel(model: ContentModel) {\n const ids = new Set<string>();\n for (const field of model.fields) {\n if (!field.id?.trim() || ids.has(field.id)) throw new Error(\"Content model field IDs must be nonempty and unique.\");\n if (!fieldTypes.has(field.type)) throw new Error(`Unsupported content field type: ${field.type}`);\n ids.add(field.id);\n }\n}\n\n/** Accept UTC ISO strings without rolling invalid dates into the next month. */\nexport function parseUtcDateTime(value: unknown): string | undefined {\n if (typeof value !== \"string\") return;\n const match = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})(?::(\\d{2})(?:\\.(\\d{1,3}))?)?(?:Z|\\+00:00)$/.exec(value);\n if (!match) return;\n const canonical = `${match[1]}:${match[2] ?? \"00\"}.${(match[3] ?? \"\").padEnd(3, \"0\")}Z`;\n const date = new Date(canonical);\n if (Number.isFinite(date.getTime()) && date.toISOString() === canonical) return canonical;\n}\n\nfunction isLink(value: unknown, key: \"assetId\" | \"entryId\"): boolean {\n return !!value && typeof value === \"object\" && key in value &&\n typeof (value as Record<string, unknown>)[key] === \"string\" &&\n ((value as Record<string, string>)[key]).trim().length > 0;\n}\n\nfunction hasRichText(node: JSONContent): boolean {\n return !!node.text?.trim() || node.type === \"horizontalRule\" || !!node.content?.some(hasRichText);\n}\n\n/** Validate data loaded from JSON as well as values emitted by EntryEditor. */\nexport function validateEntryValues(model: ContentModel, values: Record<string, unknown>): EntryErrors {\n assertModel(model);\n return Object.fromEntries(model.fields.flatMap((field): [string, string][] => {\n const value = Object.hasOwn(values, field.id) ? values[field.id] : undefined;\n let error: string | undefined;\n if (value === undefined) {\n if (field.required) error = `${field.label} is required.`;\n } else {\n switch (field.type) {\n case \"shortText\":\n case \"longText\":\n if (typeof value !== \"string\") error = \"Enter text.\";\n else if (field.required && !value.trim()) error = `${field.label} is required.`;\n break;\n case \"richText\":\n error = documentError(value);\n if (!error && field.required && !hasRichText(value as RichTextDocument)) error = `${field.label} is required.`;\n break;\n case \"number\":\n if (typeof value !== \"number\" || !Number.isFinite(value)) error = \"Enter a finite number.\";\n break;\n case \"boolean\":\n if (typeof value !== \"boolean\") error = \"Choose a boolean value.\";\n break;\n case \"dateTime\":\n if (!parseUtcDateTime(value)) error = \"Enter a valid UTC date and time.\";\n break;\n case \"asset\":\n case \"reference\": {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n if (field.multiple ? !Array.isArray(value) || !value.every((item) => isLink(item, key)) : !isLink(value, key)) {\n error = field.multiple\n ? `Choose a list of ${field.type === \"asset\" ? \"assets\" : \"entries\"}.`\n : `Choose an ${field.type === \"asset\" ? \"asset\" : \"entry\"}.`;\n } else if (field.required && Array.isArray(value) && value.length === 0) error = `${field.label} is required.`;\n break;\n }\n }\n }\n return error ? [[field.id, error]] : [];\n }));\n}\n","import { useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { EntryPicker, type EntryItem } from \"../EntryPicker\";\nimport { Field } from \"../Field\";\nimport { NumberField } from \"../NumberField\";\nimport { RichTextEditor, type RichTextDocument } from \"../RichTextEditor\";\nimport { emptyDocument } from \"../RichTextEditor/document\";\nimport { Switch } from \"../Switch\";\nimport { TextArea, TextField } from \"../TextField\";\nimport { parseUtcDateTime, validateEntryValues, type ContentField, type ContentModel, type EntryErrors, type EntryFieldValue, type EntryValues } from \"./model\";\nimport \"./style.css\";\n\nexport { validateEntryValues } from \"./model\";\nexport type { AssetValue, ContentField, ContentModel, EntryErrors, EntryFieldValue, EntryValues, ReferenceValue } from \"./model\";\n\nexport interface EntryEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\"> {\n model: ContentModel;\n value?: EntryValues;\n defaultValue?: EntryValues;\n onValueChange?: (values: EntryValues) => void;\n /** Available assets or entries, keyed by content field ID. */\n pickerItems?: Record<string, EntryItem[]>;\n /** App-provided messages take precedence over built-in validation. */\n errors?: EntryErrors;\n isDisabled?: boolean;\n /** Reveal validation for untouched fields, for example after a submit attempt. */\n showAllErrors?: boolean;\n}\n\nfunction selectedIds(field: ContentField, value: EntryFieldValue | undefined): string[] {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n const links = Array.isArray(value) ? value : value === undefined ? [] : [value];\n return links.flatMap((link) => {\n if (!link || typeof link !== \"object\" || !(key in link)) return [];\n const id = (link as unknown as Record<string, unknown>)[key];\n return typeof id === \"string\" ? [id] : [];\n });\n}\n\n/** An ordered set of content inputs; submission and persistence belong to the app. */\nexport function EntryEditor({\n model,\n value,\n defaultValue = {},\n onValueChange,\n pickerItems = {},\n errors = {},\n isDisabled = false,\n showAllErrors = false,\n className,\n ...rest\n}: EntryEditorProps) {\n const [internal, setInternal] = useState<EntryValues>(defaultValue);\n const [changed, setChanged] = useState<Set<string>>(() => new Set());\n const current = value ?? internal;\n const validation = validateEntryValues(model, current);\n\n const commit = (id: string, next: EntryFieldValue | undefined) => {\n if (isDisabled) return;\n const values = { ...current };\n if (next === undefined) delete values[id];\n else values[id] = next;\n if (value === undefined) setInternal(values);\n setChanged((previous) => new Set(previous).add(id));\n onValueChange?.(values);\n };\n\n return (\n <div className={[\"ds-entry-editor\", className].filter(Boolean).join(\" \")} {...rest}>\n {model.fields.map((field) => {\n const stored = Object.hasOwn(current, field.id) ? current[field.id] : undefined;\n const isPicker = field.type === \"asset\" || field.type === \"reference\";\n const label = field.type === \"dateTime\" ? `${field.label} (UTC)` : field.label;\n const error = errors[field.id] ?? (showAllErrors || changed.has(field.id) ? validation[field.id] : undefined);\n return (\n <Field key={field.id} label={label} hint={field.hint} error={error} isRequired={field.required} isGroup={isPicker}>\n {({ required, ...accessibility }): ReactNode => {\n switch (field.type) {\n case \"shortText\":\n case \"longText\": {\n const Control = field.type === \"shortText\" ? TextField : TextArea;\n return <Control {...accessibility} required={required} disabled={isDisabled} placeholder={field.placeholder}\n value={typeof stored === \"string\" ? stored : \"\"} onChange={(event) => commit(field.id, event.target.value)} />;\n }\n case \"number\":\n return <NumberField {...accessibility} required={required} isDisabled={isDisabled} placeholder={field.placeholder} step=\"any\"\n hasSteppers={false} value={typeof stored === \"number\" && Number.isFinite(stored) ? stored : \"\"}\n onValueChange={(next) => { if (next === \"\" || Number.isFinite(next)) commit(field.id, next === \"\" ? undefined : next); }} />;\n case \"boolean\":\n return <Switch {...accessibility} isDisabled={isDisabled} isSelected={stored === true}\n onSelectedChange={(next) => commit(field.id, next)} />;\n case \"dateTime\":\n return <TextField {...accessibility} required={required} disabled={isDisabled} type=\"datetime-local\" step=\"0.001\"\n value={parseUtcDateTime(stored)?.slice(0, -1) ?? \"\"}\n onChange={(event) => commit(field.id, parseUtcDateTime(`${event.target.value}Z`))} />;\n case \"richText\":\n return <RichTextEditor {...accessibility} label={field.label} isRequired={required} isDisabled={isDisabled}\n placeholder={field.placeholder} format=\"json\" value={stored === undefined ? emptyDocument : stored as RichTextDocument}\n onChange={(next) => commit(field.id, next)} />;\n case \"asset\":\n case \"reference\":\n return <div {...accessibility} role=\"group\">\n <EntryPicker items={pickerItems[field.id] ?? []} value={selectedIds(field, stored)}\n label={field.type === \"asset\" ? \"Assets\" : \"Entries\"} addLabel={`Choose ${field.label}`}\n mode={field.multiple ? \"multiple\" : \"single\"} isDisabled={isDisabled}\n onValueChange={(ids) => {\n if (field.type === \"asset\") {\n const links = ids.map((assetId) => ({ assetId }));\n commit(field.id, field.multiple ? links : links[0]);\n } else {\n const links = ids.map((entryId) => ({ entryId }));\n commit(field.id, field.multiple ? links : links[0]);\n }\n }} />\n </div>;\n }\n }}\n </Field>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,181 @@
1
+ import { Field as e } from "../components/Field/index.js";
2
+ import { TextArea as t, TextField as n } from "../components/TextField/index.js";
3
+ import { n as r, r as i, t as a } from "./RichTextEditor-CSpjsL1z.js";
4
+ import { EntryPicker as o } from "../components/EntryPicker/index.js";
5
+ import { NumberField as s } from "../components/NumberField/index.js";
6
+ import { Switch as c } from "../components/Switch/index.js";
7
+ import { useState as l } from "react";
8
+ import { jsx as u } from "react/jsx-runtime";
9
+ //#region src/components/EntryEditor/model.ts
10
+ var d = /* @__PURE__ */ new Set([
11
+ "shortText",
12
+ "longText",
13
+ "richText",
14
+ "number",
15
+ "boolean",
16
+ "dateTime",
17
+ "asset",
18
+ "reference"
19
+ ]);
20
+ function f(e) {
21
+ let t = /* @__PURE__ */ new Set();
22
+ for (let n of e.fields) {
23
+ if (!n.id?.trim() || t.has(n.id)) throw Error("Content model field IDs must be nonempty and unique.");
24
+ if (!d.has(n.type)) throw Error(`Unsupported content field type: ${n.type}`);
25
+ t.add(n.id);
26
+ }
27
+ }
28
+ function p(e) {
29
+ if (typeof e != "string") return;
30
+ let t = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?(?:Z|\+00:00)$/.exec(e);
31
+ if (!t) return;
32
+ let n = `${t[1]}:${t[2] ?? "00"}.${(t[3] ?? "").padEnd(3, "0")}Z`, r = new Date(n);
33
+ if (Number.isFinite(r.getTime()) && r.toISOString() === n) return n;
34
+ }
35
+ function m(e, t) {
36
+ return !!e && typeof e == "object" && t in e && typeof e[t] == "string" && e[t].trim().length > 0;
37
+ }
38
+ function h(e) {
39
+ return !!e.text?.trim() || e.type === "horizontalRule" || !!e.content?.some(h);
40
+ }
41
+ function g(e, t) {
42
+ return f(e), Object.fromEntries(e.fields.flatMap((e) => {
43
+ let n = Object.hasOwn(t, e.id) ? t[e.id] : void 0, i;
44
+ if (n === void 0) e.required && (i = `${e.label} is required.`);
45
+ else switch (e.type) {
46
+ case "shortText":
47
+ case "longText":
48
+ typeof n == "string" ? e.required && !n.trim() && (i = `${e.label} is required.`) : i = "Enter text.";
49
+ break;
50
+ case "richText":
51
+ i = r(n), !i && e.required && !h(n) && (i = `${e.label} is required.`);
52
+ break;
53
+ case "number":
54
+ (typeof n != "number" || !Number.isFinite(n)) && (i = "Enter a finite number.");
55
+ break;
56
+ case "boolean":
57
+ typeof n != "boolean" && (i = "Choose a boolean value.");
58
+ break;
59
+ case "dateTime":
60
+ p(n) || (i = "Enter a valid UTC date and time.");
61
+ break;
62
+ case "asset":
63
+ case "reference": {
64
+ let t = e.type === "asset" ? "assetId" : "entryId";
65
+ (e.multiple ? !Array.isArray(n) || !n.every((e) => m(e, t)) : !m(n, t)) ? i = e.multiple ? `Choose a list of ${e.type === "asset" ? "assets" : "entries"}.` : `Choose an ${e.type === "asset" ? "asset" : "entry"}.` : e.required && Array.isArray(n) && n.length === 0 && (i = `${e.label} is required.`);
66
+ break;
67
+ }
68
+ }
69
+ return i ? [[e.id, i]] : [];
70
+ }));
71
+ }
72
+ //#endregion
73
+ //#region src/components/EntryEditor/index.tsx
74
+ function _(e, t) {
75
+ let n = e.type === "asset" ? "assetId" : "entryId";
76
+ return (Array.isArray(t) ? t : t === void 0 ? [] : [t]).flatMap((e) => {
77
+ if (!e || typeof e != "object" || !(n in e)) return [];
78
+ let t = e[n];
79
+ return typeof t == "string" ? [t] : [];
80
+ });
81
+ }
82
+ function v({ model: r, value: d, defaultValue: f = {}, onValueChange: m, pickerItems: h = {}, errors: v = {}, isDisabled: y = !1, showAllErrors: b = !1, className: x, ...S }) {
83
+ let [C, w] = l(f), [T, E] = l(() => /* @__PURE__ */ new Set()), D = d ?? C, O = g(r, D), k = (e, t) => {
84
+ if (y) return;
85
+ let n = { ...D };
86
+ t === void 0 ? delete n[e] : n[e] = t, d === void 0 && w(n), E((t) => new Set(t).add(e)), m?.(n);
87
+ };
88
+ return /* @__PURE__ */ u("div", {
89
+ className: ["ds-entry-editor", x].filter(Boolean).join(" "),
90
+ ...S,
91
+ children: r.fields.map((r) => {
92
+ let l = Object.hasOwn(D, r.id) ? D[r.id] : void 0, d = r.type === "asset" || r.type === "reference", f = r.type === "dateTime" ? `${r.label} (UTC)` : r.label, m = v[r.id] ?? (b || T.has(r.id) ? O[r.id] : void 0);
93
+ return /* @__PURE__ */ u(e, {
94
+ label: f,
95
+ hint: r.hint,
96
+ error: m,
97
+ isRequired: r.required,
98
+ isGroup: d,
99
+ children: ({ required: e, ...d }) => {
100
+ switch (r.type) {
101
+ case "shortText":
102
+ case "longText": {
103
+ let i = r.type === "shortText" ? n : t;
104
+ return /* @__PURE__ */ u(i, {
105
+ ...d,
106
+ required: e,
107
+ disabled: y,
108
+ placeholder: r.placeholder,
109
+ value: typeof l == "string" ? l : "",
110
+ onChange: (e) => k(r.id, e.target.value)
111
+ });
112
+ }
113
+ case "number": return /* @__PURE__ */ u(s, {
114
+ ...d,
115
+ required: e,
116
+ isDisabled: y,
117
+ placeholder: r.placeholder,
118
+ step: "any",
119
+ hasSteppers: !1,
120
+ value: typeof l == "number" && Number.isFinite(l) ? l : "",
121
+ onValueChange: (e) => {
122
+ (e === "" || Number.isFinite(e)) && k(r.id, e === "" ? void 0 : e);
123
+ }
124
+ });
125
+ case "boolean": return /* @__PURE__ */ u(c, {
126
+ ...d,
127
+ isDisabled: y,
128
+ isSelected: l === !0,
129
+ onSelectedChange: (e) => k(r.id, e)
130
+ });
131
+ case "dateTime": return /* @__PURE__ */ u(n, {
132
+ ...d,
133
+ required: e,
134
+ disabled: y,
135
+ type: "datetime-local",
136
+ step: "0.001",
137
+ value: p(l)?.slice(0, -1) ?? "",
138
+ onChange: (e) => k(r.id, p(`${e.target.value}Z`))
139
+ });
140
+ case "richText": return /* @__PURE__ */ u(a, {
141
+ ...d,
142
+ label: r.label,
143
+ isRequired: e,
144
+ isDisabled: y,
145
+ placeholder: r.placeholder,
146
+ format: "json",
147
+ value: l === void 0 ? i : l,
148
+ onChange: (e) => k(r.id, e)
149
+ });
150
+ case "asset":
151
+ case "reference": return /* @__PURE__ */ u("div", {
152
+ ...d,
153
+ role: "group",
154
+ children: /* @__PURE__ */ u(o, {
155
+ items: h[r.id] ?? [],
156
+ value: _(r, l),
157
+ label: r.type === "asset" ? "Assets" : "Entries",
158
+ addLabel: `Choose ${r.label}`,
159
+ mode: r.multiple ? "multiple" : "single",
160
+ isDisabled: y,
161
+ onValueChange: (e) => {
162
+ if (r.type === "asset") {
163
+ let t = e.map((e) => ({ assetId: e }));
164
+ k(r.id, r.multiple ? t : t[0]);
165
+ } else {
166
+ let t = e.map((e) => ({ entryId: e }));
167
+ k(r.id, r.multiple ? t : t[0]);
168
+ }
169
+ }
170
+ })
171
+ });
172
+ }
173
+ }
174
+ }, r.id);
175
+ })
176
+ });
177
+ }
178
+ //#endregion
179
+ export { g as n, v as t };
180
+
181
+ //# sourceMappingURL=EntryEditor-xVuiqtFo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntryEditor-BF808OZR.js","names":["documentError","emptyDocument","RichTextEditor","fieldTypes","assertModel","ids","field","model","parseUtcDateTime","value","match","canonical","date","isLink","key","hasRichText","node","validateEntryValues","values","error","item","selectedIds","link","id","EntryEditor","rest","internal","setInternal","useState","defaultValue","changed","setChanged","current","validation","commit","isDisabled","next","previous","onValueChange","className","stored","isPicker","label","errors","showAllErrors","Field","accessibility","Control","TextField","TextArea","event","NumberField","Switch","required","EntryPicker","pickerItems","links"],"sources":["../../src/components/EntryEditor/model.ts","../../src/components/EntryEditor/index.tsx"],"sourcesContent":["import type { JSONContent } from \"@tiptap/react\";\nimport { documentError } from \"../RichTextEditor/document\";\nimport type { RichTextDocument } from \"../RichTextEditor\";\n\ninterface ContentFieldBase {\n id: string;\n label: string;\n hint?: string;\n placeholder?: string;\n required?: boolean;\n}\n\nexport type ContentField = ContentFieldBase & (\n | { type: \"shortText\" | \"longText\" | \"richText\" | \"number\" | \"boolean\" | \"dateTime\"; multiple?: never }\n | { type: \"asset\" | \"reference\"; multiple?: boolean }\n);\n\nexport interface ContentModel {\n fields: readonly ContentField[];\n}\n\nexport interface AssetValue { assetId: string }\nexport interface ReferenceValue { entryId: string }\nexport type EntryFieldValue = string | number | boolean | RichTextDocument | AssetValue | ReferenceValue | AssetValue[] | ReferenceValue[];\nexport type EntryValues = Record<string, EntryFieldValue | undefined>;\nexport type EntryErrors = Partial<Record<string, string>>;\n\nconst fieldTypes = new Set([\"shortText\", \"longText\", \"richText\", \"number\", \"boolean\", \"dateTime\", \"asset\", \"reference\"]);\n\nfunction assertModel(model: ContentModel) {\n const ids = new Set<string>();\n for (const field of model.fields) {\n if (!field.id?.trim() || ids.has(field.id)) throw new Error(\"Content model field IDs must be nonempty and unique.\");\n if (!fieldTypes.has(field.type)) throw new Error(`Unsupported content field type: ${field.type}`);\n ids.add(field.id);\n }\n}\n\n/** Accept UTC ISO strings without rolling invalid dates into the next month. */\nexport function parseUtcDateTime(value: unknown): string | undefined {\n if (typeof value !== \"string\") return;\n const match = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})(?::(\\d{2})(?:\\.(\\d{1,3}))?)?(?:Z|\\+00:00)$/.exec(value);\n if (!match) return;\n const canonical = `${match[1]}:${match[2] ?? \"00\"}.${(match[3] ?? \"\").padEnd(3, \"0\")}Z`;\n const date = new Date(canonical);\n if (Number.isFinite(date.getTime()) && date.toISOString() === canonical) return canonical;\n}\n\nfunction isLink(value: unknown, key: \"assetId\" | \"entryId\"): boolean {\n return !!value && typeof value === \"object\" && key in value &&\n typeof (value as Record<string, unknown>)[key] === \"string\" &&\n ((value as Record<string, string>)[key]).trim().length > 0;\n}\n\nfunction hasRichText(node: JSONContent): boolean {\n return !!node.text?.trim() || node.type === \"horizontalRule\" || !!node.content?.some(hasRichText);\n}\n\n/** Validate data loaded from JSON as well as values emitted by EntryEditor. */\nexport function validateEntryValues(model: ContentModel, values: Record<string, unknown>): EntryErrors {\n assertModel(model);\n return Object.fromEntries(model.fields.flatMap((field): [string, string][] => {\n const value = Object.hasOwn(values, field.id) ? values[field.id] : undefined;\n let error: string | undefined;\n if (value === undefined) {\n if (field.required) error = `${field.label} is required.`;\n } else {\n switch (field.type) {\n case \"shortText\":\n case \"longText\":\n if (typeof value !== \"string\") error = \"Enter text.\";\n else if (field.required && !value.trim()) error = `${field.label} is required.`;\n break;\n case \"richText\":\n error = documentError(value);\n if (!error && field.required && !hasRichText(value as RichTextDocument)) error = `${field.label} is required.`;\n break;\n case \"number\":\n if (typeof value !== \"number\" || !Number.isFinite(value)) error = \"Enter a finite number.\";\n break;\n case \"boolean\":\n if (typeof value !== \"boolean\") error = \"Choose a boolean value.\";\n break;\n case \"dateTime\":\n if (!parseUtcDateTime(value)) error = \"Enter a valid UTC date and time.\";\n break;\n case \"asset\":\n case \"reference\": {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n if (field.multiple ? !Array.isArray(value) || !value.every((item) => isLink(item, key)) : !isLink(value, key)) {\n error = field.multiple\n ? `Choose a list of ${field.type === \"asset\" ? \"assets\" : \"entries\"}.`\n : `Choose an ${field.type === \"asset\" ? \"asset\" : \"entry\"}.`;\n } else if (field.required && Array.isArray(value) && value.length === 0) error = `${field.label} is required.`;\n break;\n }\n }\n }\n return error ? [[field.id, error]] : [];\n }));\n}\n","import { useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { EntryPicker, type EntryItem } from \"../EntryPicker\";\nimport { Field } from \"../Field\";\nimport { NumberField } from \"../NumberField\";\nimport { RichTextEditor, type RichTextDocument } from \"../RichTextEditor\";\nimport { emptyDocument } from \"../RichTextEditor/document\";\nimport { Switch } from \"../Switch\";\nimport { TextArea, TextField } from \"../TextField\";\nimport { parseUtcDateTime, validateEntryValues, type ContentField, type ContentModel, type EntryErrors, type EntryFieldValue, type EntryValues } from \"./model\";\nimport \"./style.css\";\n\nexport { validateEntryValues } from \"./model\";\nexport type { AssetValue, ContentField, ContentModel, EntryErrors, EntryFieldValue, EntryValues, ReferenceValue } from \"./model\";\n\nexport interface EntryEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\"> {\n model: ContentModel;\n value?: EntryValues;\n defaultValue?: EntryValues;\n onValueChange?: (values: EntryValues) => void;\n /** Available assets or entries, keyed by content field ID. */\n pickerItems?: Record<string, EntryItem[]>;\n /** App-provided messages take precedence over built-in validation. */\n errors?: EntryErrors;\n isDisabled?: boolean;\n /** Reveal validation for untouched fields, for example after a submit attempt. */\n showAllErrors?: boolean;\n}\n\nfunction selectedIds(field: ContentField, value: EntryFieldValue | undefined): string[] {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n const links = Array.isArray(value) ? value : value === undefined ? [] : [value];\n return links.flatMap((link) => {\n if (!link || typeof link !== \"object\" || !(key in link)) return [];\n const id = (link as unknown as Record<string, unknown>)[key];\n return typeof id === \"string\" ? [id] : [];\n });\n}\n\n/** An ordered set of content inputs; submission and persistence belong to the app. */\nexport function EntryEditor({\n model,\n value,\n defaultValue = {},\n onValueChange,\n pickerItems = {},\n errors = {},\n isDisabled = false,\n showAllErrors = false,\n className,\n ...rest\n}: EntryEditorProps) {\n const [internal, setInternal] = useState<EntryValues>(defaultValue);\n const [changed, setChanged] = useState<Set<string>>(() => new Set());\n const current = value ?? internal;\n const validation = validateEntryValues(model, current);\n\n const commit = (id: string, next: EntryFieldValue | undefined) => {\n if (isDisabled) return;\n const values = { ...current };\n if (next === undefined) delete values[id];\n else values[id] = next;\n if (value === undefined) setInternal(values);\n setChanged((previous) => new Set(previous).add(id));\n onValueChange?.(values);\n };\n\n return (\n <div className={[\"ds-entry-editor\", className].filter(Boolean).join(\" \")} {...rest}>\n {model.fields.map((field) => {\n const stored = Object.hasOwn(current, field.id) ? current[field.id] : undefined;\n const isPicker = field.type === \"asset\" || field.type === \"reference\";\n const label = field.type === \"dateTime\" ? `${field.label} (UTC)` : field.label;\n const error = errors[field.id] ?? (showAllErrors || changed.has(field.id) ? validation[field.id] : undefined);\n return (\n <Field key={field.id} label={label} hint={field.hint} error={error} isRequired={field.required} isGroup={isPicker}>\n {({ required, ...accessibility }): ReactNode => {\n switch (field.type) {\n case \"shortText\":\n case \"longText\": {\n const Control = field.type === \"shortText\" ? TextField : TextArea;\n return <Control {...accessibility} required={required} disabled={isDisabled} placeholder={field.placeholder}\n value={typeof stored === \"string\" ? stored : \"\"} onChange={(event) => commit(field.id, event.target.value)} />;\n }\n case \"number\":\n return <NumberField {...accessibility} required={required} isDisabled={isDisabled} placeholder={field.placeholder} step=\"any\"\n hasSteppers={false} value={typeof stored === \"number\" && Number.isFinite(stored) ? stored : \"\"}\n onValueChange={(next) => { if (next === \"\" || Number.isFinite(next)) commit(field.id, next === \"\" ? undefined : next); }} />;\n case \"boolean\":\n return <Switch {...accessibility} isDisabled={isDisabled} isSelected={stored === true}\n onSelectedChange={(next) => commit(field.id, next)} />;\n case \"dateTime\":\n return <TextField {...accessibility} required={required} disabled={isDisabled} type=\"datetime-local\" step=\"0.001\"\n value={parseUtcDateTime(stored)?.slice(0, -1) ?? \"\"}\n onChange={(event) => commit(field.id, parseUtcDateTime(`${event.target.value}Z`))} />;\n case \"richText\":\n return <RichTextEditor {...accessibility} label={field.label} isRequired={required} isDisabled={isDisabled}\n placeholder={field.placeholder} format=\"json\" value={stored === undefined ? emptyDocument : stored as RichTextDocument}\n onChange={(next) => commit(field.id, next)} />;\n case \"asset\":\n case \"reference\":\n return <div {...accessibility} role=\"group\">\n <EntryPicker items={pickerItems[field.id] ?? []} value={selectedIds(field, stored)}\n label={field.type === \"asset\" ? \"Assets\" : \"Entries\"} addLabel={`Choose ${field.label}`}\n mode={field.multiple ? \"multiple\" : \"single\"} isDisabled={isDisabled}\n onValueChange={(ids) => {\n if (field.type === \"asset\") {\n const links = ids.map((assetId) => ({ assetId }));\n commit(field.id, field.multiple ? links : links[0]);\n } else {\n const links = ids.map((entryId) => ({ entryId }));\n commit(field.id, field.multiple ? links : links[0]);\n }\n }} />\n </div>;\n }\n }}\n </Field>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EntryEditor-xVuiqtFo.js","names":["documentError","emptyDocument","RichTextEditor","fieldTypes","assertModel","ids","field","model","parseUtcDateTime","value","match","canonical","date","isLink","key","hasRichText","node","validateEntryValues","values","error","item","selectedIds","link","id","EntryEditor","rest","internal","setInternal","useState","defaultValue","changed","setChanged","current","validation","commit","isDisabled","next","previous","onValueChange","className","stored","isPicker","label","errors","showAllErrors","Field","accessibility","Control","TextField","TextArea","event","NumberField","Switch","required","EntryPicker","pickerItems","links"],"sources":["../../src/components/EntryEditor/model.ts","../../src/components/EntryEditor/index.tsx"],"sourcesContent":["import type { JSONContent } from \"@tiptap/react\";\nimport { documentError } from \"../RichTextEditor/document\";\nimport type { RichTextDocument } from \"../RichTextEditor\";\n\ninterface ContentFieldBase {\n id: string;\n label: string;\n hint?: string;\n placeholder?: string;\n required?: boolean;\n}\n\nexport type ContentField = ContentFieldBase & (\n | { type: \"shortText\" | \"longText\" | \"richText\" | \"number\" | \"boolean\" | \"dateTime\"; multiple?: never }\n | { type: \"asset\" | \"reference\"; multiple?: boolean }\n);\n\nexport interface ContentModel {\n fields: readonly ContentField[];\n}\n\nexport interface AssetValue { assetId: string }\nexport interface ReferenceValue { entryId: string }\nexport type EntryFieldValue = string | number | boolean | RichTextDocument | AssetValue | ReferenceValue | AssetValue[] | ReferenceValue[];\nexport type EntryValues = Record<string, EntryFieldValue | undefined>;\nexport type EntryErrors = Partial<Record<string, string>>;\n\nconst fieldTypes = new Set([\"shortText\", \"longText\", \"richText\", \"number\", \"boolean\", \"dateTime\", \"asset\", \"reference\"]);\n\nfunction assertModel(model: ContentModel) {\n const ids = new Set<string>();\n for (const field of model.fields) {\n if (!field.id?.trim() || ids.has(field.id)) throw new Error(\"Content model field IDs must be nonempty and unique.\");\n if (!fieldTypes.has(field.type)) throw new Error(`Unsupported content field type: ${field.type}`);\n ids.add(field.id);\n }\n}\n\n/** Accept UTC ISO strings without rolling invalid dates into the next month. */\nexport function parseUtcDateTime(value: unknown): string | undefined {\n if (typeof value !== \"string\") return;\n const match = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})(?::(\\d{2})(?:\\.(\\d{1,3}))?)?(?:Z|\\+00:00)$/.exec(value);\n if (!match) return;\n const canonical = `${match[1]}:${match[2] ?? \"00\"}.${(match[3] ?? \"\").padEnd(3, \"0\")}Z`;\n const date = new Date(canonical);\n if (Number.isFinite(date.getTime()) && date.toISOString() === canonical) return canonical;\n}\n\nfunction isLink(value: unknown, key: \"assetId\" | \"entryId\"): boolean {\n return !!value && typeof value === \"object\" && key in value &&\n typeof (value as Record<string, unknown>)[key] === \"string\" &&\n ((value as Record<string, string>)[key]).trim().length > 0;\n}\n\nfunction hasRichText(node: JSONContent): boolean {\n return !!node.text?.trim() || node.type === \"horizontalRule\" || !!node.content?.some(hasRichText);\n}\n\n/** Validate data loaded from JSON as well as values emitted by EntryEditor. */\nexport function validateEntryValues(model: ContentModel, values: Record<string, unknown>): EntryErrors {\n assertModel(model);\n return Object.fromEntries(model.fields.flatMap((field): [string, string][] => {\n const value = Object.hasOwn(values, field.id) ? values[field.id] : undefined;\n let error: string | undefined;\n if (value === undefined) {\n if (field.required) error = `${field.label} is required.`;\n } else {\n switch (field.type) {\n case \"shortText\":\n case \"longText\":\n if (typeof value !== \"string\") error = \"Enter text.\";\n else if (field.required && !value.trim()) error = `${field.label} is required.`;\n break;\n case \"richText\":\n error = documentError(value);\n if (!error && field.required && !hasRichText(value as RichTextDocument)) error = `${field.label} is required.`;\n break;\n case \"number\":\n if (typeof value !== \"number\" || !Number.isFinite(value)) error = \"Enter a finite number.\";\n break;\n case \"boolean\":\n if (typeof value !== \"boolean\") error = \"Choose a boolean value.\";\n break;\n case \"dateTime\":\n if (!parseUtcDateTime(value)) error = \"Enter a valid UTC date and time.\";\n break;\n case \"asset\":\n case \"reference\": {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n if (field.multiple ? !Array.isArray(value) || !value.every((item) => isLink(item, key)) : !isLink(value, key)) {\n error = field.multiple\n ? `Choose a list of ${field.type === \"asset\" ? \"assets\" : \"entries\"}.`\n : `Choose an ${field.type === \"asset\" ? \"asset\" : \"entry\"}.`;\n } else if (field.required && Array.isArray(value) && value.length === 0) error = `${field.label} is required.`;\n break;\n }\n }\n }\n return error ? [[field.id, error]] : [];\n }));\n}\n","import { useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { EntryPicker, type EntryItem } from \"../EntryPicker\";\nimport { Field } from \"../Field\";\nimport { NumberField } from \"../NumberField\";\nimport { RichTextEditor, type RichTextDocument } from \"../RichTextEditor\";\nimport { emptyDocument } from \"../RichTextEditor/document\";\nimport { Switch } from \"../Switch\";\nimport { TextArea, TextField } from \"../TextField\";\nimport { parseUtcDateTime, validateEntryValues, type ContentField, type ContentModel, type EntryErrors, type EntryFieldValue, type EntryValues } from \"./model\";\nimport \"./style.css\";\n\nexport { validateEntryValues } from \"./model\";\nexport type { AssetValue, ContentField, ContentModel, EntryErrors, EntryFieldValue, EntryValues, ReferenceValue } from \"./model\";\n\nexport interface EntryEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\"> {\n model: ContentModel;\n value?: EntryValues;\n defaultValue?: EntryValues;\n onValueChange?: (values: EntryValues) => void;\n /** Available assets or entries, keyed by content field ID. */\n pickerItems?: Record<string, EntryItem[]>;\n /** App-provided messages take precedence over built-in validation. */\n errors?: EntryErrors;\n isDisabled?: boolean;\n /** Reveal validation for untouched fields, for example after a submit attempt. */\n showAllErrors?: boolean;\n}\n\nfunction selectedIds(field: ContentField, value: EntryFieldValue | undefined): string[] {\n const key = field.type === \"asset\" ? \"assetId\" : \"entryId\";\n const links = Array.isArray(value) ? value : value === undefined ? [] : [value];\n return links.flatMap((link) => {\n if (!link || typeof link !== \"object\" || !(key in link)) return [];\n const id = (link as unknown as Record<string, unknown>)[key];\n return typeof id === \"string\" ? [id] : [];\n });\n}\n\n/** An ordered set of content inputs; submission and persistence belong to the app. */\nexport function EntryEditor({\n model,\n value,\n defaultValue = {},\n onValueChange,\n pickerItems = {},\n errors = {},\n isDisabled = false,\n showAllErrors = false,\n className,\n ...rest\n}: EntryEditorProps) {\n const [internal, setInternal] = useState<EntryValues>(defaultValue);\n const [changed, setChanged] = useState<Set<string>>(() => new Set());\n const current = value ?? internal;\n const validation = validateEntryValues(model, current);\n\n const commit = (id: string, next: EntryFieldValue | undefined) => {\n if (isDisabled) return;\n const values = { ...current };\n if (next === undefined) delete values[id];\n else values[id] = next;\n if (value === undefined) setInternal(values);\n setChanged((previous) => new Set(previous).add(id));\n onValueChange?.(values);\n };\n\n return (\n <div className={[\"ds-entry-editor\", className].filter(Boolean).join(\" \")} {...rest}>\n {model.fields.map((field) => {\n const stored = Object.hasOwn(current, field.id) ? current[field.id] : undefined;\n const isPicker = field.type === \"asset\" || field.type === \"reference\";\n const label = field.type === \"dateTime\" ? `${field.label} (UTC)` : field.label;\n const error = errors[field.id] ?? (showAllErrors || changed.has(field.id) ? validation[field.id] : undefined);\n return (\n <Field key={field.id} label={label} hint={field.hint} error={error} isRequired={field.required} isGroup={isPicker}>\n {({ required, ...accessibility }): ReactNode => {\n switch (field.type) {\n case \"shortText\":\n case \"longText\": {\n const Control = field.type === \"shortText\" ? TextField : TextArea;\n return <Control {...accessibility} required={required} disabled={isDisabled} placeholder={field.placeholder}\n value={typeof stored === \"string\" ? stored : \"\"} onChange={(event) => commit(field.id, event.target.value)} />;\n }\n case \"number\":\n return <NumberField {...accessibility} required={required} isDisabled={isDisabled} placeholder={field.placeholder} step=\"any\"\n hasSteppers={false} value={typeof stored === \"number\" && Number.isFinite(stored) ? stored : \"\"}\n onValueChange={(next) => { if (next === \"\" || Number.isFinite(next)) commit(field.id, next === \"\" ? undefined : next); }} />;\n case \"boolean\":\n return <Switch {...accessibility} isDisabled={isDisabled} isSelected={stored === true}\n onSelectedChange={(next) => commit(field.id, next)} />;\n case \"dateTime\":\n return <TextField {...accessibility} required={required} disabled={isDisabled} type=\"datetime-local\" step=\"0.001\"\n value={parseUtcDateTime(stored)?.slice(0, -1) ?? \"\"}\n onChange={(event) => commit(field.id, parseUtcDateTime(`${event.target.value}Z`))} />;\n case \"richText\":\n return <RichTextEditor {...accessibility} label={field.label} isRequired={required} isDisabled={isDisabled}\n placeholder={field.placeholder} format=\"json\" value={stored === undefined ? emptyDocument : stored as RichTextDocument}\n onChange={(next) => commit(field.id, next)} />;\n case \"asset\":\n case \"reference\":\n return <div {...accessibility} role=\"group\">\n <EntryPicker items={pickerItems[field.id] ?? []} value={selectedIds(field, stored)}\n label={field.type === \"asset\" ? \"Assets\" : \"Entries\"} addLabel={`Choose ${field.label}`}\n mode={field.multiple ? \"multiple\" : \"single\"} isDisabled={isDisabled}\n onValueChange={(ids) => {\n if (field.type === \"asset\") {\n const links = ids.map((assetId) => ({ assetId }));\n commit(field.id, field.multiple ? links : links[0]);\n } else {\n const links = ids.map((entryId) => ({ entryId }));\n commit(field.id, field.multiple ? links : links[0]);\n }\n }} />\n </div>;\n }\n }}\n </Field>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}