@elytracms/studio 0.0.14 → 0.0.16
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.
- package/convex/cms.ts +8 -31
- package/convex/content.ts +3 -44
- package/convex/delivery.ts +8 -42
- package/convex/functions.ts +17 -2
- package/convex/members.ts +2 -2
- package/convex/referenceMaintenance.ts +4 -73
- package/convex/references.ts +4 -2
- package/convex/schema.ts +17 -37
- package/convex/sync.ts +10 -42
- package/convex/triggers.ts +57 -0
- package/convex/webhooks.ts +3 -2
- package/dist/components/app-views/default-modules.d.ts +41 -0
- package/dist/components/app-views/default-modules.js +201 -0
- package/dist/components/app-views/default-modules.js.map +1 -0
- package/dist/components/app-views/module.d.ts +93 -0
- package/dist/components/app-views/module.js +75 -0
- package/dist/components/app-views/module.js.map +1 -0
- package/dist/components/app-views/sidebar.d.ts +95 -0
- package/dist/components/app-views/sidebar.js +153 -0
- package/dist/components/app-views/sidebar.js.map +1 -0
- package/dist/components/builder/builder-store.d.ts +92 -40
- package/dist/components/builder/builder-store.js +30 -126
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-breakpoints.d.ts +53 -0
- package/dist/components/builder/canvas-breakpoints.js +55 -0
- package/dist/components/builder/canvas-breakpoints.js.map +1 -0
- package/dist/components/builder/canvas.js +35 -61
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.js +3 -3
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.js +31 -40
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/insert-context.js +1 -1
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.js +3 -4
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/property-inspector.js +4 -64
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +23 -5
- package/dist/components/cms/bulk-actions.js +7 -3
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/bulk-bar.d.ts +22 -6
- package/dist/components/cms/bulk-bar.js +46 -9
- package/dist/components/cms/bulk-bar.js.map +1 -1
- package/dist/components/cms/cell.d.ts +0 -4
- package/dist/components/cms/cell.js +1 -5
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-index.d.ts +4 -2
- package/dist/components/cms/cms-index.js +6 -4
- package/dist/components/cms/cms-index.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +7 -6
- package/dist/components/cms/cms-store.js +55 -18
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +8 -4
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/collections-sidebar.d.ts +10 -1
- package/dist/components/cms/collections-sidebar.js +13 -2
- package/dist/components/cms/collections-sidebar.js.map +1 -1
- package/dist/components/cms/detail-editor.js +2 -2
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/doc-state.d.ts +0 -5
- package/dist/components/cms/doc-state.js +1 -7
- package/dist/components/cms/doc-state.js.map +1 -1
- package/dist/components/cms/editable-cell.js +1 -1
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.d.ts +11 -2
- package/dist/components/cms/field-control.js +22 -1
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.d.ts +71 -0
- package/dist/components/cms/field-renderer.js +67 -0
- package/dist/components/cms/field-renderer.js.map +1 -0
- package/dist/components/cms/filter-builder.js +6 -28
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.d.ts +35 -0
- package/dist/components/cms/scalar-value-editor.js +43 -0
- package/dist/components/cms/scalar-value-editor.js.map +1 -0
- package/dist/components/collab/collab-context.d.ts +6 -14
- package/dist/components/collab/collab-context.js +3 -12
- package/dist/components/collab/collab-context.js.map +1 -1
- package/dist/components/collab/collab-seed.d.ts +7 -19
- package/dist/components/collab/collab-seed.js +9 -31
- package/dist/components/collab/collab-seed.js.map +1 -1
- package/dist/components/components/registry-browser.js +6 -12
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/guards.d.ts +0 -9
- package/dist/components/guards.js +0 -12
- package/dist/components/guards.js.map +1 -1
- package/dist/components/project-session.js +8 -3
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/section-nav.d.ts +10 -10
- package/dist/components/section-nav.js +48 -25
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/section-state.d.ts +0 -2
- package/dist/components/section-state.js +0 -9
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/section-surface.d.ts +3 -7
- package/dist/components/section-surface.js +6 -14
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/sections.d.ts +0 -1
- package/dist/components/sections.js +0 -10
- package/dist/components/sections.js.map +1 -1
- package/dist/components/studio-project-screen.d.ts +3 -4
- package/dist/components/studio-project-screen.js +41 -160
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/components/studio-shell.d.ts +0 -4
- package/dist/components/studio-shell.js +14 -14
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +19 -1
- package/dist/lib/data/collection-content.js +16 -0
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/operations/graph-commands.d.ts +2 -6
- package/dist/lib/operations/graph-commands.js +1 -2
- package/dist/lib/operations/graph-commands.js.map +1 -1
- package/dist/lib/operations/index.d.ts +0 -2
- package/dist/lib/operations/index.js +0 -2
- package/dist/lib/operations/index.js.map +1 -1
- package/dist/lib/operations/session-hydration.d.ts +14 -24
- package/dist/lib/operations/session-hydration.js +15 -34
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +1 -3
- package/dist/lib/persistence/convex-adapter.js +1 -154
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.js +0 -2
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/references/reference-model.d.ts +0 -16
- package/dist/lib/references/reference-model.js +2 -54
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.js +3 -4
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/registry/index.d.ts +0 -16
- package/dist/lib/registry/index.js +0 -15
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/webhooks/tags.js +2 -16
- package/dist/lib/webhooks/tags.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +27 -1
- package/dist/lib/workspace/index.js +34 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/router/match.d.ts +22 -34
- package/dist/router/match.js +22 -40
- package/dist/router/match.js.map +1 -1
- package/package.json +3 -2
- package/convex/graphs.ts +0 -55
- package/convex/publishing.ts +0 -76
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-control.js","sourceRoot":"","sources":["../../../src/components/cms/field-control.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAQjD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC5F,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAChH,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC5F,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,EAAE,GAOH;IACC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,CACL,iBAAO,SAAS,EAAC,qCAAqC,aACpD,gBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,KAAK,KAAK,IAAI,EACvB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAC3C,SAAS,EAAC,2CAA2C,GACrD,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,GAC7B,IACD,CACT,CAAA;QACH,KAAK,QAAQ;YACX,OAAO,CACL,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GACrF,CACH,CAAA;QACH,KAAK,MAAM;YACT,OAAO,CACL,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAC1D,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,GACzF,CACH,CAAA;QACH,KAAK,QAAQ;YACX,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAkB,CAAC,CAAC,CAAC,EAAE,CAAA;gBAChE,OAAO,CACL,KAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,kBACd,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;wBACzB,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;wBACvC,OAAO,CACL,iBAAuB,SAAS,EAAC,mCAAmC,aAClE,gBACE,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,EAAE,EACX,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,GAAG,EAAE,CACb,QAAQ,CACN,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CACxE,EAEH,SAAS,EAAC,uBAAuB,GACjC,EACD,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,KAZb,GAAG,CAAC,KAAK,CAab,CACT,CAAA;oBACH,CAAC,CAAC,GACE,CACP,CAAA;YACH,CAAC;YACD,OAAO,CACL,MAAC,MAAM,IACL,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,aAEtD,iBAAQ,KAAK,EAAC,EAAE,uBAAW,EAC1B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAC1B,iBAAwB,KAAK,EAAE,GAAG,CAAC,KAAK,YACrC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,IADZ,GAAG,CAAC,KAAK,CAEb,CACV,CAAC,IACK,CACV,CAAA;QACH,KAAK,UAAU;YACb,OAAO,CACL,mBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,EACtD,SAAS,EAAC,+NAA+N,GACzO,CACH,CAAA;QACH,KAAK,UAAU;YACb,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAAA;QAC9F,KAAK,OAAO;YACV,OAAO,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAAA;QAC3F,KAAK,QAAQ;YACX,OAAO,KAAC,WAAW,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAI,CAAA;QACpG;YACE,OAAO,CACL,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,GACtD,CACH,CAAA;IACL,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzE,CAAC,CAAE,KAAiC;QACpC,CAAC,CAAC,EAAE,CAAA;AACR,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,EACtB,MAAM,EACN,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,GAOT;IACC,OAAO,CACL,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,MAAM,YAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAClB,6EAA6E;YAC7E,4EAA4E;YAC5E,uEAAuE;YACvE,IAAI,aAAa,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAA;YACtD,MAAM,KAAK,GAAG,GAAG,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAA;YACvC,OAAO,CACL,KAAC,KAAK,IAEJ,OAAO,EAAE,KAAK,EACd,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,WAAW,EAC3B,KAAK,EACH,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,yBAAO,UAAU,CAAC,GAAG,CAAC,GAAQ,EAC7B,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,kBAAkB,kBAAS,CAAC,CAAC,CAAC,IAAI,IAC1E,YAGR,KAAC,YAAY,IACX,EAAE,EAAE,KAAK,EACT,KAAK,EAAE,GAAG,EACV,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EACtB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;wBACjB,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,CAAA;wBAC5B,IAAI,IAAI,KAAK,SAAS;4BAAE,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;;4BAC3C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;wBAC7B,QAAQ,CAAC,OAAO,CAAC,CAAA;oBACnB,CAAC,GACD,IArBG,GAAG,CAAC,IAAI,CAsBP,CACT,CAAA;QACH,CAAC,CAAC,GACI,CACT,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAAC,EACnB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,EAAE,GAOH;IACC,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;QACjC,OAAO,CACL,KAAC,IAAI,IAAC,SAAS,EAAC,KAAK,YACnB,KAAC,cAAc,IACb,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EACtB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,EAAE,EACZ,QAAQ,EAAE,QAAQ,GAClB,GACG,CACR,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAmC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC9E,iFAAiF;IACjF,mFAAmF;IACnF,oFAAoF;IACpF,iFAAiF;IACjF,iFAAiF;IACjF,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IACrB,MAAM,OAAO,GAAG,MAAM,CAAW,EAAE,CAAC,CAAA;IACpC,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5C,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAA;IAC5B,MAAM,MAAM,GAAG,CAAC,SAAoC,EAAE,QAAkB,EAAE,EAAE;QAC1E,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAA;QAC1B,QAAQ,CAAC,SAAS,CAAC,CAAA;IACrB,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE;QACxC,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;QAC/B,MAAM,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,EAAE,CAAA;QAC7B,MAAM,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAC3C,IAAI,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACxC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC,CAAA;YAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAA;QAClC,CAAC;QACD,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;IAC7B,CAAC,CAAA;IACD,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE,CAC7B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACzE,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,MAAM,IAAI,GAA4B,EAAE,CAAA;QACxC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,MAAM;YAAE,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,OAAO,CAAA;QAC3F,MAAM,CAAC,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACpD,CAAC,CAAA;IACD,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,OAAgC,EAAE,EAAE;QAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;QAC/B,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAA;QACtB,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC,CAAA;IAED,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACX,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACpB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,yBAEtB,CACR,CAAC,CAAC,CAAC,CACF,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CACrB,MAAC,IAAI,IAAe,SAAS,EAAC,KAAK,aACjC,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,kBACxB,CAAC,GAAG,CAAC,IACF,EACP,KAAC,MAAM,KAAG,EACT,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,gBACE,SAAS,EACpB,QAAQ,EAAE,CAAC,KAAK,CAAC,EACjB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,YAE7B,KAAC,SAAS,IAAC,SAAS,EAAC,QAAQ,GAAG,GACzB,EACT,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,gBACE,WAAW,EACtB,QAAQ,EAAE,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EAChC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,YAE7B,KAAC,WAAW,IAAC,SAAS,EAAC,QAAQ,GAAG,GAC3B,EACT,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,gBACE,aAAa,EACxB,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,YAE1B,KAAC,MAAM,IAAC,SAAS,EAAC,QAAQ,GAAG,GACtB,IACL,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,EACN,KAAC,cAAc,IACb,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EACtB,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,GAC1C,KA3CO,IAAI,CAAC,CAAC,CAAC,CA4CX,CACR,CAAC,CACH,EACA,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,aAClD,KAAC,IAAI,IAAC,SAAS,EAAC,QAAQ,GAAG,iBACpB,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,EACrB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GAMT;IACC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,MAAM,GAAG,yBAAyB,EAAE,CAAA;IAC1C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAE9B,yEAAyE;IACzE,gFAAgF;IAChF,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,MAAM,UAAU,GAAG,OAAO,CAA0B,GAAG,EAAE;QACvD,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,UAAU,CAAC,EAAE;YAAE,OAAO,SAAS,CAAA;QACtE,IAAI,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,KAAK,KAAK,CAAC,IAAI;YAAE,OAAO,SAAS,CAAA;QACjF,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAA;QAClF,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACzB,OAAO,GAAG,CAAA;IACZ,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAA;IAE3C,SAAS,KAAK,CAAC,IAAmB;QAChC,QAAQ,CAAC,KAAK,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAA;IACxE,CAAC;IAED,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,mBACd,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CACpB,MAAC,KAAK,IAAwB,OAAO,EAAC,MAAM,aAC1C,KAAC,KAAK,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAC7C,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,iBACE,IAAI,EAAC,QAAQ,gBACF,QAAQ,EACnB,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,EACzD,SAAS,EAAC,6BAA6B,YAEvC,KAAC,CAAC,IAAC,SAAS,EAAC,QAAQ,GAAG,GACjB,CACV,CAAC,CAAC,CAAC,IAAI,KAXE,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,CAYpB,CACT,CAAC,EACD,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,aAC9D,KAAC,MAAM,KAAG,OAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IACxC,CACV,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,cAAc,IACb,gBAAgB,EAAE,KAAK,CAAC,MAAM,EAC9B,WAAW,EAAE,KAAK,CAAC,WAAW,EAC9B,KAAK,EAAE,IAAI,EACX,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,OAAO,EACrB,QAAQ,EAAE,KAAK,EACf,UAAU,EAAE,UAAU,GACtB,IACE,CACP,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,GAMT;IACC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,iBAAiB,GAAG,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,EAAE,IAAI,OAAO,CAAA;IACxF,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,KAAK,MAAM,CAAA;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACzC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAEpD,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,mBACd,IAAI,CAAC,CAAC,CAAC,CACN,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACf,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAClB,MAAC,KAAK,IAAqB,OAAO,EAAC,SAAS,aAC1C,KAAC,KAAK,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,GAAG,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EACpF,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,iBACE,IAAI,EAAC,QAAQ,gBACF,QAAQ,EACnB,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,EACrD,SAAS,EAAC,6BAA6B,YAEvC,KAAC,CAAC,IAAC,SAAS,EAAC,QAAQ,GAAG,GACjB,CACV,CAAC,CAAC,CAAC,IAAI,KAXE,GAAG,GAAG,IAAI,CAAC,EAAE,CAYjB,CACT,CAAC,CACH,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,0BAEtB,CACR,CACF,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CACX,MAAC,KAAK,IAAC,OAAO,EAAC,SAAS,aACtB,KAAC,KAAK,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,GAAG,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,IAClF,CACT,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,yBAEtB,CACR,EACD,KAAC,MAAM,KAAG,EACT,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACzC,MAAC,IAAI,IAAC,IAAI,EAAC,OAAO,yBAAU,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAQ,CAC5D,CAAC,CAAC,CAAC,IAAI,EACP,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,aAC9D,KAAC,MAAM,KAAG,OAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAC9C,CACV,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,WAAW,IACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,OAAO,EACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,GACjF,IACE,CACP,CAAA;AACH,CAAC","sourcesContent":["import { ChevronDown, ChevronUp, Image, Link2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { useMemo, useRef, useState } from 'react'\nimport type {\n AssetFieldDef,\n DocumentRef,\n FieldDef,\n ObjectFieldDef,\n RelationFieldDef,\n} from '@elytracms/core/cms-core'\nimport { descendantIds, isFieldHidden, parentRelationField } from '@elytracms/core/cms-core'\nimport { Badge, Button, Card, Code, Field, Input, Row, Select, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { applyAssetSelection, assetIdsOf, singleAssetIdOf } from '../assets/asset-selection'\nimport { fieldLabel, useCms } from './cms-store'\nimport { useOptionalDocumentEditor } from './document-editor-context'\nimport { toRefArray } from './cell'\nimport { RelationPicker } from './relation-picker'\nimport { AssetPicker } from './asset-picker'\n\nexport function FieldControl({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={value === true}\n disabled={readOnly}\n onChange={(e) => onChange(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {value === true ? 'true' : 'false'}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={typeof value === 'number' ? value : ''}\n onChange={(e) => onChange(e.target.value === '' ? undefined : Number(e.target.value))}\n />\n )\n case 'date':\n return (\n <Input\n id={id}\n type=\"date\"\n disabled={readOnly}\n value={typeof value === 'string' ? value.slice(0, 10) : ''}\n onChange={(e) => onChange(e.target.value ? `${e.target.value}T00:00:00.000Z` : undefined)}\n />\n )\n case 'select':\n if (field.multiple) {\n const selected = Array.isArray(value) ? (value as string[]) : []\n return (\n <Row gap=\"2\" wrap>\n {field.options.map((opt) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-sm\">\n <input\n type=\"checkbox\"\n checked={on}\n disabled={readOnly}\n onChange={() =>\n onChange(\n on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value],\n )\n }\n className=\"size-4 accent-primary\"\n />\n {opt.label ?? opt.value}\n </label>\n )\n })}\n </Row>\n )\n }\n return (\n <Select\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label ?? opt.value}\n </option>\n ))}\n </Select>\n )\n case 'richText':\n return (\n <textarea\n id={id}\n rows={4}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:opacity-50\"\n />\n )\n case 'relation':\n return <RelationField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'asset':\n return <AssetField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'object':\n return <ObjectField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n default:\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n}\n\n/**\n * The nested subfield form for one object item (EC-253). Reuses {@link FieldControl}\n * recursively, so any subfield type — including a nested object/repeater — works for\n * free. Writing a subfield merges it back into the item and bubbles the new item up.\n */\nfunction ObjectItemForm({\n fields,\n value,\n onChange,\n readOnly,\n idPrefix,\n}: {\n fields: readonly FieldDef[]\n value: Record<string, unknown>\n onChange: (value: Record<string, unknown>) => void\n readOnly: boolean\n idPrefix: string\n}) {\n return (\n <Stack gap=\"3\" className=\"mt-2\">\n {fields.map((sub) => {\n // EC-273: a conditionally-hidden subfield (its predicate keys off the item's\n // own sibling values) is omitted — e.g. a discriminated link's `href` shows\n // only when `linkType === 'href'`. Re-evaluated as the item is edited.\n if (isFieldHidden(sub, { values: value })) return null\n const subId = `${idPrefix}-${sub.name}`\n return (\n <Field\n key={sub.name}\n htmlFor={subId}\n hint={sub.form?.description}\n label={\n <Row gap=\"2\">\n <span>{fieldLabel(sub)}</span>\n {sub.validation?.required ? <span className=\"text-destructive\">*</span> : null}\n </Row>\n }\n >\n <FieldControl\n id={subId}\n field={sub}\n value={value[sub.name]}\n readOnly={readOnly}\n onChange={(next) => {\n const updated = { ...value }\n if (next === undefined) delete updated[sub.name]\n else updated[sub.name] = next\n onChange(updated)\n }}\n />\n </Field>\n )\n })}\n </Stack>\n )\n}\n\n/**\n * Object / repeater field control (EC-253). `cardinality: 'one'` renders a single\n * nested form in a card; `'many'` renders an add/remove/reorder list of item cards.\n * Editing happens entirely inside the shared field form — no canvas escape (unlike\n * `blocks`), so props and document fields share this control.\n */\nfunction ObjectField({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: ObjectFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n if (field.cardinality !== 'many') {\n return (\n <Card className=\"p-3\">\n <ObjectItemForm\n fields={field.fields}\n value={asRecord(value)}\n readOnly={readOnly}\n idPrefix={id}\n onChange={onChange}\n />\n </Card>\n )\n }\n\n const items = Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n // Stable React keys for the value-only items (they carry no intrinsic id). Index\n // keys would mis-associate a row's child/DOM state (an open relation/asset picker,\n // focus) when rows are reordered or removed; we keep a parallel id list in lockstep\n // with structural ops, reconciled by length when the array is replaced wholesale\n // (e.g. switching documents). Edits keep the keys — only structure changes them.\n const seq = useRef(0)\n const keysRef = useRef<number[]>([])\n if (keysRef.current.length !== items.length) {\n keysRef.current = items.map((_, i) => keysRef.current[i] ?? seq.current++)\n }\n const keys = keysRef.current\n const commit = (nextItems: Record<string, unknown>[], nextKeys: number[]) => {\n keysRef.current = nextKeys\n onChange(nextItems)\n }\n const move = (from: number, to: number) => {\n const nextItems = items.slice()\n const [movedItem] = nextItems.splice(from, 1)\n const nextKeys = keys.slice()\n const [movedKey] = nextKeys.splice(from, 1)\n if (movedItem && movedKey !== undefined) {\n nextItems.splice(to, 0, movedItem)\n nextKeys.splice(to, 0, movedKey)\n }\n commit(nextItems, nextKeys)\n }\n const removeAt = (i: number) =>\n commit(items.filter((_, j) => j !== i), keys.filter((_, j) => j !== i))\n const addItem = () => {\n const seed: Record<string, unknown> = {}\n for (const sub of field.fields) if (sub.default !== undefined) seed[sub.name] = sub.default\n commit([...items, seed], [...keys, seq.current++])\n }\n const setItem = (i: number, updated: Record<string, unknown>) => {\n const nextItems = items.slice()\n nextItems[i] = updated\n commit(nextItems, keys)\n }\n\n return (\n <Stack gap=\"2\">\n {items.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n No items\n </Text>\n ) : (\n items.map((item, i) => (\n <Card key={keys[i]} className=\"p-3\">\n <Row gap=\"2\">\n <Text size=\"sm\" tone=\"muted\">\n #{i + 1}\n </Text>\n <Spacer />\n {!readOnly ? (\n <Row gap=\"1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Move up\"\n disabled={i === 0}\n onClick={() => move(i, i - 1)}\n >\n <ChevronUp className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Move down\"\n disabled={i === items.length - 1}\n onClick={() => move(i, i + 1)}\n >\n <ChevronDown className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Remove item\"\n onClick={() => removeAt(i)}\n >\n <Trash2 className=\"size-3\" />\n </Button>\n </Row>\n ) : null}\n </Row>\n <ObjectItemForm\n fields={field.fields}\n value={asRecord(item)}\n readOnly={readOnly}\n idPrefix={`${id}-${i}`}\n onChange={(updated) => setItem(i, updated)}\n />\n </Card>\n ))\n )}\n {!readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={addItem}>\n <Plus className=\"size-3\" /> Add item\n </Button>\n ) : null}\n </Stack>\n )\n}\n\nfunction RelationField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: RelationFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const cms = useCms()\n const editor = useOptionalDocumentEditor()\n const [open, setOpen] = useState(false)\n const refs = toRefArray(value)\n\n // EC-218: for the editing document's `parent` self-relation, exclude the\n // document itself and its descendants from the picker — choosing one would make\n // the page its own ancestor (a cycle). Only applies to the parent field of a\n // hierarchical collection; ordinary relations (and other self-relations) are\n // unrestricted. validateDocument is the backstop where this context is absent.\n const excludeIds = useMemo<Set<string> | undefined>(() => {\n if (!editor || field.target !== editor.collection.id) return undefined\n if (parentRelationField(editor.collection)?.name !== field.name) return undefined\n const ids = descendantIds(editor.docRef.id, cms.documentsOf(editor.collection.id))\n ids.add(editor.docRef.id)\n return ids\n }, [editor, field.target, field.name, cms])\n\n function apply(next: DocumentRef[]) {\n onChange(field.cardinality === 'many' ? next : (next[0] ?? undefined))\n }\n\n return (\n <Row gap=\"2\" wrap>\n {refs.map((ref, i) => (\n <Badge key={`${ref.id}-${i}`} variant=\"info\">\n <Link2 className=\"size-3\" /> {cms.titleOf(ref)}\n {!readOnly ? (\n <button\n type=\"button\"\n aria-label=\"Remove\"\n onClick={() => apply(refs.filter((r) => r.id !== ref.id))}\n className=\"ml-1 hover:text-destructive\"\n >\n <X className=\"size-3\" />\n </button>\n ) : null}\n </Badge>\n ))}\n {!readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Pencil /> {refs.length > 0 ? 'Change' : 'Pick'}\n </Button>\n ) : null}\n <RelationPicker\n targetCollection={field.target}\n cardinality={field.cardinality}\n value={refs}\n open={open}\n onOpenChange={setOpen}\n onChange={apply}\n excludeIds={excludeIds}\n />\n </Row>\n )\n}\n\nfunction AssetField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: AssetFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const cms = useCms()\n const [open, setOpen] = useState(false)\n const assetCollectionId = cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset'\n const many = field.cardinality === 'many'\n const ids = many ? assetIdsOf(value) : []\n const single = !many ? singleAssetIdOf(value) : null\n\n return (\n <Row gap=\"2\" wrap>\n {many ? (\n ids.length > 0 ? (\n ids.map((aid, i) => (\n <Badge key={`${aid}-${i}`} variant=\"outline\">\n <Image className=\"size-3\" /> {cms.titleOf({ collection: assetCollectionId, id: aid })}\n {!readOnly ? (\n <button\n type=\"button\"\n aria-label=\"Remove\"\n onClick={() => onChange(ids.filter((x) => x !== aid))}\n className=\"ml-1 hover:text-destructive\"\n >\n <X className=\"size-3\" />\n </button>\n ) : null}\n </Badge>\n ))\n ) : (\n <Text size=\"sm\" tone=\"subtle\">\n No assets\n </Text>\n )\n ) : single ? (\n <Badge variant=\"outline\">\n <Image className=\"size-3\" /> {cms.titleOf({ collection: assetCollectionId, id: single })}\n </Badge>\n ) : (\n <Text size=\"sm\" tone=\"subtle\">\n No asset\n </Text>\n )}\n <Spacer />\n {field.accept && field.accept.length > 0 ? (\n <Code tone=\"muted\">accepts {field.accept.join(', ')}</Code>\n ) : null}\n {!readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Pencil /> {many ? 'Add' : single ? 'Change' : 'Pick'}\n </Button>\n ) : null}\n <AssetPicker\n value={single}\n open={open}\n onOpenChange={setOpen}\n onChange={(next) => onChange(applyAssetSelection(value, next, field.cardinality))}\n />\n </Row>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"field-control.js","sourceRoot":"","sources":["../../../src/components/cms/field-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ChevronDown, ChevronUp, Image, Link2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { useMemo, useRef, useState } from 'react'\nimport type {\n AssetFieldDef,\n DocumentRef,\n FieldDef,\n ObjectFieldDef,\n RelationFieldDef,\n} from '@elytracms/core/cms-core'\nimport { descendantIds, isFieldHidden, parentRelationField } from '@elytracms/core/cms-core'\nimport { Badge, Button, Card, Code, Field, Input, Row, Select, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { applyAssetSelection, assetIdsOf, singleAssetIdOf } from '../assets/asset-selection'\nimport { fieldLabel, useCms } from './cms-store'\nimport { useOptionalDocumentEditor } from './document-editor-context'\nimport { toRefArray } from './cell'\nimport { RelationPicker } from './relation-picker'\nimport { AssetPicker } from './asset-picker'\nimport {\n FieldRendererBoundary,\n FieldRendererIssue,\n useFieldRenderers,\n} from './field-renderer'\n\ninterface FieldControlProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\n\n/**\n * The document-editor field control. Resolves a project's custom renderer for the\n * field type (EC-238, customization seam EC-236) and falls back to the built-in\n * control: a registered renderer renders inside an error boundary (a throw → the\n * built-in + a visible issue); a rejected registration (unknown type / non-component)\n * shows the built-in with the issue above it; otherwise the built-in renders alone.\n */\nexport function FieldControl(props: FieldControlProps) {\n const renderers = useFieldRenderers()\n const builtIn = <BuiltInFieldControl {...props} />\n const Custom = renderers.get(props.field.type)\n if (Custom) {\n return (\n <FieldRendererBoundary\n fallback={builtIn}\n issue={<FieldRendererIssue message=\"Custom renderer crashed — showing the built-in control.\" />}\n >\n <Custom {...props} />\n </FieldRendererBoundary>\n )\n }\n const rejected = renderers.issues.find((issue) => issue.id === props.field.type)\n if (rejected) {\n return (\n <Stack gap=\"1\">\n <FieldRendererIssue message={rejected.message} />\n {builtIn}\n </Stack>\n )\n }\n return builtIn\n}\n\nfunction BuiltInFieldControl({ field, value, onChange, readOnly, id }: FieldControlProps) {\n switch (field.type) {\n case 'boolean':\n return (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={value === true}\n disabled={readOnly}\n onChange={(e) => onChange(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {value === true ? 'true' : 'false'}\n </Text>\n </label>\n )\n case 'number':\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={typeof value === 'number' ? value : ''}\n onChange={(e) => onChange(e.target.value === '' ? undefined : Number(e.target.value))}\n />\n )\n case 'date':\n return (\n <Input\n id={id}\n type=\"date\"\n disabled={readOnly}\n value={typeof value === 'string' ? value.slice(0, 10) : ''}\n onChange={(e) => onChange(e.target.value ? `${e.target.value}T00:00:00.000Z` : undefined)}\n />\n )\n case 'select':\n if (field.multiple) {\n const selected = Array.isArray(value) ? (value as string[]) : []\n return (\n <Row gap=\"2\" wrap>\n {field.options.map((opt) => {\n const on = selected.includes(opt.value)\n return (\n <label key={opt.value} className=\"flex items-center gap-1.5 text-sm\">\n <input\n type=\"checkbox\"\n checked={on}\n disabled={readOnly}\n onChange={() =>\n onChange(\n on ? selected.filter((v) => v !== opt.value) : [...selected, opt.value],\n )\n }\n className=\"size-4 accent-primary\"\n />\n {opt.label ?? opt.value}\n </label>\n )\n })}\n </Row>\n )\n }\n return (\n <Select\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n >\n <option value=\"\">—</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label ?? opt.value}\n </option>\n ))}\n </Select>\n )\n case 'richText':\n return (\n <textarea\n id={id}\n rows={4}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:opacity-50\"\n />\n )\n case 'relation':\n return <RelationField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'asset':\n return <AssetField field={field} value={value} onChange={onChange} readOnly={readOnly} />\n case 'object':\n return <ObjectField field={field} value={value} onChange={onChange} readOnly={readOnly} id={id} />\n default:\n return (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n />\n )\n }\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n}\n\n/**\n * The nested subfield form for one object item (EC-253). Reuses {@link FieldControl}\n * recursively, so any subfield type — including a nested object/repeater — works for\n * free. Writing a subfield merges it back into the item and bubbles the new item up.\n */\nfunction ObjectItemForm({\n fields,\n value,\n onChange,\n readOnly,\n idPrefix,\n}: {\n fields: readonly FieldDef[]\n value: Record<string, unknown>\n onChange: (value: Record<string, unknown>) => void\n readOnly: boolean\n idPrefix: string\n}) {\n return (\n <Stack gap=\"3\" className=\"mt-2\">\n {fields.map((sub) => {\n // EC-273: a conditionally-hidden subfield (its predicate keys off the item's\n // own sibling values) is omitted — e.g. a discriminated link's `href` shows\n // only when `linkType === 'href'`. Re-evaluated as the item is edited.\n if (isFieldHidden(sub, { values: value })) return null\n const subId = `${idPrefix}-${sub.name}`\n return (\n <Field\n key={sub.name}\n htmlFor={subId}\n hint={sub.form?.description}\n label={\n <Row gap=\"2\">\n <span>{fieldLabel(sub)}</span>\n {sub.validation?.required ? <span className=\"text-destructive\">*</span> : null}\n </Row>\n }\n >\n <FieldControl\n id={subId}\n field={sub}\n value={value[sub.name]}\n readOnly={readOnly}\n onChange={(next) => {\n const updated = { ...value }\n if (next === undefined) delete updated[sub.name]\n else updated[sub.name] = next\n onChange(updated)\n }}\n />\n </Field>\n )\n })}\n </Stack>\n )\n}\n\n/**\n * Object / repeater field control (EC-253). `cardinality: 'one'` renders a single\n * nested form in a card; `'many'` renders an add/remove/reorder list of item cards.\n * Editing happens entirely inside the shared field form — no canvas escape (unlike\n * `blocks`), so props and document fields share this control.\n */\nfunction ObjectField({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: ObjectFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n if (field.cardinality !== 'many') {\n return (\n <Card className=\"p-3\">\n <ObjectItemForm\n fields={field.fields}\n value={asRecord(value)}\n readOnly={readOnly}\n idPrefix={id}\n onChange={onChange}\n />\n </Card>\n )\n }\n\n const items = Array.isArray(value) ? (value as Record<string, unknown>[]) : []\n // Stable React keys for the value-only items (they carry no intrinsic id). Index\n // keys would mis-associate a row's child/DOM state (an open relation/asset picker,\n // focus) when rows are reordered or removed; we keep a parallel id list in lockstep\n // with structural ops, reconciled by length when the array is replaced wholesale\n // (e.g. switching documents). Edits keep the keys — only structure changes them.\n const seq = useRef(0)\n const keysRef = useRef<number[]>([])\n if (keysRef.current.length !== items.length) {\n keysRef.current = items.map((_, i) => keysRef.current[i] ?? seq.current++)\n }\n const keys = keysRef.current\n const commit = (nextItems: Record<string, unknown>[], nextKeys: number[]) => {\n keysRef.current = nextKeys\n onChange(nextItems)\n }\n const move = (from: number, to: number) => {\n const nextItems = items.slice()\n const [movedItem] = nextItems.splice(from, 1)\n const nextKeys = keys.slice()\n const [movedKey] = nextKeys.splice(from, 1)\n if (movedItem && movedKey !== undefined) {\n nextItems.splice(to, 0, movedItem)\n nextKeys.splice(to, 0, movedKey)\n }\n commit(nextItems, nextKeys)\n }\n const removeAt = (i: number) =>\n commit(items.filter((_, j) => j !== i), keys.filter((_, j) => j !== i))\n const addItem = () => {\n const seed: Record<string, unknown> = {}\n for (const sub of field.fields) if (sub.default !== undefined) seed[sub.name] = sub.default\n commit([...items, seed], [...keys, seq.current++])\n }\n const setItem = (i: number, updated: Record<string, unknown>) => {\n const nextItems = items.slice()\n nextItems[i] = updated\n commit(nextItems, keys)\n }\n\n return (\n <Stack gap=\"2\">\n {items.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n No items\n </Text>\n ) : (\n items.map((item, i) => (\n <Card key={keys[i]} className=\"p-3\">\n <Row gap=\"2\">\n <Text size=\"sm\" tone=\"muted\">\n #{i + 1}\n </Text>\n <Spacer />\n {!readOnly ? (\n <Row gap=\"1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Move up\"\n disabled={i === 0}\n onClick={() => move(i, i - 1)}\n >\n <ChevronUp className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Move down\"\n disabled={i === items.length - 1}\n onClick={() => move(i, i + 1)}\n >\n <ChevronDown className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Remove item\"\n onClick={() => removeAt(i)}\n >\n <Trash2 className=\"size-3\" />\n </Button>\n </Row>\n ) : null}\n </Row>\n <ObjectItemForm\n fields={field.fields}\n value={asRecord(item)}\n readOnly={readOnly}\n idPrefix={`${id}-${i}`}\n onChange={(updated) => setItem(i, updated)}\n />\n </Card>\n ))\n )}\n {!readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={addItem}>\n <Plus className=\"size-3\" /> Add item\n </Button>\n ) : null}\n </Stack>\n )\n}\n\nfunction RelationField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: RelationFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const cms = useCms()\n const editor = useOptionalDocumentEditor()\n const [open, setOpen] = useState(false)\n const refs = toRefArray(value)\n\n // EC-218: for the editing document's `parent` self-relation, exclude the\n // document itself and its descendants from the picker — choosing one would make\n // the page its own ancestor (a cycle). Only applies to the parent field of a\n // hierarchical collection; ordinary relations (and other self-relations) are\n // unrestricted. validateDocument is the backstop where this context is absent.\n const excludeIds = useMemo<Set<string> | undefined>(() => {\n if (!editor || field.target !== editor.collection.id) return undefined\n if (parentRelationField(editor.collection)?.name !== field.name) return undefined\n const ids = descendantIds(editor.docRef.id, cms.documentsOf(editor.collection.id))\n ids.add(editor.docRef.id)\n return ids\n }, [editor, field.target, field.name, cms])\n\n function apply(next: DocumentRef[]) {\n onChange(field.cardinality === 'many' ? next : (next[0] ?? undefined))\n }\n\n return (\n <Row gap=\"2\" wrap>\n {refs.map((ref, i) => (\n <Badge key={`${ref.id}-${i}`} variant=\"info\">\n <Link2 className=\"size-3\" /> {cms.titleOf(ref)}\n {!readOnly ? (\n <button\n type=\"button\"\n aria-label=\"Remove\"\n onClick={() => apply(refs.filter((r) => r.id !== ref.id))}\n className=\"ml-1 hover:text-destructive\"\n >\n <X className=\"size-3\" />\n </button>\n ) : null}\n </Badge>\n ))}\n {!readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Pencil /> {refs.length > 0 ? 'Change' : 'Pick'}\n </Button>\n ) : null}\n <RelationPicker\n targetCollection={field.target}\n cardinality={field.cardinality}\n value={refs}\n open={open}\n onOpenChange={setOpen}\n onChange={apply}\n excludeIds={excludeIds}\n />\n </Row>\n )\n}\n\nfunction AssetField({\n field,\n value,\n onChange,\n readOnly,\n}: {\n field: AssetFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const cms = useCms()\n const [open, setOpen] = useState(false)\n const assetCollectionId = cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset'\n const many = field.cardinality === 'many'\n const ids = many ? assetIdsOf(value) : []\n const single = !many ? singleAssetIdOf(value) : null\n\n return (\n <Row gap=\"2\" wrap>\n {many ? (\n ids.length > 0 ? (\n ids.map((aid, i) => (\n <Badge key={`${aid}-${i}`} variant=\"outline\">\n <Image className=\"size-3\" /> {cms.titleOf({ collection: assetCollectionId, id: aid })}\n {!readOnly ? (\n <button\n type=\"button\"\n aria-label=\"Remove\"\n onClick={() => onChange(ids.filter((x) => x !== aid))}\n className=\"ml-1 hover:text-destructive\"\n >\n <X className=\"size-3\" />\n </button>\n ) : null}\n </Badge>\n ))\n ) : (\n <Text size=\"sm\" tone=\"subtle\">\n No assets\n </Text>\n )\n ) : single ? (\n <Badge variant=\"outline\">\n <Image className=\"size-3\" /> {cms.titleOf({ collection: assetCollectionId, id: single })}\n </Badge>\n ) : (\n <Text size=\"sm\" tone=\"subtle\">\n No asset\n </Text>\n )}\n <Spacer />\n {field.accept && field.accept.length > 0 ? (\n <Code tone=\"muted\">accepts {field.accept.join(', ')}</Code>\n ) : null}\n {!readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <Pencil /> {many ? 'Add' : single ? 'Change' : 'Pick'}\n </Button>\n ) : null}\n <AssetPicker\n value={single}\n open={open}\n onOpenChange={setOpen}\n onChange={(next) => onChange(applyAssetSelection(value, next, field.cardinality))}\n />\n </Row>\n )\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import type { FieldDef } from '@elytracms/core/cms-core';
|
|
4
|
+
import type { CustomizationIssue, FieldRendererEntry } from '@elytracms/core/studio-core';
|
|
5
|
+
/**
|
|
6
|
+
* EC-238 — custom field renderers, an instance of the EC-236 customization seam.
|
|
7
|
+
*
|
|
8
|
+
* A project registers a React component (via `config.fieldRenderers`) keyed by
|
|
9
|
+
* field type to REPLACE the built-in document-editor control. The contract is
|
|
10
|
+
* the exact {@link FieldControl} props seam, so a project component drops in
|
|
11
|
+
* unchanged and reuses internal helpers (relation/asset pickers, validation
|
|
12
|
+
* state) by calling the exported studio hooks (`useCms`,
|
|
13
|
+
* `useOptionalDocumentEditor`) itself. Resolution falls back to the built-in
|
|
14
|
+
* control, and every failure mode — unknown field type, duplicate registration,
|
|
15
|
+
* a non-component value, or a renderer that throws — surfaces a visible issue,
|
|
16
|
+
* never a silent drop or a crash.
|
|
17
|
+
*/
|
|
18
|
+
/** The props a custom field renderer receives — identical to the built-in control. */
|
|
19
|
+
export interface CustomFieldRendererProps {
|
|
20
|
+
field: FieldDef;
|
|
21
|
+
value: unknown;
|
|
22
|
+
onChange: (value: unknown) => void;
|
|
23
|
+
readOnly: boolean;
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
export type CustomFieldRenderer = (props: CustomFieldRendererProps) => ReactNode;
|
|
27
|
+
export interface ResolvedFieldRenderers {
|
|
28
|
+
/** The custom renderer for a field type, or `undefined` → use the built-in. */
|
|
29
|
+
get(type: string): CustomFieldRenderer | undefined;
|
|
30
|
+
/** Resolution issues (unknown type, duplicate id, non-component); located by `id` = type. */
|
|
31
|
+
issues: CustomizationIssue[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the config's custom field renderers through the shared customization
|
|
35
|
+
* core (EC-236): additive registry keyed by field type, rejecting an unknown
|
|
36
|
+
* type / non-component / duplicate with a visible issue and keeping the built-in.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveFieldRenderers(entries: readonly FieldRendererEntry[]): ResolvedFieldRenderers;
|
|
39
|
+
/** The active project's resolved field renderers (config is static per studio boot). */
|
|
40
|
+
export declare function useFieldRenderers(): ResolvedFieldRenderers;
|
|
41
|
+
/** A visible, inline field-renderer issue — the customization seam's "never silent". */
|
|
42
|
+
export declare function FieldRendererIssue({ message }: {
|
|
43
|
+
message: string;
|
|
44
|
+
}): import("react").JSX.Element;
|
|
45
|
+
/**
|
|
46
|
+
* Error boundary around a custom renderer: a renderer that throws falls back to
|
|
47
|
+
* the built-in control (passed as `fallback`) with a visible `issue` above it,
|
|
48
|
+
* so a broken third-party component degrades the one field, never the editor.
|
|
49
|
+
*/
|
|
50
|
+
export declare class FieldRendererBoundary extends Component<{
|
|
51
|
+
fallback: ReactNode;
|
|
52
|
+
issue: ReactNode;
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
}, {
|
|
55
|
+
failed: boolean;
|
|
56
|
+
}> {
|
|
57
|
+
state: {
|
|
58
|
+
failed: boolean;
|
|
59
|
+
};
|
|
60
|
+
static getDerivedStateFromError(): {
|
|
61
|
+
failed: boolean;
|
|
62
|
+
};
|
|
63
|
+
render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Example custom field renderer (EC-238 DoD): a colour control proving reuse of
|
|
67
|
+
* the studio form primitives and a save round-trip. A consuming repo registers it
|
|
68
|
+
* with `fieldRenderers: [{ type: 'plugin.color', component: colorFieldRenderer }]`
|
|
69
|
+
* (or `{ type: 'text', ... }` to override the built-in text control).
|
|
70
|
+
*/
|
|
71
|
+
export declare const colorFieldRenderer: CustomFieldRenderer;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Component, useMemo } from 'react';
|
|
3
|
+
import { AlertTriangle } from 'lucide-react';
|
|
4
|
+
import { fieldTypeSchema } from '@elytracms/core/cms-core';
|
|
5
|
+
import { resolveOverrides } from '@elytracms/core/studio-core';
|
|
6
|
+
import { resolveDeploymentFieldRenderers } from '@elytracms/studio/lib/workspace';
|
|
7
|
+
import { Input, Row, Text } from '@elytracms/studio/ui';
|
|
8
|
+
/** A field type is registrable if it is a built-in type or a dotted project type id. */
|
|
9
|
+
const DOTTED_TYPE = /^[a-z][a-z0-9]*\.[a-z][a-z0-9]*$/i;
|
|
10
|
+
function isKnownFieldType(type) {
|
|
11
|
+
return fieldTypeSchema.safeParse(type).success || DOTTED_TYPE.test(type);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the config's custom field renderers through the shared customization
|
|
15
|
+
* core (EC-236): additive registry keyed by field type, rejecting an unknown
|
|
16
|
+
* type / non-component / duplicate with a visible issue and keeping the built-in.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveFieldRenderers(entries) {
|
|
19
|
+
const resolved = resolveOverrides({
|
|
20
|
+
point: 'fieldRenderers',
|
|
21
|
+
defaults: [],
|
|
22
|
+
overrides: entries.map((entry) => ({ id: entry.type, value: entry.component })),
|
|
23
|
+
allowNew: true,
|
|
24
|
+
validate: (entry) => typeof entry.value !== 'function'
|
|
25
|
+
? 'the component is not a function'
|
|
26
|
+
: isKnownFieldType(entry.id)
|
|
27
|
+
? null
|
|
28
|
+
: `"${entry.id}" is not a known field type`,
|
|
29
|
+
});
|
|
30
|
+
return { get: (type) => resolved.get(type), issues: resolved.issues };
|
|
31
|
+
}
|
|
32
|
+
/** The active project's resolved field renderers (config is static per studio boot). */
|
|
33
|
+
export function useFieldRenderers() {
|
|
34
|
+
return useMemo(() => resolveFieldRenderers(resolveDeploymentFieldRenderers()), []);
|
|
35
|
+
}
|
|
36
|
+
/** A visible, inline field-renderer issue — the customization seam's "never silent". */
|
|
37
|
+
export function FieldRendererIssue({ message }) {
|
|
38
|
+
return (_jsxs(Row, { gap: "1", align: "center", className: "text-amber-600 dark:text-amber-400", title: message, children: [_jsx(AlertTriangle, { "aria-hidden": true, className: "size-3.5" }), _jsx(Text, { size: "xs", children: message })] }));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Error boundary around a custom renderer: a renderer that throws falls back to
|
|
42
|
+
* the built-in control (passed as `fallback`) with a visible `issue` above it,
|
|
43
|
+
* so a broken third-party component degrades the one field, never the editor.
|
|
44
|
+
*/
|
|
45
|
+
export class FieldRendererBoundary extends Component {
|
|
46
|
+
state = { failed: false };
|
|
47
|
+
static getDerivedStateFromError() {
|
|
48
|
+
return { failed: true };
|
|
49
|
+
}
|
|
50
|
+
render() {
|
|
51
|
+
if (this.state.failed) {
|
|
52
|
+
return (_jsxs(_Fragment, { children: [this.props.issue, this.props.fallback] }));
|
|
53
|
+
}
|
|
54
|
+
return this.props.children;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Example custom field renderer (EC-238 DoD): a colour control proving reuse of
|
|
59
|
+
* the studio form primitives and a save round-trip. A consuming repo registers it
|
|
60
|
+
* with `fieldRenderers: [{ type: 'plugin.color', component: colorFieldRenderer }]`
|
|
61
|
+
* (or `{ type: 'text', ... }` to override the built-in text control).
|
|
62
|
+
*/
|
|
63
|
+
export const colorFieldRenderer = ({ value, onChange, readOnly, id }) => {
|
|
64
|
+
const hex = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value) ? value : '#000000';
|
|
65
|
+
return (_jsxs(Row, { gap: "2", align: "center", children: [_jsx("input", { id: id, type: "color", disabled: readOnly, value: hex, onChange: (event) => onChange(event.target.value), "aria-label": "Colour", className: "h-8 w-12 cursor-pointer rounded-md border border-input bg-background disabled:opacity-50" }), _jsx(Input, { value: typeof value === 'string' ? value : '', disabled: readOnly, placeholder: "#rrggbb", onChange: (event) => onChange(event.target.value || undefined), className: "w-28 font-mono text-xs" })] }));
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=field-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-renderer.js","sourceRoot":"","sources":["../../../src/components/cms/field-renderer.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, useMemo } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle } from 'lucide-react'\nimport { fieldTypeSchema } from '@elytracms/core/cms-core'\nimport type { FieldDef } from '@elytracms/core/cms-core'\nimport { resolveOverrides } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue, FieldRendererEntry } from '@elytracms/core/studio-core'\nimport { resolveDeploymentFieldRenderers } from '@elytracms/studio/lib/workspace'\nimport { Input, Row, Text } from '@elytracms/studio/ui'\n\n/**\n * EC-238 — custom field renderers, an instance of the EC-236 customization seam.\n *\n * A project registers a React component (via `config.fieldRenderers`) keyed by\n * field type to REPLACE the built-in document-editor control. The contract is\n * the exact {@link FieldControl} props seam, so a project component drops in\n * unchanged and reuses internal helpers (relation/asset pickers, validation\n * state) by calling the exported studio hooks (`useCms`,\n * `useOptionalDocumentEditor`) itself. Resolution falls back to the built-in\n * control, and every failure mode — unknown field type, duplicate registration,\n * a non-component value, or a renderer that throws — surfaces a visible issue,\n * never a silent drop or a crash.\n */\n\n/** The props a custom field renderer receives — identical to the built-in control. */\nexport interface CustomFieldRendererProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\nexport type CustomFieldRenderer = (props: CustomFieldRendererProps) => ReactNode\n\n/** A field type is registrable if it is a built-in type or a dotted project type id. */\nconst DOTTED_TYPE = /^[a-z][a-z0-9]*\\.[a-z][a-z0-9]*$/i\nfunction isKnownFieldType(type: string): boolean {\n return fieldTypeSchema.safeParse(type).success || DOTTED_TYPE.test(type)\n}\n\nexport interface ResolvedFieldRenderers {\n /** The custom renderer for a field type, or `undefined` → use the built-in. */\n get(type: string): CustomFieldRenderer | undefined\n /** Resolution issues (unknown type, duplicate id, non-component); located by `id` = type. */\n issues: CustomizationIssue[]\n}\n\n/**\n * Resolve the config's custom field renderers through the shared customization\n * core (EC-236): additive registry keyed by field type, rejecting an unknown\n * type / non-component / duplicate with a visible issue and keeping the built-in.\n */\nexport function resolveFieldRenderers(\n entries: readonly FieldRendererEntry[],\n): ResolvedFieldRenderers {\n const resolved = resolveOverrides<CustomFieldRenderer>({\n point: 'fieldRenderers',\n defaults: [],\n overrides: entries.map((entry) => ({ id: entry.type, value: entry.component as CustomFieldRenderer })),\n allowNew: true,\n validate: (entry) =>\n typeof entry.value !== 'function'\n ? 'the component is not a function'\n : isKnownFieldType(entry.id)\n ? null\n : `\"${entry.id}\" is not a known field type`,\n })\n return { get: (type) => resolved.get(type), issues: resolved.issues }\n}\n\n/** The active project's resolved field renderers (config is static per studio boot). */\nexport function useFieldRenderers(): ResolvedFieldRenderers {\n return useMemo(() => resolveFieldRenderers(resolveDeploymentFieldRenderers()), [])\n}\n\n/** A visible, inline field-renderer issue — the customization seam's \"never silent\". */\nexport function FieldRendererIssue({ message }: { message: string }) {\n return (\n <Row gap=\"1\" align=\"center\" className=\"text-amber-600 dark:text-amber-400\" title={message}>\n <AlertTriangle aria-hidden className=\"size-3.5\" />\n <Text size=\"xs\">{message}</Text>\n </Row>\n )\n}\n\n/**\n * Error boundary around a custom renderer: a renderer that throws falls back to\n * the built-in control (passed as `fallback`) with a visible `issue` above it,\n * so a broken third-party component degrades the one field, never the editor.\n */\nexport class FieldRendererBoundary extends Component<\n { fallback: ReactNode; issue: ReactNode; children: ReactNode },\n { failed: boolean }\n> {\n override state = { failed: false }\n static getDerivedStateFromError() {\n return { failed: true }\n }\n override render() {\n if (this.state.failed) {\n return (\n <>\n {this.props.issue}\n {this.props.fallback}\n </>\n )\n }\n return this.props.children\n }\n}\n\n/**\n * Example custom field renderer (EC-238 DoD): a colour control proving reuse of\n * the studio form primitives and a save round-trip. A consuming repo registers it\n * with `fieldRenderers: [{ type: 'plugin.color', component: colorFieldRenderer }]`\n * (or `{ type: 'text', ... }` to override the built-in text control).\n */\nexport const colorFieldRenderer: CustomFieldRenderer = ({ value, onChange, readOnly, id }) => {\n const hex = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value) ? value : '#000000'\n return (\n <Row gap=\"2\" align=\"center\">\n <input\n id={id}\n type=\"color\"\n disabled={readOnly}\n value={hex}\n onChange={(event) => onChange(event.target.value)}\n aria-label=\"Colour\"\n className=\"h-8 w-12 cursor-pointer rounded-md border border-input bg-background disabled:opacity-50\"\n />\n <Input\n value={typeof value === 'string' ? value : ''}\n disabled={readOnly}\n placeholder=\"#rrggbb\"\n onChange={(event) => onChange(event.target.value || undefined)}\n className=\"w-28 font-mono text-xs\"\n />\n </Row>\n )\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
2
|
import { ListFilter, X } from 'lucide-react';
|
|
4
|
-
import { Button,
|
|
5
|
-
import { OPERATOR_LABELS, STATE_FIELD, defaultClause, filterableFieldOptions, operatorsFor,
|
|
3
|
+
import { Button, Row, Select, Stack, Text } from '@elytracms/studio/ui';
|
|
4
|
+
import { OPERATOR_LABELS, STATE_FIELD, defaultClause, filterableFieldOptions, operatorsFor, scalarListFromText, scalarListToText, validateClauses, } from '@elytracms/studio/lib/cms-views';
|
|
6
5
|
import { useCms } from './cms-store';
|
|
6
|
+
import { DraftValueInput, ScalarFieldValueEditor } from './scalar-value-editor';
|
|
7
7
|
/**
|
|
8
8
|
* The filter builder of the collection table (EC-169). Strictly the closed
|
|
9
9
|
* AD-3 vocabulary: it offers ONLY schema-`filterable` fields (plus the
|
|
@@ -50,35 +50,13 @@ function FilterClauseRow({ clause, index, options, issue, onChange, onRemove, })
|
|
|
50
50
|
function ClauseValueEditor({ option, clause, index, onChange, }) {
|
|
51
51
|
const testId = `filter-value-${index}`;
|
|
52
52
|
if (clause.op === 'in') {
|
|
53
|
-
return (_jsx(DraftValueInput, { initial: scalarListToText(clause.value), placeholder: "value, value, \u2026", testId: testId, onText: (raw) => onChange({ ...clause, value: scalarListFromText(option, raw) }) }));
|
|
53
|
+
return (_jsx(DraftValueInput, { ariaLabel: "Filter value", initial: scalarListToText(clause.value), placeholder: "value, value, \u2026", testId: testId, onText: (raw) => onChange({ ...clause, value: scalarListFromText(option, raw) }) }));
|
|
54
54
|
}
|
|
55
55
|
const scalar = Array.isArray(clause.value) ? (clause.value[0] ?? '') : clause.value;
|
|
56
56
|
if (clause.field === STATE_FIELD || option?.def === null) {
|
|
57
57
|
return (_jsxs(Select, { "aria-label": "Filter value", value: String(scalar), onChange: (event) => onChange({ ...clause, value: event.target.value }), className: "h-7 w-auto py-0 text-xs", "data-testid": testId, children: [_jsx("option", { value: "published", children: "published" }), _jsx("option", { value: "draft", children: "draft" })] }));
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return (_jsxs(Select, { "aria-label": "Filter value", value: scalar === true ? 'true' : 'false', onChange: (event) => onChange({ ...clause, value: event.target.value === 'true' }), className: "h-7 w-auto py-0 text-xs", "data-testid": testId, children: [_jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] }));
|
|
62
|
-
case 'select':
|
|
63
|
-
return (_jsxs(Select, { "aria-label": "Filter value", value: String(scalar), onChange: (event) => onChange({ ...clause, value: event.target.value }), className: "h-7 w-auto py-0 text-xs", "data-testid": testId, children: [_jsx("option", { value: "", children: "\u2014" }), option.def.options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label ?? opt.value }, opt.value)))] }));
|
|
64
|
-
case 'date':
|
|
65
|
-
return (_jsx(Input, { "aria-label": "Filter value", type: "date", value: typeof scalar === 'string' ? scalar.slice(0, 10) : '', onChange: (event) => onChange({ ...clause, value: scalarFromText(option, event.target.value) }), className: "h-7 w-36 text-xs", "data-testid": testId }));
|
|
66
|
-
case 'number':
|
|
67
|
-
return (_jsx(DraftValueInput, { type: "number", initial: scalar === '' ? '' : String(scalar), testId: testId, onText: (raw) => onChange({ ...clause, value: scalarFromText(option, raw) }) }));
|
|
68
|
-
default:
|
|
69
|
-
return (_jsx(DraftValueInput, { initial: String(scalar), testId: testId, onText: (raw) => onChange({ ...clause, value: scalarFromText(option, raw) }) }));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Text input with a local draft so typing is never re-normalized mid-keystroke
|
|
74
|
-
* (e.g. a trailing comma in an `in` list). Remounted (keyed) when the clause's
|
|
75
|
-
* field/operator changes.
|
|
76
|
-
*/
|
|
77
|
-
function DraftValueInput({ initial, type = 'text', placeholder, testId, onText, }) {
|
|
78
|
-
const [draft, setDraft] = useState(initial);
|
|
79
|
-
return (_jsx(Input, { "aria-label": "Filter value", type: type, value: draft, placeholder: placeholder ?? 'value', onChange: (event) => {
|
|
80
|
-
setDraft(event.target.value);
|
|
81
|
-
onText(event.target.value);
|
|
82
|
-
}, className: "h-7 w-36 text-xs", "data-testid": testId }));
|
|
59
|
+
// Every concrete scalar field shares the bulk set-field control (EC-247).
|
|
60
|
+
return (_jsx(ScalarFieldValueEditor, { option: option, value: scalar, ariaLabel: "Filter value", testId: testId, onChange: (value) => onChange({ ...clause, value }) }));
|
|
83
61
|
}
|
|
84
62
|
//# sourceMappingURL=filter-builder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-builder.js","sourceRoot":"","sources":["../../../src/components/cms/filter-builder.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport { ListFilter, X } from 'lucide-react'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport type { FilterOperator } from '@elytracms/core/content'\nimport { Button, Input, Row, Select, Stack, Text } from '@elytracms/studio/ui'\nimport {\n OPERATOR_LABELS,\n STATE_FIELD,\n defaultClause,\n filterableFieldOptions,\n operatorsFor,\n scalarFromText,\n scalarListFromText,\n scalarListToText,\n validateClauses,\n} from '@elytracms/studio/lib/cms-views'\nimport type { ClauseIssue, FilterClause, FilterableFieldOption } from '@elytracms/studio/lib/cms-views'\nimport { useCms } from './cms-store'\n\n/**\n * The filter builder of the collection table (EC-169). Strictly the closed\n * AD-3 vocabulary: it offers ONLY schema-`filterable` fields (plus the\n * built-in publication state), only `eq`/`in`/`lt`/`lte`/`gt`/`gte`, and\n * conditions combine with AND — there is no OR, no nesting, no free-text\n * query input. Clauses that no longer validate (e.g. a saved view whose field\n * lost its `filterable` flag) are flagged inline and excluded from matching.\n */\nexport function FilterBuilder({ collection }: { collection: CollectionDef }) {\n const cms = useCms()\n const collectionId = collection.id\n const state = cms.tableStateOf(collectionId)\n const options = filterableFieldOptions(collection)\n const issues = validateClauses(collection, state.filters)\n\n // Always update through the previous state — render snapshots go stale\n // across rapid successive events (sort clicks, column toggles).\n function updateFilters(update: (filters: FilterClause[]) => FilterClause[]) {\n cms.setTableState(collectionId, (prev) => ({ ...prev, filters: update(prev.filters) }))\n }\n\n return (\n <Row gap=\"2\" wrap align=\"start\" data-testid=\"filter-builder\">\n {state.filters.map((clause, index) => (\n <FilterClauseRow\n key={index}\n clause={clause}\n index={index}\n options={options}\n issue={issues.find((issue) => issue.index === index) ?? null}\n onChange={(next) => updateFilters((filters) => filters.map((c, i) => (i === index ? next : c)))}\n onRemove={() => updateFilters((filters) => filters.filter((_, i) => i !== index))}\n />\n ))}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n data-testid=\"add-filter\"\n onClick={() => updateFilters((filters) => [...filters, defaultClause(options[0]!)])}\n >\n <ListFilter /> Add filter\n </Button>\n {state.filters.length > 1 ? (\n <Text as=\"span\" size=\"xs\" tone=\"subtle\" className=\"self-center\">\n all conditions must match (AND)\n </Text>\n ) : null}\n </Row>\n )\n}\n\n/** One AND-condition: field · operator · value, plus its validation issue. */\nfunction FilterClauseRow({\n clause,\n index,\n options,\n issue,\n onChange,\n onRemove,\n}: {\n clause: FilterClause\n index: number\n options: FilterableFieldOption[]\n issue: ClauseIssue | null\n onChange: (clause: FilterClause) => void\n onRemove: () => void\n}) {\n const option = options.find((candidate) => candidate.field === clause.field)\n const operators = option ? operatorsFor(option) : [clause.op]\n\n function changeField(field: string) {\n const next = options.find((candidate) => candidate.field === field)\n if (next) onChange(defaultClause(next))\n }\n\n function changeOperator(op: FilterOperator) {\n if (op === 'in') {\n const list = Array.isArray(clause.value) ? clause.value : [clause.value]\n onChange({ ...clause, op, value: list.filter((value) => value !== '') })\n } else {\n const scalar = Array.isArray(clause.value) ? (clause.value[0] ?? '') : clause.value\n onChange({ ...clause, op, value: scalar })\n }\n }\n\n return (\n <Stack gap=\"1\" data-testid={`filter-clause-${index}`}>\n <Row\n gap=\"1\"\n className=\"rounded-md border border-border bg-muted/30 p-1\"\n aria-invalid={issue ? true : undefined}\n >\n <Select\n aria-label=\"Filter field\"\n value={option ? clause.field : ''}\n onChange={(event) => changeField(event.target.value)}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={`filter-field-${index}`}\n >\n {option ? null : (\n <option value=\"\" disabled>\n {clause.field}\n </option>\n )}\n {options.map((candidate) => (\n <option key={candidate.field} value={candidate.field}>\n {candidate.label}\n </option>\n ))}\n </Select>\n <Select\n aria-label=\"Filter operator\"\n value={clause.op}\n onChange={(event) => changeOperator(event.target.value as FilterOperator)}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={`filter-op-${index}`}\n >\n {operators.map((op) => (\n <option key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </option>\n ))}\n </Select>\n <ClauseValueEditor\n key={`${clause.field}:${clause.op}`}\n option={option}\n clause={clause}\n index={index}\n onChange={onChange}\n />\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Remove filter\"\n data-testid={`filter-remove-${index}`}\n onClick={onRemove}\n >\n <X />\n </Button>\n </Row>\n {issue ? (\n <Text size=\"xs\" className=\"text-destructive\" data-testid={`filter-issue-${index}`}>\n {issue.message}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/** The typed value input for a clause (closed per field type and operator). */\nfunction ClauseValueEditor({\n option,\n clause,\n index,\n onChange,\n}: {\n option: FilterableFieldOption | undefined\n clause: FilterClause\n index: number\n onChange: (clause: FilterClause) => void\n}) {\n const testId = `filter-value-${index}`\n\n if (clause.op === 'in') {\n return (\n <DraftValueInput\n initial={scalarListToText(clause.value)}\n placeholder=\"value, value, …\"\n testId={testId}\n onText={(raw) => onChange({ ...clause, value: scalarListFromText(option, raw) })}\n />\n )\n }\n\n const scalar = Array.isArray(clause.value) ? (clause.value[0] ?? '') : clause.value\n\n if (clause.field === STATE_FIELD || option?.def === null) {\n return (\n <Select\n aria-label=\"Filter value\"\n value={String(scalar)}\n onChange={(event) => onChange({ ...clause, value: event.target.value })}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"published\">published</option>\n <option value=\"draft\">draft</option>\n </Select>\n )\n }\n\n switch (option?.def?.type) {\n case 'boolean':\n return (\n <Select\n aria-label=\"Filter value\"\n value={scalar === true ? 'true' : 'false'}\n onChange={(event) => onChange({ ...clause, value: event.target.value === 'true' })}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </Select>\n )\n case 'select':\n return (\n <Select\n aria-label=\"Filter value\"\n value={String(scalar)}\n onChange={(event) => onChange({ ...clause, value: event.target.value })}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"\">—</option>\n {option.def.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label ?? opt.value}\n </option>\n ))}\n </Select>\n )\n case 'date':\n return (\n <Input\n aria-label=\"Filter value\"\n type=\"date\"\n value={typeof scalar === 'string' ? scalar.slice(0, 10) : ''}\n onChange={(event) => onChange({ ...clause, value: scalarFromText(option, event.target.value) })}\n className=\"h-7 w-36 text-xs\"\n data-testid={testId}\n />\n )\n case 'number':\n return (\n <DraftValueInput\n type=\"number\"\n initial={scalar === '' ? '' : String(scalar)}\n testId={testId}\n onText={(raw) => onChange({ ...clause, value: scalarFromText(option, raw) })}\n />\n )\n default:\n return (\n <DraftValueInput\n initial={String(scalar)}\n testId={testId}\n onText={(raw) => onChange({ ...clause, value: scalarFromText(option, raw) })}\n />\n )\n }\n}\n\n/**\n * Text input with a local draft so typing is never re-normalized mid-keystroke\n * (e.g. a trailing comma in an `in` list). Remounted (keyed) when the clause's\n * field/operator changes.\n */\nfunction DraftValueInput({\n initial,\n type = 'text',\n placeholder,\n testId,\n onText,\n}: {\n initial: string\n type?: 'text' | 'number'\n placeholder?: string\n testId: string\n onText: (raw: string) => void\n}) {\n const [draft, setDraft] = useState(initial)\n return (\n <Input\n aria-label=\"Filter value\"\n type={type}\n value={draft}\n placeholder={placeholder ?? 'value'}\n onChange={(event) => {\n setDraft(event.target.value)\n onText(event.target.value)\n }}\n className=\"h-7 w-36 text-xs\"\n data-testid={testId}\n />\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"filter-builder.js","sourceRoot":"","sources":["../../../src/components/cms/filter-builder.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ListFilter, X } from 'lucide-react'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport type { FilterOperator } from '@elytracms/core/content'\nimport { Button, Row, Select, Stack, Text } from '@elytracms/studio/ui'\nimport {\n OPERATOR_LABELS,\n STATE_FIELD,\n defaultClause,\n filterableFieldOptions,\n operatorsFor,\n scalarListFromText,\n scalarListToText,\n validateClauses,\n} from '@elytracms/studio/lib/cms-views'\nimport type { ClauseIssue, FilterClause, FilterableFieldOption } from '@elytracms/studio/lib/cms-views'\nimport { useCms } from './cms-store'\nimport { DraftValueInput, ScalarFieldValueEditor } from './scalar-value-editor'\n\n/**\n * The filter builder of the collection table (EC-169). Strictly the closed\n * AD-3 vocabulary: it offers ONLY schema-`filterable` fields (plus the\n * built-in publication state), only `eq`/`in`/`lt`/`lte`/`gt`/`gte`, and\n * conditions combine with AND — there is no OR, no nesting, no free-text\n * query input. Clauses that no longer validate (e.g. a saved view whose field\n * lost its `filterable` flag) are flagged inline and excluded from matching.\n */\nexport function FilterBuilder({ collection }: { collection: CollectionDef }) {\n const cms = useCms()\n const collectionId = collection.id\n const state = cms.tableStateOf(collectionId)\n const options = filterableFieldOptions(collection)\n const issues = validateClauses(collection, state.filters)\n\n // Always update through the previous state — render snapshots go stale\n // across rapid successive events (sort clicks, column toggles).\n function updateFilters(update: (filters: FilterClause[]) => FilterClause[]) {\n cms.setTableState(collectionId, (prev) => ({ ...prev, filters: update(prev.filters) }))\n }\n\n return (\n <Row gap=\"2\" wrap align=\"start\" data-testid=\"filter-builder\">\n {state.filters.map((clause, index) => (\n <FilterClauseRow\n key={index}\n clause={clause}\n index={index}\n options={options}\n issue={issues.find((issue) => issue.index === index) ?? null}\n onChange={(next) => updateFilters((filters) => filters.map((c, i) => (i === index ? next : c)))}\n onRemove={() => updateFilters((filters) => filters.filter((_, i) => i !== index))}\n />\n ))}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n data-testid=\"add-filter\"\n onClick={() => updateFilters((filters) => [...filters, defaultClause(options[0]!)])}\n >\n <ListFilter /> Add filter\n </Button>\n {state.filters.length > 1 ? (\n <Text as=\"span\" size=\"xs\" tone=\"subtle\" className=\"self-center\">\n all conditions must match (AND)\n </Text>\n ) : null}\n </Row>\n )\n}\n\n/** One AND-condition: field · operator · value, plus its validation issue. */\nfunction FilterClauseRow({\n clause,\n index,\n options,\n issue,\n onChange,\n onRemove,\n}: {\n clause: FilterClause\n index: number\n options: FilterableFieldOption[]\n issue: ClauseIssue | null\n onChange: (clause: FilterClause) => void\n onRemove: () => void\n}) {\n const option = options.find((candidate) => candidate.field === clause.field)\n const operators = option ? operatorsFor(option) : [clause.op]\n\n function changeField(field: string) {\n const next = options.find((candidate) => candidate.field === field)\n if (next) onChange(defaultClause(next))\n }\n\n function changeOperator(op: FilterOperator) {\n if (op === 'in') {\n const list = Array.isArray(clause.value) ? clause.value : [clause.value]\n onChange({ ...clause, op, value: list.filter((value) => value !== '') })\n } else {\n const scalar = Array.isArray(clause.value) ? (clause.value[0] ?? '') : clause.value\n onChange({ ...clause, op, value: scalar })\n }\n }\n\n return (\n <Stack gap=\"1\" data-testid={`filter-clause-${index}`}>\n <Row\n gap=\"1\"\n className=\"rounded-md border border-border bg-muted/30 p-1\"\n aria-invalid={issue ? true : undefined}\n >\n <Select\n aria-label=\"Filter field\"\n value={option ? clause.field : ''}\n onChange={(event) => changeField(event.target.value)}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={`filter-field-${index}`}\n >\n {option ? null : (\n <option value=\"\" disabled>\n {clause.field}\n </option>\n )}\n {options.map((candidate) => (\n <option key={candidate.field} value={candidate.field}>\n {candidate.label}\n </option>\n ))}\n </Select>\n <Select\n aria-label=\"Filter operator\"\n value={clause.op}\n onChange={(event) => changeOperator(event.target.value as FilterOperator)}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={`filter-op-${index}`}\n >\n {operators.map((op) => (\n <option key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </option>\n ))}\n </Select>\n <ClauseValueEditor\n key={`${clause.field}:${clause.op}`}\n option={option}\n clause={clause}\n index={index}\n onChange={onChange}\n />\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Remove filter\"\n data-testid={`filter-remove-${index}`}\n onClick={onRemove}\n >\n <X />\n </Button>\n </Row>\n {issue ? (\n <Text size=\"xs\" className=\"text-destructive\" data-testid={`filter-issue-${index}`}>\n {issue.message}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/** The typed value input for a clause (closed per field type and operator). */\nfunction ClauseValueEditor({\n option,\n clause,\n index,\n onChange,\n}: {\n option: FilterableFieldOption | undefined\n clause: FilterClause\n index: number\n onChange: (clause: FilterClause) => void\n}) {\n const testId = `filter-value-${index}`\n\n if (clause.op === 'in') {\n return (\n <DraftValueInput\n ariaLabel=\"Filter value\"\n initial={scalarListToText(clause.value)}\n placeholder=\"value, value, …\"\n testId={testId}\n onText={(raw) => onChange({ ...clause, value: scalarListFromText(option, raw) })}\n />\n )\n }\n\n const scalar = Array.isArray(clause.value) ? (clause.value[0] ?? '') : clause.value\n\n if (clause.field === STATE_FIELD || option?.def === null) {\n return (\n <Select\n aria-label=\"Filter value\"\n value={String(scalar)}\n onChange={(event) => onChange({ ...clause, value: event.target.value })}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"published\">published</option>\n <option value=\"draft\">draft</option>\n </Select>\n )\n }\n\n // Every concrete scalar field shares the bulk set-field control (EC-247).\n return (\n <ScalarFieldValueEditor\n option={option}\n value={scalar}\n ariaLabel=\"Filter value\"\n testId={testId}\n onChange={(value) => onChange({ ...clause, value })}\n />\n )\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FilterScalar } from '@elytracms/core/content';
|
|
2
|
+
import type { FilterableFieldOption } from '@elytracms/studio/lib/cms-views';
|
|
3
|
+
/**
|
|
4
|
+
* The closed single-scalar value control (EC-247), shared by the filter
|
|
5
|
+
* builder's `eq`/comparison clauses and the bulk set-field action so both
|
|
6
|
+
* surfaces enter a value the SAME way. It renders exactly the input the field's
|
|
7
|
+
* schema type warrants — a select for boolean/select, a date input, a
|
|
8
|
+
* number/text input otherwise. The `in` multi-value list and the
|
|
9
|
+
* publication-state pseudo field stay in the filter builder: they are
|
|
10
|
+
* filter-only and never a set-field target.
|
|
11
|
+
*
|
|
12
|
+
* It is deliberately *closed* — no field type invents a new control — keeping
|
|
13
|
+
* value entry consistent and agent-inspectable across the two surfaces.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ScalarFieldValueEditor({ option, value, testId, ariaLabel, onChange, }: {
|
|
16
|
+
/** The field whose value is being entered (its `def` drives the control). */
|
|
17
|
+
option: FilterableFieldOption | undefined;
|
|
18
|
+
value: FilterScalar;
|
|
19
|
+
testId: string;
|
|
20
|
+
ariaLabel?: string;
|
|
21
|
+
onChange: (value: FilterScalar) => void;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
23
|
+
/**
|
|
24
|
+
* Text input with a local draft so typing is never re-normalized mid-keystroke
|
|
25
|
+
* (e.g. a trailing comma in a filter `in` list). Remount (key it) when the
|
|
26
|
+
* field/operator changes so the draft resets to the new initial.
|
|
27
|
+
*/
|
|
28
|
+
export declare function DraftValueInput({ initial, type, placeholder, ariaLabel, testId, onText, }: {
|
|
29
|
+
initial: string;
|
|
30
|
+
type?: 'text' | 'number';
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
ariaLabel?: string;
|
|
33
|
+
testId: string;
|
|
34
|
+
onText: (raw: string) => void;
|
|
35
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Input, Select } from '@elytracms/studio/ui';
|
|
4
|
+
import { scalarFromText } from '@elytracms/studio/lib/cms-views';
|
|
5
|
+
/**
|
|
6
|
+
* The closed single-scalar value control (EC-247), shared by the filter
|
|
7
|
+
* builder's `eq`/comparison clauses and the bulk set-field action so both
|
|
8
|
+
* surfaces enter a value the SAME way. It renders exactly the input the field's
|
|
9
|
+
* schema type warrants — a select for boolean/select, a date input, a
|
|
10
|
+
* number/text input otherwise. The `in` multi-value list and the
|
|
11
|
+
* publication-state pseudo field stay in the filter builder: they are
|
|
12
|
+
* filter-only and never a set-field target.
|
|
13
|
+
*
|
|
14
|
+
* It is deliberately *closed* — no field type invents a new control — keeping
|
|
15
|
+
* value entry consistent and agent-inspectable across the two surfaces.
|
|
16
|
+
*/
|
|
17
|
+
export function ScalarFieldValueEditor({ option, value, testId, ariaLabel = 'Value', onChange, }) {
|
|
18
|
+
switch (option?.def?.type) {
|
|
19
|
+
case 'boolean':
|
|
20
|
+
return (_jsxs(Select, { "aria-label": ariaLabel, value: value === true ? 'true' : 'false', onChange: (event) => onChange(event.target.value === 'true'), className: "h-7 w-auto py-0 text-xs", "data-testid": testId, children: [_jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] }));
|
|
21
|
+
case 'select':
|
|
22
|
+
return (_jsxs(Select, { "aria-label": ariaLabel, value: String(value), onChange: (event) => onChange(event.target.value), className: "h-7 w-auto py-0 text-xs", "data-testid": testId, children: [_jsx("option", { value: "", children: "\u2014" }), option.def.options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label ?? opt.value }, opt.value)))] }));
|
|
23
|
+
case 'date':
|
|
24
|
+
return (_jsx(Input, { "aria-label": ariaLabel, type: "date", value: typeof value === 'string' ? value.slice(0, 10) : '', onChange: (event) => onChange(scalarFromText(option, event.target.value)), className: "h-7 w-36 text-xs", "data-testid": testId }));
|
|
25
|
+
case 'number':
|
|
26
|
+
return (_jsx(DraftValueInput, { type: "number", initial: value === '' ? '' : String(value), ariaLabel: ariaLabel, testId: testId, onText: (raw) => onChange(scalarFromText(option, raw)) }));
|
|
27
|
+
default:
|
|
28
|
+
return (_jsx(DraftValueInput, { initial: String(value), ariaLabel: ariaLabel, testId: testId, onText: (raw) => onChange(scalarFromText(option, raw)) }));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Text input with a local draft so typing is never re-normalized mid-keystroke
|
|
33
|
+
* (e.g. a trailing comma in a filter `in` list). Remount (key it) when the
|
|
34
|
+
* field/operator changes so the draft resets to the new initial.
|
|
35
|
+
*/
|
|
36
|
+
export function DraftValueInput({ initial, type = 'text', placeholder, ariaLabel = 'Value', testId, onText, }) {
|
|
37
|
+
const [draft, setDraft] = useState(initial);
|
|
38
|
+
return (_jsx(Input, { "aria-label": ariaLabel, type: type, value: draft, placeholder: placeholder ?? 'value', onChange: (event) => {
|
|
39
|
+
setDraft(event.target.value);
|
|
40
|
+
onText(event.target.value);
|
|
41
|
+
}, className: "h-7 w-36 text-xs", "data-testid": testId }));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=scalar-value-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scalar-value-editor.js","sourceRoot":"","sources":["../../../src/components/cms/scalar-value-editor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport type { FilterScalar } from '@elytracms/core/content'\nimport { Input, Select } from '@elytracms/studio/ui'\nimport { scalarFromText } from '@elytracms/studio/lib/cms-views'\nimport type { FilterableFieldOption } from '@elytracms/studio/lib/cms-views'\n\n/**\n * The closed single-scalar value control (EC-247), shared by the filter\n * builder's `eq`/comparison clauses and the bulk set-field action so both\n * surfaces enter a value the SAME way. It renders exactly the input the field's\n * schema type warrants — a select for boolean/select, a date input, a\n * number/text input otherwise. The `in` multi-value list and the\n * publication-state pseudo field stay in the filter builder: they are\n * filter-only and never a set-field target.\n *\n * It is deliberately *closed* — no field type invents a new control — keeping\n * value entry consistent and agent-inspectable across the two surfaces.\n */\nexport function ScalarFieldValueEditor({\n option,\n value,\n testId,\n ariaLabel = 'Value',\n onChange,\n}: {\n /** The field whose value is being entered (its `def` drives the control). */\n option: FilterableFieldOption | undefined\n value: FilterScalar\n testId: string\n ariaLabel?: string\n onChange: (value: FilterScalar) => void\n}) {\n switch (option?.def?.type) {\n case 'boolean':\n return (\n <Select\n aria-label={ariaLabel}\n value={value === true ? 'true' : 'false'}\n onChange={(event) => onChange(event.target.value === 'true')}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </Select>\n )\n case 'select':\n return (\n <Select\n aria-label={ariaLabel}\n value={String(value)}\n onChange={(event) => onChange(event.target.value)}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"\">—</option>\n {option.def.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label ?? opt.value}\n </option>\n ))}\n </Select>\n )\n case 'date':\n return (\n <Input\n aria-label={ariaLabel}\n type=\"date\"\n value={typeof value === 'string' ? value.slice(0, 10) : ''}\n onChange={(event) => onChange(scalarFromText(option, event.target.value))}\n className=\"h-7 w-36 text-xs\"\n data-testid={testId}\n />\n )\n case 'number':\n return (\n <DraftValueInput\n type=\"number\"\n initial={value === '' ? '' : String(value)}\n ariaLabel={ariaLabel}\n testId={testId}\n onText={(raw) => onChange(scalarFromText(option, raw))}\n />\n )\n default:\n return (\n <DraftValueInput\n initial={String(value)}\n ariaLabel={ariaLabel}\n testId={testId}\n onText={(raw) => onChange(scalarFromText(option, raw))}\n />\n )\n }\n}\n\n/**\n * Text input with a local draft so typing is never re-normalized mid-keystroke\n * (e.g. a trailing comma in a filter `in` list). Remount (key it) when the\n * field/operator changes so the draft resets to the new initial.\n */\nexport function DraftValueInput({\n initial,\n type = 'text',\n placeholder,\n ariaLabel = 'Value',\n testId,\n onText,\n}: {\n initial: string\n type?: 'text' | 'number'\n placeholder?: string\n ariaLabel?: string\n testId: string\n onText: (raw: string) => void\n}) {\n const [draft, setDraft] = useState(initial)\n return (\n <Input\n aria-label={ariaLabel}\n type={type}\n value={draft}\n placeholder={placeholder ?? 'value'}\n onChange={(event) => {\n setDraft(event.target.value)\n onText(event.target.value)\n }}\n className=\"h-7 w-36 text-xs\"\n data-testid={testId}\n />\n )\n}\n"]}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { Collaboration } from '@elytracms/core/collaboration';
|
|
3
3
|
import type { Actor, PreviewMode } from '@elytracms/core/collaboration';
|
|
4
|
-
import type { ProjectGraph } from '@elytracms/core/project-graph';
|
|
5
|
-
import type { HydratedPublish } from '@elytracms/studio/lib/operations';
|
|
6
4
|
/**
|
|
7
5
|
* Per-project collaboration session (Phase 10). Holds one {@link Collaboration} store seeded with
|
|
8
|
-
* an initial version +
|
|
9
|
-
* after an imperative store change. Non-persistent (session)
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* (EC-179): a deployment IS one environment, so there is no environment selector.
|
|
6
|
+
* an initial version + presence for the current user, and exposes a `mutate` helper that re-renders
|
|
7
|
+
* consumers after an imperative store change. Non-persistent (session): it is the builder canvas's
|
|
8
|
+
* session-scoped presence/perspective store. EC-287 retired the project graph and its publish path,
|
|
9
|
+
* so the seed no longer reflects a pinned graph revision; content publishing is per document
|
|
10
|
+
* (EC-224), surfaced by the CMS document Versions pane.
|
|
14
11
|
*/
|
|
15
12
|
export interface CollabStore {
|
|
16
13
|
collab: Collaboration;
|
|
@@ -22,13 +19,8 @@ export interface CollabStore {
|
|
|
22
19
|
/** Run an imperative store mutation, then re-render consumers. */
|
|
23
20
|
mutate: <T>(fn: () => T) => T;
|
|
24
21
|
}
|
|
25
|
-
declare
|
|
26
|
-
export declare function CollabProvider({ projectId, graph, publish, children, }: {
|
|
22
|
+
export declare function CollabProvider({ projectId, children, }: {
|
|
27
23
|
projectId: string;
|
|
28
|
-
graph: ProjectGraph;
|
|
29
|
-
/** The persisted publish to reflect in the session seed (Convex mode, EC-177 gap 1). */
|
|
30
|
-
publish?: HydratedPublish | null;
|
|
31
24
|
children: ReactNode;
|
|
32
25
|
}): import("react").JSX.Element;
|
|
33
26
|
export declare function useCollab(): CollabStore;
|
|
34
|
-
export { COLLABORATOR };
|