@elytracms/studio 0.0.87 → 0.0.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/components/ai/ai-chat-panel.js +8 -1
  2. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  3. package/dist/components/assets/assets-store.d.ts +11 -1
  4. package/dist/components/assets/assets-store.js +10 -0
  5. package/dist/components/assets/assets-store.js.map +1 -1
  6. package/dist/components/assets/focal-point.d.ts +0 -7
  7. package/dist/components/assets/focal-point.js +0 -10
  8. package/dist/components/assets/focal-point.js.map +1 -1
  9. package/dist/components/builder/canvas-render-context.d.ts +12 -1
  10. package/dist/components/builder/canvas-render-context.js +20 -16
  11. package/dist/components/builder/canvas-render-context.js.map +1 -1
  12. package/dist/components/cms/asset-field.d.ts +2 -1
  13. package/dist/components/cms/asset-field.js +27 -59
  14. package/dist/components/cms/asset-field.js.map +1 -1
  15. package/dist/components/cms/asset-picker-core.d.ts +77 -40
  16. package/dist/components/cms/asset-picker-core.js +73 -46
  17. package/dist/components/cms/asset-picker-core.js.map +1 -1
  18. package/dist/components/cms/asset-picker.d.ts +48 -19
  19. package/dist/components/cms/asset-picker.js +69 -30
  20. package/dist/components/cms/asset-picker.js.map +1 -1
  21. package/dist/components/cms/cell-edit.d.ts +3 -1
  22. package/dist/components/cms/cell-edit.js +2 -2
  23. package/dist/components/cms/cell-edit.js.map +1 -1
  24. package/dist/components/cms/cms-store.js +15 -23
  25. package/dist/components/cms/cms-store.js.map +1 -1
  26. package/dist/components/cms/editable-cell.js +3 -6
  27. package/dist/components/cms/editable-cell.js.map +1 -1
  28. package/dist/components/cms/gate-validation.d.ts +27 -2
  29. package/dist/components/cms/gate-validation.js +19 -1
  30. package/dist/components/cms/gate-validation.js.map +1 -1
  31. package/dist/components/cms/rich-text/editor.js +2 -2
  32. package/dist/components/cms/rich-text/editor.js.map +1 -1
  33. package/dist/components/cms/rich-text/insert.d.ts +7 -0
  34. package/dist/components/cms/rich-text/insert.js +7 -0
  35. package/dist/components/cms/rich-text/insert.js.map +1 -1
  36. package/dist/components/nav-issues.d.ts +23 -3
  37. package/dist/components/nav-issues.js +38 -2
  38. package/dist/components/nav-issues.js.map +1 -1
  39. package/dist/components/section-nav.js +26 -2
  40. package/dist/components/section-nav.js.map +1 -1
  41. package/dist/lib/ai/tools.d.ts +10 -4
  42. package/dist/lib/ai/tools.js +12 -4
  43. package/dist/lib/ai/tools.js.map +1 -1
  44. package/dist/lib/data/collection-content.d.ts +22 -1
  45. package/dist/lib/data/collection-content.js +15 -0
  46. package/dist/lib/data/collection-content.js.map +1 -1
  47. package/dist/lib/engine-version.d.ts +3 -3
  48. package/dist/lib/engine-version.js +3 -3
  49. package/dist/lib/engine-version.js.map +1 -1
  50. package/dist/lib/i18n/messages.de.js +5 -0
  51. package/dist/lib/i18n/messages.de.js.map +1 -1
  52. package/dist/lib/i18n/messages.en.d.ts +4 -0
  53. package/dist/lib/i18n/messages.en.js +6 -0
  54. package/dist/lib/i18n/messages.en.js.map +1 -1
  55. package/dist/lib/operations/session-hydration.d.ts +24 -26
  56. package/dist/lib/operations/session-hydration.js +15 -53
  57. package/dist/lib/operations/session-hydration.js.map +1 -1
  58. package/dist/lib/workspace/config-issues.d.ts +6 -0
  59. package/dist/lib/workspace/config-issues.js +8 -4
  60. package/dist/lib/workspace/config-issues.js.map +1 -1
  61. package/dist/lib/workspace/index.d.ts +1 -1
  62. package/dist/lib/workspace/index.js +1 -1
  63. package/dist/lib/workspace/index.js.map +1 -1
  64. package/dist/router/navigate-once.d.ts +19 -4
  65. package/dist/router/navigate-once.js +22 -5
  66. package/dist/router/navigate-once.js.map +1 -1
  67. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-render-context.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-render-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAUjE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EACL,6BAA6B,EAC7B,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAGvC;;;;;;;;;;;;;GAaG;AAEH,6DAA6D;AAC7D,MAAM,UAAU,UAAU,CAAC,IAAmB,EAAE,MAAmB,IAAI,GAAG,EAAE;IAC1E,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChB,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACvD,KAAK,MAAM,KAAK,IAAI,QAAQ;YAAE,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IACtD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAmB,EACnB,UAA8C,EAC9C,SAA+F,EAC/F,WAA8B;IAE9B,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IAC3C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,IAAI,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;QAAE,OAAO,IAAI,CAAA;IAC5C,OAAO,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5D,CAAC;AA2BD;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAA6B;IACrE,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE;YAC/B,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,OAAO,QAAQ,CAAA;YACnD,2EAA2E;YAC3E,0EAA0E;YAC1E,0DAA0D;YAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;YAC7C,yEAAyE;YACzE,6EAA6E;YAC7E,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;YAC9C,OAAO,CACL,iCAImB,IAAI,CAAC,EAAE,EACxB,SAAS,EAAC,aAAa,mBACR,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,UAAU,IAAI,SAAS,kBACzC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,SAAS,IAAI,SAAS,YAEpD,UAAU,CAAC,CAAC,CAAC,CACZ,KAAC,6BAA6B,IAAC,MAAM,EAAE,UAAU,GAAI,CACtD,CAAC,CAAC,CAAC,CACF,8BACG,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,KAAC,wBAAwB,IAAC,OAAO,EAAE,YAAY,GAAI,CAAC,CAAC,CAAC,IAAI,EAClF,QAAQ,IACR,CACJ,IAbI,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAclD,CACP,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAQtC;IACC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,IAAI,CAAA;IAEpF,yEAAyE;IACzE,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,0EAA0E;IAC1E,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,YAAY,GAAG,oBAAoB,CAAC,uBAAuB,EAAE,mBAAmB,CAAC,CAAA;IACvF,SAAS,CAAC,GAAG,EAAE,CAAC,6BAA6B,EAAE,EAAE,EAAE,CAAC,CAAA;IAEpD,4EAA4E;IAC5E,4EAA4E;IAC5E,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEjE,yEAAyE;IACzE,2EAA2E;IAC3E,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAA;IACvC,MAAM,YAAY,GAAiB,WAAW,CAC5C,CAAC,OAAO,EAAE,EAAE;QACV,MAAM,MAAM,GAAG,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAA;QAChE,IAAI,CAAC,MAAM,EAAE,GAAG;YAAE,OAAO,IAAI,CAAA;QAC7B,OAAO;YACL,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;YAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;YAC7B,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,IAAI;YACvB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;SACtC,CAAA;IACH,CAAC,EACD,CAAC,WAAW,CAAC,CACd,CAAA;IAED,yEAAyE;IACzE,iFAAiF;IACjF,oFAAoF;IACpF,gFAAgF;IAChF,oFAAoF;IACpF,2BAA2B;IAC3B,MAAM,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAA;IACpC,MAAM,YAAY,GAAG,MAAM,IAAI,SAAS,CAAA;IACxC,oFAAoF;IACpF,oFAAoF;IACpF,oFAAoF;IACpF,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;IAExB,6EAA6E;IAC7E,2EAA2E;IAC3E,gFAAgF;IAChF,8EAA8E;IAC9E,8EAA8E;IAC9E,mBAAmB;IACnB,MAAM,eAAe,GAAoB,WAAW,CAClD,CAAC,GAAG,EAAE,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,WAAW,CAAC;IAC9D,6EAA6E;IAC7E,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CAC1C,CAAA;IAED,iFAAiF;IACjF,mFAAmF;IACnF,gFAAgF;IAChF,6EAA6E;IAC7E,2BAA2B;IAC3B,MAAM,cAAc,GAAmB,WAAW,CAChD,CAAC,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,CAAC,EACjE,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CAC1C,CAAA;IAED,mFAAmF;IACnF,gFAAgF;IAChF,gFAAgF;IAChF,mFAAmF;IACnF,MAAM,WAAW,GAAgB,WAAW,CAC1C,CAAC,GAAG,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,WAAW,CAAC,EAC1D,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CAC1C,CAAA;IAED,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,IAAmB,EAAoC,EAAE,CACxD,qBAAqB,CACnB,IAAI,EACJ,KAAK,EAAE,UAAU,EACjB,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC,EACvD,WAAW,CACZ,EACH,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CACjD,CAAA;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,6EAA6E;IAC7E,kEAAkE;IAClE,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,IAAmB,EAAiB,EAAE;QACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC9D,IAAI,CAAC,QAAQ,EAAE,OAAO;YAAE,OAAO,IAAI,CAAA;QACnC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;QACpD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA,CAAC,yDAAyD;QACjF,MAAM,MAAM,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAA;QAChD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACpC,+EAA+E;QAC/E,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzE,CAAC,EACD,CAAC,MAAM,EAAE,YAAY,CAAC,CACvB,CAAA;IAED,MAAM,GAAG,GAAkB,OAAO,CAChC,GAAG,EAAE,CACH,yBAAyB,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,YAAY;QACZ,eAAe;QACf,cAAc;QACd,WAAW;QACX,WAAW;QACX,UAAU;QACV,SAAS;QACT,cAAc;QACd,eAAe;QACf,cAAc;KACf,CAAC,EACJ,CAAC,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,CAAC,CAC1J,CAAA;IAED,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAA;AAC7B,CAAC;AAED;;;;;;;;;GASG;AACH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,EAAE,OAAO,EAAuB;IACvE,OAAO,CACL,eACE,SAAS,EAAC,WAAW,iBACT,sBAAsB,EAClC,KAAK,EAAE;YACL,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,kCAAkC;YAC1C,YAAY,EAAE,CAAC;YACf,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,UAAU;YAClB,UAAU,EAAE,uBAAuB;YACnC,KAAK,EAAE,sBAAsB;YAC7B,IAAI,EAAE,+CAA+C;SACtD,aAED,gBAAM,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,wBAAK,SAAS,CAAC,oCAAoC,CAAC,IAAQ,EAAC,GAAG,EAChG,eAAM,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,YAAG,OAAO,GAAQ,IAC3C,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,EAAE,MAAM,EAAyC;IAC7F,MAAM,KAAK,GACT,MAAM,KAAK,aAAa;QACtB,CAAC,CAAC,SAAS,CAAC,yCAAyC,CAAC;QACtD,CAAC,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAA;IACtD,MAAM,IAAI,GACR,MAAM,KAAK,aAAa;QACtB,CAAC,CAAC,SAAS,CAAC,wCAAwC,CAAC;QACrD,CAAC,CAAC,SAAS,CAAC,oCAAoC,CAAC,CAAA;IACrD,OAAO,CACL,eACE,SAAS,EAAC,WAAW,EACrB,KAAK,EAAE;YACL,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,kCAAkC;YAC1C,YAAY,EAAE,CAAC;YACf,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,WAAW;YACnB,UAAU,EAAE,wBAAwB;YACpC,KAAK,EAAE,qBAAqB;YAC5B,IAAI,EAAE,+CAA+C;YACrD,SAAS,EAAE,QAAQ;SACpB,aAED,eAAK,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,wBAAK,KAAK,IAAO,EAChD,cAAK,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,YAAG,IAAI,GAAO,IACnE,CACP,CAAA;AACH,CAAC","sourcesContent":["import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react'\nimport { listingQueryOf } from '@elytracms/core/runtime-renderer'\nimport type {\n RenderContext,\n ResolveAsset,\n ResolveLink,\n ResolveListing,\n ResolveRelation,\n} from '@elytracms/core/runtime-renderer'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { contentQueryErrorMessage } from '@elytracms/studio/lib/persona/labels'\nimport {\n acquireContentSessionConsumer,\n contentSessionEpoch,\n resolveCanvasLink,\n resolveCanvasListing,\n resolveCanvasRelation,\n subscribeContentSession,\n validateCanvasListingQuery,\n} from '@elytracms/studio/lib/data'\nimport type { CanvasPerspective } from '@elytracms/studio/lib/data'\nimport { useStudioLocale } from '@elytracms/studio/lib/locale'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { useReuse } from './reuse-context'\nimport { reuseEmbedRef } from './reuse'\nimport type { ReuseCapability } from './reuse'\n\n/**\n * The canvas's `RenderContext` (EC-330, extracted from `BuilderCanvas`): the\n * injected resolvers that make the iframe preview match delivery — assets\n * (EC-195), relations (EC-254), listings (EC-255), rich-text links (EC-291) —\n * plus the per-node decorator that stamps the editable wrappers (selection/hover\n * state, rich-text remount epochs, and the EC-300 unresolved-reusable\n * placeholder). The binding resolver and the EC-176 locale-fallback chips (which\n * read bound props) went with the data-binding subsystem (EC-574).\n *\n * The context assembly is a pure factory ({@link createCanvasRenderContext})\n * over a pure decision helper ({@link unresolvedReuseStatus}), so the decoration\n * and resolver wiring are unit-testable without providers;\n * {@link useCanvasRenderContext} binds them to the live stores.\n */\n\n/** Collect every node id under a root (the page subtree). */\nexport function collectIds(node: ComponentNode, out: Set<string> = new Set()): Set<string> {\n out.add(node.id)\n for (const children of Object.values(node.slots ?? {})) {\n for (const child of children) collectIds(child, out)\n }\n return out\n}\n\n/**\n * EC-300/AD-16: a transcluding embed whose target doesn't resolve in the active\n * perspective renders a placeholder, never an empty section. `unpublished` = the\n * target exists as a draft but isn't live (Published view of a draft-only reusable);\n * `missing` = no resolvable target at all (deleted). `null` = resolves fine, or the\n * node isn't an embed / has no target picked yet (its own empty state owns that).\n */\nexport function unresolvedReuseStatus(\n node: ComponentNode,\n capability: ReuseCapability | null | undefined,\n resolveAt: (ref: { collection: string; id: string }, perspective: CanvasPerspective) => unknown,\n perspective: CanvasPerspective,\n): 'unpublished' | 'missing' | null {\n if (!capability) return null\n const ref = reuseEmbedRef(node, capability)\n if (!ref) return null\n if (resolveAt(ref, perspective)) return null\n return resolveAt(ref, 'draft') ? 'unpublished' : 'missing'\n}\n\n/** Everything the pure context factory needs — resolvers plus decoration state. */\nexport interface CanvasRenderContextDeps {\n registry: RenderContext['registry']\n implementations: RenderContext['implementations']\n resolveAsset: ResolveAsset\n resolveRelation: ResolveRelation\n resolveListing: ResolveListing\n resolveLink: ResolveLink\n /** Only page nodes are editable; layout chrome must not be decorated. */\n pageNodeIds: ReadonlySet<string>\n selectedId: string | null\n hoveredId: string | null\n /** Per-node remount epochs from the inline rich-text editing (EC-159). */\n richEditEpochs: Record<string, number>\n unresolvedReuse(node: ComponentNode): 'unpublished' | 'missing' | null\n /**\n * EC-460 (never-silent): a human-readable message when the node declares a\n * `listing` whose built query is STATICALLY invalid against the active session\n * schema (unknown collection, non-filterable filter/sort field) — the canvas\n * then shows a visible notice above the block instead of an empty grid that\n * is indistinguishable from \"no content yet\". `null` = healthy (or no listing).\n */\n listingIssueOf(node: ComponentNode): string | null\n}\n\n/**\n * Assemble the canvas `RenderContext`. Pure (no hooks): the same deps produce\n * the same context, so the decorator's wrapper markup is pinnable in tests.\n */\nexport function createCanvasRenderContext(deps: CanvasRenderContextDeps): RenderContext {\n return {\n registry: deps.registry,\n implementations: deps.implementations,\n resolveAsset: deps.resolveAsset,\n resolveRelation: deps.resolveRelation,\n resolveListing: deps.resolveListing,\n resolveLink: deps.resolveLink,\n decorateNode: (node, rendered) => {\n if (!deps.pageNodeIds.has(node.id)) return rendered\n // An unresolved hard reference shows the placeholder in place of the empty\n // transclusion; the node stays selectable, so its inspector relation chip\n // (EC-296) deep-links to the reusable to edit/publish it.\n const unresolved = deps.unresolvedReuse(node)\n // EC-460: a config-invalid listing query gets a visible notice ABOVE the\n // block (which keeps its own empty state below) — never a silent empty grid.\n const listingIssue = deps.listingIssueOf(node)\n return (\n <div\n // The epoch suffix forces a remount after a rich-text editing\n // session mutated this node's rendered DOM (see richEditEpochs).\n key={`${node.id}#${deps.richEditEpochs[node.id] ?? 0}`}\n data-ec-node-id={node.id}\n className=\"ec-editable\"\n data-selected={node.id === deps.selectedId || undefined}\n data-hovered={node.id === deps.hoveredId || undefined}\n >\n {unresolved ? (\n <UnresolvedReusablePlaceholder status={unresolved} />\n ) : (\n <>\n {listingIssue !== null ? <CanvasListingIssueNotice message={listingIssue} /> : null}\n {rendered}\n </>\n )}\n </div>\n )\n },\n }\n}\n\n/**\n * The live canvas render context: binds the pure factory to the studio's\n * stores (asset library, content session, active locale, reuse capability) for\n * the given perspective. Returns the context plus the page-node id set the\n * canvas also needs for spacing-handle gating.\n */\nexport function useCanvasRenderContext(args: {\n /** The edited composition root (`builder.present`). */\n present: ComponentNode\n selectedId: string | null\n hoveredId: string | null\n studio: StudioRegistry\n perspective: CanvasPerspective\n richEditEpochs: Record<string, number>\n}): { ctx: RenderContext; pageNodeIds: ReadonlySet<string> } {\n const { present, selectedId, hoveredId, studio, perspective, richEditEpochs } = args\n\n // EC-460: the canvas follows the preview content session reactively. The\n // epoch bumps whenever `configureContentSession` re-points the session (the\n // write-sync refresh, a CMS-surface hydration, boot) — it keys the resolver\n // callbacks below, so the context identity changes and the canvas re-renders\n // against the fresh session. Registering as a CONSUMER tells the write-sync a\n // preview surface is live, so a stale session actually refreshes (with no\n // consumer it only flags — connected mode never pays a per-autosave query\n // while nobody is previewing).\n const sessionEpoch = useSyncExternalStore(subscribeContentSession, contentSessionEpoch)\n useEffect(() => acquireContentSessionConsumer(), [])\n\n // Only page nodes are editable; layout chrome (rendered around the page) is\n // read-only context and must not be selectable (it isn't in the page tree).\n const pageNodeIds = useMemo(() => collectIds(present), [present])\n\n // EC-195: resolve an `asset`-typed Image prop (an asset id) to its url +\n // intrinsic dimensions from the live asset library, so the canvas previews\n // exactly what the host renders. A record without a resolved url (e.g. a\n // never-served seed) → null → the implementation's visible empty state.\n const assetsStore = useOptionalAssets()\n const resolveAsset: ResolveAsset = useCallback(\n (assetId) => {\n const record = assetsStore?.assets.find((a) => a.id === assetId)\n if (!record?.url) return null\n return {\n url: record.url,\n width: record.width ?? null,\n height: record.height ?? null,\n alt: record.alt ?? null,\n focalPoint: record.focalPoint ?? null,\n }\n },\n [assetsStore],\n )\n\n // The canvas's active locale for relation/listing/link resolution below.\n // EC-479: the ambient locale is `null` until a project's config is installed (it\n // no longer opens on the engine's `en/de` fixture). `undefined` means \"you pick\" to\n // the content engine, which then resolves in the SESSION's own default locale —\n // never a fabricated one — so a canvas mounted outside a CMS session still previews\n // in the project's locale.\n const { locale } = useStudioLocale()\n const activeLocale = locale ?? undefined\n // EC-300: the reuse capability identifies a transcluding embed + its relation prop,\n // so the canvas can detect a HARD reference whose target doesn't resolve and show a\n // placeholder instead of an empty section. Null-safe (no provider / no capability).\n const reuse = useReuse()\n\n // EC-254: populate a block's `relation` prop (the editor's document pick — a\n // ProductGrid's products, a Reusable's element) to delivery shape from the\n // active content session, in the canvas's active locale, so the canvas previews\n // picked documents exactly as the host renders them. The studio analog of the\n // host's baked relations map. A missing/invisible target → null → the block's\n // own empty state.\n const resolveRelation: ResolveRelation = useCallback(\n (ref) => resolveCanvasRelation(ref, activeLocale, perspective),\n // sessionEpoch: re-resolve when the content session was re-pointed (EC-460).\n [activeLocale, perspective, sessionEpoch],\n )\n\n // EC-255: resolve a listing block's query (a ProductGrid keyed to a category) to\n // its documents from the active content session, in the canvas's active locale, so\n // the canvas previews a dynamic grid exactly as the host renders it. The studio\n // analog of the host's baked listings map; an empty/invalid query → [] → the\n // block's own empty state.\n const resolveListing: ResolveListing = useCallback(\n (query) => resolveCanvasListing(query, activeLocale, perspective),\n [activeLocale, perspective, sessionEpoch],\n )\n\n // EC-291: resolve a rich-text internal link (`{type:'reference'}`) to its target's\n // URL path from the active content session, in the canvas's active locale — the\n // studio analog of the host's baked links map. A missing/unpublished/unroutable\n // target → null → the rich-text renderer shows the visible `unresolved-ref` issue.\n const resolveLink: ResolveLink = useCallback(\n (ref) => resolveCanvasLink(ref, activeLocale, perspective),\n [activeLocale, perspective, sessionEpoch],\n )\n\n const unresolvedReuse = useCallback(\n (node: ComponentNode): 'unpublished' | 'missing' | null =>\n unresolvedReuseStatus(\n node,\n reuse?.capability,\n (ref, p) => resolveCanvasRelation(ref, activeLocale, p),\n perspective,\n ),\n [reuse, activeLocale, perspective, sessionEpoch],\n )\n\n // EC-460 (never-silent): flag a listing whose query is statically invalid\n // against the active session schema. Pure over the registry + the shared\n // `listingQueryOf` (the same query builder the renderer/host use), so notice\n // and resolution can never disagree about WHICH query was judged.\n const listingIssueOf = useCallback(\n (node: ComponentNode): string | null => {\n const manifest = studio.registry.getManifest(node.componentId)\n if (!manifest?.listing) return null\n const query = listingQueryOf(node, manifest.listing)\n if (!query) return null // awaiting the filter pick — the block's own empty state\n const errors = validateCanvasListingQuery(query)\n if (errors.length === 0) return null\n // EC-494: `ContentQueryError` carries a catalog ref, so this notice localizes.\n return errors.map((error) => contentQueryErrorMessage(error)).join(' ')\n },\n [studio, sessionEpoch],\n )\n\n const ctx: RenderContext = useMemo(\n () =>\n createCanvasRenderContext({\n registry: studio.registry,\n implementations: studio.implementations,\n resolveAsset,\n resolveRelation,\n resolveListing,\n resolveLink,\n pageNodeIds,\n selectedId,\n hoveredId,\n richEditEpochs,\n unresolvedReuse,\n listingIssueOf,\n }),\n [studio, resolveAsset, resolveRelation, resolveListing, resolveLink, unresolvedReuse, pageNodeIds, selectedId, hoveredId, richEditEpochs, listingIssueOf],\n )\n\n return { ctx, pageNodeIds }\n}\n\n/**\n * In-canvas placeholder for a transcluding embed whose target doesn't resolve\n * (EC-300/AD-16) — shown in place of the empty section so a hard reference never\n * silently vanishes. Rendered INTO the iframe (portal), which carries only the\n * project's stylesheet, so it uses inline styles rather than studio UI classes\n * (the EC-414 AD-B chrome contract), with the `.ec-chrome` containment recipe\n * on its root. Exported for the chrome contract test. The node stays\n * selectable; the inspector's `element` relation chip (EC-296) deep-links to\n * the reusable to edit or publish it.\n */\n/**\n * In-canvas notice for a listing block whose query is statically invalid\n * against the active session schema (EC-460 — the never-silent rule): the\n * message renders ABOVE the block (which keeps its own empty state), so a\n * misdeclared `listing` manifest (non-filterable sort/filter field, unknown\n * collection) is an explicit, visible state — never an empty grid an editor\n * can't tell apart from \"no content yet\". Rendered INTO the iframe (project\n * stylesheet only), so it uses inline styles + the `.ec-chrome` containment\n * recipe, exactly like {@link UnresolvedReusablePlaceholder}. Exported for the\n * chrome contract test.\n */\nexport function CanvasListingIssueNotice({ message }: { message: string }) {\n return (\n <div\n className=\"ec-chrome\"\n data-testid=\"canvas-listing-issue\"\n style={{\n boxSizing: 'border-box',\n border: '1px dashed rgba(200,140,40,0.65)',\n borderRadius: 8,\n padding: '10px 14px',\n margin: '8px 16px',\n background: 'rgba(230,160,40,0.08)',\n color: 'rgba(120,80,10,0.95)',\n font: '13px/1.5 system-ui, -apple-system, sans-serif',\n }}\n >\n <span style={{ fontWeight: 600 }}>⚠ {translate('builder.canvas.listingInvalidTitle')}</span>{' '}\n <span style={{ opacity: 0.9 }}>{message}</span>\n </div>\n )\n}\n\nexport function UnresolvedReusablePlaceholder({ status }: { status: 'unpublished' | 'missing' }) {\n const title =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedTitle')\n : translate('builder.canvas.reusableMissingTitle')\n const hint =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedHint')\n : translate('builder.canvas.reusableMissingHint')\n return (\n <div\n className=\"ec-chrome\"\n style={{\n boxSizing: 'border-box',\n border: '1px dashed rgba(120,120,120,0.5)',\n borderRadius: 8,\n padding: '24px 28px',\n margin: '12px 16px',\n background: 'rgba(127,127,127,0.06)',\n color: 'rgba(90,90,90,0.95)',\n font: '14px/1.5 system-ui, -apple-system, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontWeight: 600 }}>⚠ {title}</div>\n <div style={{ marginTop: 6, fontSize: 13, opacity: 0.85 }}>{hint}</div>\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"canvas-render-context.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-render-context.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react'\nimport { resolvedAsset } from '@elytracms/core/content'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { listingQueryOf } from '@elytracms/core/runtime-renderer'\nimport type {\n RenderAsset,\n RenderContext,\n ResolveAsset,\n ResolveLink,\n ResolveListing,\n ResolveRelation,\n} from '@elytracms/core/runtime-renderer'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { contentQueryErrorMessage } from '@elytracms/studio/lib/persona/labels'\nimport {\n acquireContentSessionConsumer,\n contentSessionEpoch,\n resolveCanvasLink,\n resolveCanvasListing,\n resolveCanvasRelation,\n subscribeContentSession,\n validateCanvasListingQuery,\n} from '@elytracms/studio/lib/data'\nimport type { CanvasPerspective } from '@elytracms/studio/lib/data'\nimport { useStudioLocale } from '@elytracms/studio/lib/locale'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { useReuse } from './reuse-context'\nimport { reuseEmbedRef } from './reuse'\nimport type { ReuseCapability } from './reuse'\n\n/**\n * The canvas's `RenderContext` (EC-330, extracted from `BuilderCanvas`): the\n * injected resolvers that make the iframe preview match delivery — assets\n * (EC-195), relations (EC-254), listings (EC-255), rich-text links (EC-291) —\n * plus the per-node decorator that stamps the editable wrappers (selection/hover\n * state, rich-text remount epochs, and the EC-300 unresolved-reusable\n * placeholder). The binding resolver and the EC-176 locale-fallback chips (which\n * read bound props) went with the data-binding subsystem (EC-574).\n *\n * The context assembly is a pure factory ({@link createCanvasRenderContext})\n * over a pure decision helper ({@link unresolvedReuseStatus}), so the decoration\n * and resolver wiring are unit-testable without providers;\n * {@link useCanvasRenderContext} binds them to the live stores.\n */\n\n/** Collect every node id under a root (the page subtree). */\nexport function collectIds(node: ComponentNode, out: Set<string> = new Set()): Set<string> {\n out.add(node.id)\n for (const children of Object.values(node.slots ?? {})) {\n for (const child of children) collectIds(child, out)\n }\n return out\n}\n\n/**\n * EC-300/AD-16: a transcluding embed whose target doesn't resolve in the active\n * perspective renders a placeholder, never an empty section. `unpublished` = the\n * target exists as a draft but isn't live (Published view of a draft-only reusable);\n * `missing` = no resolvable target at all (deleted). `null` = resolves fine, or the\n * node isn't an embed / has no target picked yet (its own empty state owns that).\n */\nexport function unresolvedReuseStatus(\n node: ComponentNode,\n capability: ReuseCapability | null | undefined,\n resolveAt: (ref: { collection: string; id: string }, perspective: CanvasPerspective) => unknown,\n perspective: CanvasPerspective,\n): 'unpublished' | 'missing' | null {\n if (!capability) return null\n const ref = reuseEmbedRef(node, capability)\n if (!ref) return null\n if (resolveAt(ref, perspective)) return null\n return resolveAt(ref, 'draft') ? 'unpublished' : 'missing'\n}\n\n/** Everything the pure context factory needs — resolvers plus decoration state. */\nexport interface CanvasRenderContextDeps {\n registry: RenderContext['registry']\n implementations: RenderContext['implementations']\n resolveAsset: ResolveAsset\n resolveRelation: ResolveRelation\n resolveListing: ResolveListing\n resolveLink: ResolveLink\n /** Only page nodes are editable; layout chrome must not be decorated. */\n pageNodeIds: ReadonlySet<string>\n selectedId: string | null\n hoveredId: string | null\n /** Per-node remount epochs from the inline rich-text editing (EC-159). */\n richEditEpochs: Record<string, number>\n unresolvedReuse(node: ComponentNode): 'unpublished' | 'missing' | null\n /**\n * EC-460 (never-silent): a human-readable message when the node declares a\n * `listing` whose built query is STATICALLY invalid against the active session\n * schema (unknown collection, non-filterable filter/sort field) — the canvas\n * then shows a visible notice above the block instead of an empty grid that\n * is indistinguishable from \"no content yet\". `null` = healthy (or no listing).\n */\n listingIssueOf(node: ComponentNode): string | null\n}\n\n/**\n * Assemble the canvas `RenderContext`. Pure (no hooks): the same deps produce\n * the same context, so the decorator's wrapper markup is pinnable in tests.\n */\nexport function createCanvasRenderContext(deps: CanvasRenderContextDeps): RenderContext {\n return {\n registry: deps.registry,\n implementations: deps.implementations,\n resolveAsset: deps.resolveAsset,\n resolveRelation: deps.resolveRelation,\n resolveListing: deps.resolveListing,\n resolveLink: deps.resolveLink,\n decorateNode: (node, rendered) => {\n if (!deps.pageNodeIds.has(node.id)) return rendered\n // An unresolved hard reference shows the placeholder in place of the empty\n // transclusion; the node stays selectable, so its inspector relation chip\n // (EC-296) deep-links to the reusable to edit/publish it.\n const unresolved = deps.unresolvedReuse(node)\n // EC-460: a config-invalid listing query gets a visible notice ABOVE the\n // block (which keeps its own empty state below) — never a silent empty grid.\n const listingIssue = deps.listingIssueOf(node)\n return (\n <div\n // The epoch suffix forces a remount after a rich-text editing\n // session mutated this node's rendered DOM (see richEditEpochs).\n key={`${node.id}#${deps.richEditEpochs[node.id] ?? 0}`}\n data-ec-node-id={node.id}\n className=\"ec-editable\"\n data-selected={node.id === deps.selectedId || undefined}\n data-hovered={node.id === deps.hoveredId || undefined}\n >\n {unresolved ? (\n <UnresolvedReusablePlaceholder status={unresolved} />\n ) : (\n <>\n {listingIssue !== null ? <CanvasListingIssueNotice message={listingIssue} /> : null}\n {rendered}\n </>\n )}\n </div>\n )\n },\n }\n}\n\n/**\n * The live canvas render context: binds the pure factory to the studio's\n * stores (asset library, content session, active locale, reuse capability) for\n * the given perspective. Returns the context plus the page-node id set the\n * canvas also needs for spacing-handle gating.\n */\n/**\n * The canvas's asset reader IS delivery's (NK-049 finding, 2026-10-23). This used to\n * hand-pick `url/width/height/alt/focalPoint`, read `alt` from the legacy column\n * only and drop `meta` — so a component's AI badge (`meta.aiGenerated`) and an\n * editor-set `meta.alt` rendered on the site but not on the canvas. One reader\n * (`resolvedAsset`, `content/assets.ts`) means the canvas and the host cannot\n * disagree about an asset's own facts; `RenderAsset` is a subset of\n * `ResolvedAsset`, `lqip` included. Pure, exported for the node test.\n */\nexport function canvasRenderAsset(record: AssetRecord | undefined): RenderAsset | null {\n if (!record?.url) return null\n return resolvedAsset(record, record.url)\n}\n\nexport function useCanvasRenderContext(args: {\n /** The edited composition root (`builder.present`). */\n present: ComponentNode\n selectedId: string | null\n hoveredId: string | null\n studio: StudioRegistry\n perspective: CanvasPerspective\n richEditEpochs: Record<string, number>\n}): { ctx: RenderContext; pageNodeIds: ReadonlySet<string> } {\n const { present, selectedId, hoveredId, studio, perspective, richEditEpochs } = args\n\n // EC-460: the canvas follows the preview content session reactively. The\n // epoch bumps whenever `configureContentSession` re-points the session (the\n // write-sync refresh, a CMS-surface hydration, boot) — it keys the resolver\n // callbacks below, so the context identity changes and the canvas re-renders\n // against the fresh session. Registering as a CONSUMER tells the write-sync a\n // preview surface is live, so a stale session actually refreshes (with no\n // consumer it only flags — connected mode never pays a per-autosave query\n // while nobody is previewing).\n const sessionEpoch = useSyncExternalStore(subscribeContentSession, contentSessionEpoch)\n useEffect(() => acquireContentSessionConsumer(), [])\n\n // Only page nodes are editable; layout chrome (rendered around the page) is\n // read-only context and must not be selectable (it isn't in the page tree).\n const pageNodeIds = useMemo(() => collectIds(present), [present])\n\n // EC-195: resolve an `asset`-typed Image prop (an asset id) from the live asset\n // library, so the canvas previews exactly what the host renders. A record\n // without a resolved url (e.g. a never-served seed) → null → the\n // implementation's visible empty state.\n const assetsStore = useOptionalAssets()\n const resolveAsset: ResolveAsset = useCallback(\n (assetId) => canvasRenderAsset(assetsStore?.assets.find((a) => a.id === assetId)),\n [assetsStore],\n )\n\n // The canvas's active locale for relation/listing/link resolution below.\n // EC-479: the ambient locale is `null` until a project's config is installed (it\n // no longer opens on the engine's `en/de` fixture). `undefined` means \"you pick\" to\n // the content engine, which then resolves in the SESSION's own default locale —\n // never a fabricated one — so a canvas mounted outside a CMS session still previews\n // in the project's locale.\n const { locale } = useStudioLocale()\n const activeLocale = locale ?? undefined\n // EC-300: the reuse capability identifies a transcluding embed + its relation prop,\n // so the canvas can detect a HARD reference whose target doesn't resolve and show a\n // placeholder instead of an empty section. Null-safe (no provider / no capability).\n const reuse = useReuse()\n\n // EC-254: populate a block's `relation` prop (the editor's document pick — a\n // ProductGrid's products, a Reusable's element) to delivery shape from the\n // active content session, in the canvas's active locale, so the canvas previews\n // picked documents exactly as the host renders them. The studio analog of the\n // host's baked relations map. A missing/invisible target → null → the block's\n // own empty state.\n const resolveRelation: ResolveRelation = useCallback(\n (ref) => resolveCanvasRelation(ref, activeLocale, perspective),\n // sessionEpoch: re-resolve when the content session was re-pointed (EC-460).\n [activeLocale, perspective, sessionEpoch],\n )\n\n // EC-255: resolve a listing block's query (a ProductGrid keyed to a category) to\n // its documents from the active content session, in the canvas's active locale, so\n // the canvas previews a dynamic grid exactly as the host renders it. The studio\n // analog of the host's baked listings map; an empty/invalid query → [] → the\n // block's own empty state.\n const resolveListing: ResolveListing = useCallback(\n (query) => resolveCanvasListing(query, activeLocale, perspective),\n [activeLocale, perspective, sessionEpoch],\n )\n\n // EC-291: resolve a rich-text internal link (`{type:'reference'}`) to its target's\n // URL path from the active content session, in the canvas's active locale — the\n // studio analog of the host's baked links map. A missing/unpublished/unroutable\n // target → null → the rich-text renderer shows the visible `unresolved-ref` issue.\n const resolveLink: ResolveLink = useCallback(\n (ref) => resolveCanvasLink(ref, activeLocale, perspective),\n [activeLocale, perspective, sessionEpoch],\n )\n\n const unresolvedReuse = useCallback(\n (node: ComponentNode): 'unpublished' | 'missing' | null =>\n unresolvedReuseStatus(\n node,\n reuse?.capability,\n (ref, p) => resolveCanvasRelation(ref, activeLocale, p),\n perspective,\n ),\n [reuse, activeLocale, perspective, sessionEpoch],\n )\n\n // EC-460 (never-silent): flag a listing whose query is statically invalid\n // against the active session schema. Pure over the registry + the shared\n // `listingQueryOf` (the same query builder the renderer/host use), so notice\n // and resolution can never disagree about WHICH query was judged.\n const listingIssueOf = useCallback(\n (node: ComponentNode): string | null => {\n const manifest = studio.registry.getManifest(node.componentId)\n if (!manifest?.listing) return null\n const query = listingQueryOf(node, manifest.listing)\n if (!query) return null // awaiting the filter pick — the block's own empty state\n const errors = validateCanvasListingQuery(query)\n if (errors.length === 0) return null\n // EC-494: `ContentQueryError` carries a catalog ref, so this notice localizes.\n return errors.map((error) => contentQueryErrorMessage(error)).join(' ')\n },\n [studio, sessionEpoch],\n )\n\n const ctx: RenderContext = useMemo(\n () =>\n createCanvasRenderContext({\n registry: studio.registry,\n implementations: studio.implementations,\n resolveAsset,\n resolveRelation,\n resolveListing,\n resolveLink,\n pageNodeIds,\n selectedId,\n hoveredId,\n richEditEpochs,\n unresolvedReuse,\n listingIssueOf,\n }),\n [studio, resolveAsset, resolveRelation, resolveListing, resolveLink, unresolvedReuse, pageNodeIds, selectedId, hoveredId, richEditEpochs, listingIssueOf],\n )\n\n return { ctx, pageNodeIds }\n}\n\n/**\n * In-canvas placeholder for a transcluding embed whose target doesn't resolve\n * (EC-300/AD-16) — shown in place of the empty section so a hard reference never\n * silently vanishes. Rendered INTO the iframe (portal), which carries only the\n * project's stylesheet, so it uses inline styles rather than studio UI classes\n * (the EC-414 AD-B chrome contract), with the `.ec-chrome` containment recipe\n * on its root. Exported for the chrome contract test. The node stays\n * selectable; the inspector's `element` relation chip (EC-296) deep-links to\n * the reusable to edit or publish it.\n */\n/**\n * In-canvas notice for a listing block whose query is statically invalid\n * against the active session schema (EC-460 — the never-silent rule): the\n * message renders ABOVE the block (which keeps its own empty state), so a\n * misdeclared `listing` manifest (non-filterable sort/filter field, unknown\n * collection) is an explicit, visible state — never an empty grid an editor\n * can't tell apart from \"no content yet\". Rendered INTO the iframe (project\n * stylesheet only), so it uses inline styles + the `.ec-chrome` containment\n * recipe, exactly like {@link UnresolvedReusablePlaceholder}. Exported for the\n * chrome contract test.\n */\nexport function CanvasListingIssueNotice({ message }: { message: string }) {\n return (\n <div\n className=\"ec-chrome\"\n data-testid=\"canvas-listing-issue\"\n style={{\n boxSizing: 'border-box',\n border: '1px dashed rgba(200,140,40,0.65)',\n borderRadius: 8,\n padding: '10px 14px',\n margin: '8px 16px',\n background: 'rgba(230,160,40,0.08)',\n color: 'rgba(120,80,10,0.95)',\n font: '13px/1.5 system-ui, -apple-system, sans-serif',\n }}\n >\n <span style={{ fontWeight: 600 }}>⚠ {translate('builder.canvas.listingInvalidTitle')}</span>{' '}\n <span style={{ opacity: 0.9 }}>{message}</span>\n </div>\n )\n}\n\nexport function UnresolvedReusablePlaceholder({ status }: { status: 'unpublished' | 'missing' }) {\n const title =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedTitle')\n : translate('builder.canvas.reusableMissingTitle')\n const hint =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedHint')\n : translate('builder.canvas.reusableMissingHint')\n return (\n <div\n className=\"ec-chrome\"\n style={{\n boxSizing: 'border-box',\n border: '1px dashed rgba(120,120,120,0.5)',\n borderRadius: 8,\n padding: '24px 28px',\n margin: '12px 16px',\n background: 'rgba(127,127,127,0.06)',\n color: 'rgba(90,90,90,0.95)',\n font: '14px/1.5 system-ui, -apple-system, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontWeight: 600 }}>⚠ {title}</div>\n <div style={{ marginTop: 6, fontSize: 13, opacity: 0.85 }}>{hint}</div>\n </div>\n )\n}\n"]}
@@ -11,7 +11,8 @@ import type { AssetFieldDef } from '@elytracms/core/cms-core';
11
11
  * a focal-point-aware preview, uploads directly from the field (reusing the EC-152
12
12
  * upload path — no parallel uploader), and edits the selected image's alt + focal
13
13
  * point inline (persisted on the asset document, where delivery reads them, EC-230).
14
- * With no `<AssetsProvider>` (fixtures/playground) it degrades to the labelled-badge
14
+ * With no `<AssetsProvider>` (a fixture-only session — every project, the playground
15
+ * included, mounts one, `studio-project-screen.tsx`) it degrades to the labelled-badge
15
16
  * picker — no preview/upload — so it stays usable without a real library.
16
17
  */
17
18
  export declare function AssetField({ field, value, onChange, readOnly, }: {
@@ -1,19 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { AlertTriangle, ImagePlus, RotateCcw, SlidersHorizontal, Trash2, Upload, X } from 'lucide-react';
2
+ import { ImagePlus, RotateCcw, SlidersHorizontal, Trash2, Upload, X } from 'lucide-react';
3
3
  import { useEffect, useMemo, useRef, useState } from 'react';
4
- import { assetAcceptAttr, assetMatchesAccept, assetNode, assetSubfieldsOf, describeAccept, readAssetId, } from '@elytracms/core/cms-core';
4
+ import { assetNode, assetSubfieldsOf, readAssetId } from '@elytracms/core/cms-core';
5
5
  import { Button, Card, Code, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Field, Row, Spacer, Stack, Text, } from '@elytracms/studio/ui';
6
6
  import { cn } from '@elytracms/studio/lib/utils';
7
7
  import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
8
8
  import { appendAssetSelections, applyAssetSelection, assetIdsOf, assetPickForm, singleAssetIdOf, } from '../assets/asset-selection.js';
9
9
  import { useOptionalAssets } from '../assets/assets-store.js';
10
10
  import { AssetThumb, FocalCropPreviews, FocalPointPicker, useAssetRecord } from '../assets/asset-thumb.js';
11
- import { uploadBatchProgress } from './asset-picker-core.js';
11
+ import { assetPickerBinding, splitByAccept, uploadBatchProgress } from './asset-picker-core.js';
12
12
  import { assetMetaFieldsOf, pruneMeta } from '../assets/asset-metadata.js';
13
13
  import { fieldLabel } from './cms-labels.js';
14
14
  import { useCollectionView } from '../collection-view/collection-view-context.js';
15
15
  import { useFieldDensity } from './field-density.js';
16
- import { AssetPicker } from './asset-picker.js';
16
+ import { AssetPicker, AssetRejectedNotice } from './asset-picker.js';
17
17
  import { FieldControl } from './field-control.js';
18
18
  import { ObjectItemForm } from './object-array-field.js';
19
19
  /**
@@ -28,7 +28,8 @@ import { ObjectItemForm } from './object-array-field.js';
28
28
  * a focal-point-aware preview, uploads directly from the field (reusing the EC-152
29
29
  * upload path — no parallel uploader), and edits the selected image's alt + focal
30
30
  * point inline (persisted on the asset document, where delivery reads them, EC-230).
31
- * With no `<AssetsProvider>` (fixtures/playground) it degrades to the labelled-badge
31
+ * With no `<AssetsProvider>` (a fixture-only session — every project, the playground
32
+ * included, mounts one, `studio-project-screen.tsx`) it degrades to the labelled-badge
32
33
  * picker — no preview/upload — so it stays usable without a real library.
33
34
  */
34
35
  export function AssetField({ field, value, onChange, readOnly, }) {
@@ -42,9 +43,9 @@ export function AssetField({ field, value, onChange, readOnly, }) {
42
43
  // Names of files a drop/pick rejected against the field's `accept` (EC-301) — shown
43
44
  // as a transient inline issue, cleared on the next valid upload. Never a silent drop.
44
45
  const [rejected, setRejected] = useState([]);
45
- // The active upload BATCH (EC-460): the pre-upload library snapshot + how many
46
- // files the drop submitted, so the effect below attaches the WHOLE batch (the
47
- // `upload()` API returns nothing — the library diff is the signal).
46
+ // The active upload BATCH (EC-460): exactly the upload ids this field's drop
47
+ // started (EC-588 A7), so the effect below attaches the WHOLE batch and nothing
48
+ // another tab or a sibling field uploaded meanwhile.
48
49
  const pendingRef = useRef(null);
49
50
  const assetCollectionId = source.assetCollectionId ?? 'asset';
50
51
  const many = field.multiple;
@@ -61,12 +62,6 @@ export function AssetField({ field, value, onChange, readOnly, }) {
61
62
  // The raw `many` entries (envelopes or bare ids), index-aligned with `ids`, so each
62
63
  // gallery item can read/write its own sub-fields.
63
64
  const entries = many && Array.isArray(value) ? value : [];
64
- const libraryIds = useMemo(() => (assets?.status === 'ready' ? assets.assets.map((a) => a.id) : []), [assets]);
65
- // Uploads still running (failed entries are terminal — visible in the queue
66
- // for retry; they must not keep a batch waiting forever).
67
- const uploadsInFlight = assets
68
- ? assets.uploads.filter((u) => u.state.phase !== 'failed').length
69
- : 0;
70
65
  // Auto-select what an upload FROM this field added once it lands in the
71
66
  // library. EC-460: a `many` field attaches EVERY batch arrival as it lands
72
67
  // (the batch stays armed until it settles), so dropping N files attaches all
@@ -74,9 +69,9 @@ export function AssetField({ field, value, onChange, readOnly, }) {
74
69
  // rest in the library. A `one` field keeps the first-to-land selection.
75
70
  useEffect(() => {
76
71
  const batch = pendingRef.current;
77
- if (!batch)
72
+ if (!batch || !assets)
78
73
  return;
79
- const progress = uploadBatchProgress(batch, libraryIds, uploadsInFlight);
74
+ const progress = uploadBatchProgress(batch, assets, field.accept);
80
75
  if (progress.added.length === 0) {
81
76
  if (progress.settled)
82
77
  pendingRef.current = null; // everything failed — disarm
@@ -94,47 +89,28 @@ export function AssetField({ field, value, onChange, readOnly, }) {
94
89
  const next = applyAssetSelection(value, progress.added[0], false, form);
95
90
  if (next !== value)
96
91
  onChange(next);
97
- // Reacts to the library set growing (an upload landing) and the upload
98
- // queue draining (a batch settling) — `value`/`onChange` are read from the
99
- // closure on purpose.
100
- }, [libraryIds, uploadsInFlight, many, form, onChange, value]);
92
+ // Reacts to the assets store changing (an upload landing in the library, the
93
+ // queue draining) — `value`/`onChange` are read from the closure on purpose.
94
+ }, [assets, field.accept, many, form, onChange, value]);
101
95
  function uploadFiles(files) {
102
96
  if (!assets || files.length === 0)
103
97
  return;
104
- // EC-301: a drag-drop bypasses the file dialog's `accept` filter — re-check each
105
- // file against the field's `accept` and surface a visible issue for the rejects
106
- // instead of silently uploading the wrong kind.
107
- const ok = [];
108
- const bad = [];
109
- for (const file of files) {
110
- if (assetMatchesAccept({ contentType: file.type, filename: file.name }, field.accept))
111
- ok.push(file);
112
- else
113
- bad.push(file.name);
114
- }
115
- setRejected(bad);
116
- if (ok.length === 0)
98
+ // EC-301 → EC-588: the drop rule the picker applies too — a drag-drop bypasses
99
+ // the file dialog's `accept` filter, so re-check each file and name the rejects in
100
+ // a visible notice instead of silently uploading the wrong kind.
101
+ const { accepted, rejected: refused } = splitByAccept(files, field.accept);
102
+ setRejected(refused);
103
+ if (accepted.length === 0)
117
104
  return;
118
- // Remember the library snapshot + the batch size so the effect above can
119
- // attach every upload of this batch as it lands (the upload API returns nothing).
120
- pendingRef.current = { before: new Set(libraryIds), expected: ok.length };
121
- assets.upload(ok);
105
+ pendingRef.current = { uploadIds: assets.upload(accepted) };
122
106
  }
123
- // Apply a picked asset id through the pick operation the table cell also commits
124
- // (EC-558), so a placement's per-use sub-fields (EC-301) survive a swap or an append.
125
- function applyPicked(picked) {
126
- const next = applyAssetSelection(value, picked, many, form);
107
+ // The picker is configured from the field — the SAME binding the table cell uses
108
+ // (EC-570 #1 → EC-588): `accept`, `multiple`, and a pick through the carry rule
109
+ // (EC-558) so a placement's per-use sub-fields (EC-301) survive a swap or an append.
110
+ const pickerBinding = assetPickerBinding(field, value, (next) => {
127
111
  if (next !== value)
128
112
  onChange(next);
129
- }
130
- // EC-460: attach a whole settled in-picker upload batch to a `many` field —
131
- // every id not already attached, in arrival order (envelopes when the field
132
- // declares per-use sub-fields). Only wired for `many` fields (see the picker).
133
- function applyPickedMany(picked) {
134
- const next = appendAssetSelections(value, picked, form);
135
- if (next !== value)
136
- onChange(next);
137
- }
113
+ });
138
114
  const canUpload = !readOnly && assets !== null;
139
115
  // Drag-to-upload onto the field (EC-301): a depth counter keeps the overlay
140
116
  // steady as the pointer crosses child element boundaries. `dragenter`/`dragleave`
@@ -188,15 +164,7 @@ export function AssetField({ field, value, onChange, readOnly, }) {
188
164
  nextEntries[i] = assetNode(aid, next);
189
165
  onChange(nextEntries);
190
166
  }, onRemove: readOnly ? undefined : () => onChange(entries.filter((_, j) => j !== i)) }, `${aid}-${i}`));
191
- }) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : many ? (_jsxs(Stack, { gap: "2", children: [ids.length > 0 ? (_jsx("div", { className: cn('grid gap-2', compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4'), children: ids.map((aid, i) => (_jsx(AssetChip, { assetId: aid, collectionId: assetCollectionId, onRemove: readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid)) }, `${aid}-${i}`))) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : single && hasSub ? (_jsx(Card, { className: "border-l-2 border-l-border p-3", children: _jsxs(Stack, { gap: "3", children: [_jsx(AssetSinglePreview, { assetId: single, compact: compact, readOnly: readOnly, hasLibrary: assets !== null, fallbackTitle: source.titleOf({ collection: assetCollectionId, id: single }), selectButton: selectButton, onEdit: () => setEditing(true), onRemove: () => onChange(applyAssetSelection(value, null, many, form)) }), _jsx("div", { className: "border-t border-border/60 pt-3", children: _jsx(ObjectItemForm, { fields: subFields, value: assetSubfieldsOf(value), readOnly: readOnly, idPrefix: "asset-sub", onChange: (next) => onChange(assetNode(single, next)) }) })] }) })) : single ? (_jsx(AssetSinglePreview, { assetId: single, compact: compact, readOnly: readOnly, hasLibrary: assets !== null, fallbackTitle: source.titleOf({ collection: assetCollectionId, id: single }), selectButton: selectButton, onEdit: () => setEditing(true), onRemove: () => onChange(applyAssetSelection(value, null, many, form)) })) : (_jsxs(Stack, { gap: "2", children: [_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) }), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })), !many && single && assets ? (_jsx(AssetOptionsDialog, { assetId: single, readOnly: readOnly, open: editing, onOpenChange: setEditing })) : null, _jsx(AssetPicker, { value: single, open: open, onOpenChange: setOpen, accept: assetAcceptAttr(field.accept), onChange: applyPicked, multiple: many, ...(many ? { onChangeMany: applyPickedMany } : {}) }), rejected.length > 0 ? (_jsxs(Row, { gap: "2", align: "center", children: [_jsx(AlertTriangle, { className: "size-4 shrink-0 text-warning" }), _jsx(Text, { size: "xs", tone: "muted", children: rejected.length === 1
192
- ? translate('cms.asset.rejected.one', {
193
- name: rejected[0],
194
- types: describeAccept(field.accept),
195
- })
196
- : translate('cms.asset.rejected.other', {
197
- count: rejected.length,
198
- types: describeAccept(field.accept),
199
- }) })] })) : null, dragging ? (_jsx("div", { className: "pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85", children: _jsxs(Row, { gap: "2", align: "center", children: [_jsx(Upload, { className: "size-4 text-primary" }), _jsx(Text, { size: "sm", className: "font-medium", children: translate('cms.asset.dropToUpload') })] }) })) : null] }));
167
+ }) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : many ? (_jsxs(Stack, { gap: "2", children: [ids.length > 0 ? (_jsx("div", { className: cn('grid gap-2', compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4'), children: ids.map((aid, i) => (_jsx(AssetChip, { assetId: aid, collectionId: assetCollectionId, onRemove: readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid)) }, `${aid}-${i}`))) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : single && hasSub ? (_jsx(Card, { className: "border-l-2 border-l-border p-3", children: _jsxs(Stack, { gap: "3", children: [_jsx(AssetSinglePreview, { assetId: single, compact: compact, readOnly: readOnly, hasLibrary: assets !== null, fallbackTitle: source.titleOf({ collection: assetCollectionId, id: single }), selectButton: selectButton, onEdit: () => setEditing(true), onRemove: () => onChange(applyAssetSelection(value, null, many, form)) }), _jsx("div", { className: "border-t border-border/60 pt-3", children: _jsx(ObjectItemForm, { fields: subFields, value: assetSubfieldsOf(value), readOnly: readOnly, idPrefix: "asset-sub", onChange: (next) => onChange(assetNode(single, next)) }) })] }) })) : single ? (_jsx(AssetSinglePreview, { assetId: single, compact: compact, readOnly: readOnly, hasLibrary: assets !== null, fallbackTitle: source.titleOf({ collection: assetCollectionId, id: single }), selectButton: selectButton, onEdit: () => setEditing(true), onRemove: () => onChange(applyAssetSelection(value, null, many, form)) })) : (_jsxs(Stack, { gap: "2", children: [_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) }), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })), !many && single && assets ? (_jsx(AssetOptionsDialog, { assetId: single, readOnly: readOnly, open: editing, onOpenChange: setEditing })) : null, _jsx(AssetPicker, { open: open, onOpenChange: setOpen, ...pickerBinding }), _jsx(AssetRejectedNotice, { rejected: rejected, accept: field.accept }), dragging ? (_jsx("div", { className: "pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85", children: _jsxs(Row, { gap: "2", align: "center", children: [_jsx(Upload, { className: "size-4 text-primary" }), _jsx(Text, { size: "sm", className: "font-medium", children: translate('cms.asset.dropToUpload') })] }) })) : null] }));
200
168
  }
201
169
  /**
202
170
  * Empty-state placeholder shown when an asset field has no selection (EC-301).
@@ -1 +1 @@
1
- {"version":3,"file":"asset-field.js","sourceRoot":"","sources":["../../../src/components/cms/asset-field.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, ImagePlus, RotateCcw, SlidersHorizontal, Trash2, Upload, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { AssetFieldDef, FieldDef } from '@elytracms/core/cms-core'\nimport {\n assetAcceptAttr,\n assetMatchesAccept,\n assetNode,\n assetSubfieldsOf,\n describeAccept,\n readAssetId,\n} from '@elytracms/core/cms-core'\nimport {\n Button,\n Card,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Field,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport {\n appendAssetSelections,\n applyAssetSelection,\n assetIdsOf,\n assetPickForm,\n singleAssetIdOf,\n} from '../assets/asset-selection'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { AssetThumb, FocalCropPreviews, FocalPointPicker, useAssetRecord } from '../assets/asset-thumb'\nimport { uploadBatchProgress } from './asset-picker-core'\nimport type { UploadBatch } from './asset-picker-core'\nimport { assetMetaFieldsOf, pruneMeta } from '../assets/asset-metadata'\nimport { fieldLabel } from './cms-labels'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { useFieldDensity } from './field-density'\nimport { AssetPicker } from './asset-picker'\nimport { FieldControl } from './field-control'\nimport { ObjectItemForm } from './object-array-field'\n\n/**\n * The asset-field family (EC-152 + EC-248 + EC-301) — split from\n * `field-control.tsx` (EC-330): the {@link AssetField} control plus its\n * preview/gallery/options sub-components. Per-use sub-fields and the options\n * dialog's metadata form render through the shared object item form /\n * `FieldControl`, so asset metadata stays schema-driven.\n */\n\n/**\n * The asset field (EC-152 + EC-248). Beyond picking from the library it now shows\n * a focal-point-aware preview, uploads directly from the field (reusing the EC-152\n * upload path — no parallel uploader), and edits the selected image's alt + focal\n * point inline (persisted on the asset document, where delivery reads them, EC-230).\n * With no `<AssetsProvider>` (fixtures/playground) it degrades to the labelled-badge\n * picker — no preview/upload — so it stays usable without a real library.\n */\nexport function 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 source = useCollectionView()\n const assets = useOptionalAssets()\n const density = useFieldDensity()\n const compact = density === 'compact'\n const [open, setOpen] = useState(false)\n const [editing, setEditing] = useState(false)\n const [dragging, setDragging] = useState(false)\n // Names of files a drop/pick rejected against the field's `accept` (EC-301) — shown\n // as a transient inline issue, cleared on the next valid upload. Never a silent drop.\n const [rejected, setRejected] = useState<string[]>([])\n // The active upload BATCH (EC-460): the pre-upload library snapshot + how many\n // files the drop submitted, so the effect below attaches the WHOLE batch (the\n // `upload()` API returns nothing — the library diff is the signal).\n const pendingRef = useRef<UploadBatch | null>(null)\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const many = field.multiple\n const ids = many ? assetIdsOf(value) : []\n const single = !many ? singleAssetIdOf(value) : null\n // EC-301 per-use sub-fields: with `field.fields` the value stores asset ENVELOPES\n // carrying the sub-field siblings (`{ type:'asset', id, caption }`); without them it\n // stays bare ids (self-typed on save). Picks/edits preserve a placement's sub-fields.\n const subFields = field.fields ?? []\n const hasSub = subFields.length > 0\n // How a pick writes a NEW placement: an envelope when the field has sub-fields,\n // else a bare id. The table cell reads the same decision (EC-558).\n const form = assetPickForm(field)\n // The raw `many` entries (envelopes or bare ids), index-aligned with `ids`, so each\n // gallery item can read/write its own sub-fields.\n const entries = many && Array.isArray(value) ? (value as unknown[]) : []\n\n const libraryIds = useMemo(\n () => (assets?.status === 'ready' ? assets.assets.map((a) => a.id) : []),\n [assets],\n )\n // Uploads still running (failed entries are terminal — visible in the queue\n // for retry; they must not keep a batch waiting forever).\n const uploadsInFlight = assets\n ? assets.uploads.filter((u) => u.state.phase !== 'failed').length\n : 0\n\n // Auto-select what an upload FROM this field added once it lands in the\n // library. EC-460: a `many` field attaches EVERY batch arrival as it lands\n // (the batch stays armed until it settles), so dropping N files attaches all\n // N — the old flow disarmed on the FIRST arrival and silently stranded the\n // rest in the library. A `one` field keeps the first-to-land selection.\n useEffect(() => {\n const batch = pendingRef.current\n if (!batch) return\n const progress = uploadBatchProgress(batch, libraryIds, uploadsInFlight)\n if (progress.added.length === 0) {\n if (progress.settled) pendingRef.current = null // everything failed — disarm\n return\n }\n if (many) {\n const next = appendAssetSelections(value, progress.added, form)\n if (next !== value) onChange(next)\n if (progress.settled) pendingRef.current = null\n return\n }\n pendingRef.current = null\n const next = applyAssetSelection(value, progress.added[0]!, false, form)\n if (next !== value) onChange(next)\n // Reacts to the library set growing (an upload landing) and the upload\n // queue draining (a batch settling) — `value`/`onChange` are read from the\n // closure on purpose.\n }, [libraryIds, uploadsInFlight, many, form, onChange, value])\n\n function uploadFiles(files: File[]) {\n if (!assets || files.length === 0) return\n // EC-301: a drag-drop bypasses the file dialog's `accept` filter — re-check each\n // file against the field's `accept` and surface a visible issue for the rejects\n // instead of silently uploading the wrong kind.\n const ok: File[] = []\n const bad: string[] = []\n for (const file of files) {\n if (assetMatchesAccept({ contentType: file.type, filename: file.name }, field.accept)) ok.push(file)\n else bad.push(file.name)\n }\n setRejected(bad)\n if (ok.length === 0) return\n // Remember the library snapshot + the batch size so the effect above can\n // attach every upload of this batch as it lands (the upload API returns nothing).\n pendingRef.current = { before: new Set(libraryIds), expected: ok.length }\n assets.upload(ok)\n }\n\n // Apply a picked asset id through the pick operation the table cell also commits\n // (EC-558), so a placement's per-use sub-fields (EC-301) survive a swap or an append.\n function applyPicked(picked: string | null) {\n const next = applyAssetSelection(value, picked, many, form)\n if (next !== value) onChange(next)\n }\n\n // EC-460: attach a whole settled in-picker upload batch to a `many` field —\n // every id not already attached, in arrival order (envelopes when the field\n // declares per-use sub-fields). Only wired for `many` fields (see the picker).\n function applyPickedMany(picked: string[]) {\n const next = appendAssetSelections(value, picked, form)\n if (next !== value) onChange(next)\n }\n\n const canUpload = !readOnly && assets !== null\n\n // Drag-to-upload onto the field (EC-301): a depth counter keeps the overlay\n // steady as the pointer crosses child element boundaries. `dragenter`/`dragleave`\n // fire per descendant, so toggling on a bare `dragleave` blinks constantly; we\n // only hide the overlay when the counter returns to zero (the pointer has truly\n // left the field). The overlay is pointer-events-none, so it never fires its own\n // enter/leave to confuse the count.\n const dragDepth = useRef(0)\n function onDragEnter(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault() // required so a drop is allowed\n }\n function onDragLeave() {\n if (!canUpload) return\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n uploadFiles(Array.from(e.dataTransfer.files))\n }\n // While the picker is open the field is behind the modal, so any drop is happening\n // INSIDE the dialog — detach the field's drag handlers entirely so a drop there can\n // only be handled by the picker, never double-counted by the field too (EC-301). This\n // is the belt to the picker's stopPropagation braces: the field cannot react at all.\n const dragProps = canUpload && !open ? { onDragEnter, onDragOver, onDragLeave, onDrop } : {}\n\n const selectButton = !readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <ImagePlus /> {translate('common.select')}\n </Button>\n ) : null\n\n return (\n <Stack\n gap=\"2\"\n className={cn(\n 'relative rounded-lg transition-shadow',\n dragging && 'ring-2 ring-primary ring-offset-2 ring-offset-background',\n )}\n {...dragProps}\n >\n {many && hasSub ? (\n // Gallery with per-use sub-fields (EC-301): a card per placement — thumbnail +\n // its own sub-field form — reusing the object/repeater item rendering.\n <Stack gap=\"2\">\n {entries.length > 0 ? (\n <Stack gap=\"2\">\n {entries.map((entry, i) => {\n const aid = readAssetId(entry)\n if (!aid) return null\n return (\n <AssetGalleryItem\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n fields={subFields}\n value={assetSubfieldsOf(entry)}\n readOnly={readOnly}\n idPrefix={`asset-${i}`}\n onChangeSubs={(next) => {\n const nextEntries = entries.slice()\n nextEntries[i] = assetNode(aid, next)\n onChange(nextEntries)\n }}\n onRemove={readOnly ? undefined : () => onChange(entries.filter((_, j) => j !== i))}\n />\n )\n })}\n </Stack>\n ) : (\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : many ? (\n // Gallery: a grid of focal-aware previews + the single Select entry.\n <Stack gap=\"2\">\n {ids.length > 0 ? (\n <div\n className={cn(\n 'grid gap-2',\n compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4',\n )}\n >\n {ids.map((aid, i) => (\n <AssetChip\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n onRemove={readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid))}\n />\n ))}\n </div>\n ) : (\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : single && hasSub ? (\n // With per-use sub-fields the asset + its fields read as ONE grouped unit\n // (like an object), so it's clear they belong together (EC-301 feedback).\n <Card className=\"border-l-2 border-l-border p-3\">\n <Stack gap=\"3\">\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, many, form))}\n />\n <div className=\"border-t border-border/60 pt-3\">\n <ObjectItemForm\n fields={subFields}\n value={assetSubfieldsOf(value)}\n readOnly={readOnly}\n idPrefix=\"asset-sub\"\n onChange={(next) => onChange(assetNode(single, next))}\n />\n </div>\n </Stack>\n </Card>\n ) : single ? (\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, many, form))}\n />\n ) : (\n <Stack gap=\"2\">\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n )}\n\n {/* Image options (focal point + metadata) for a single selected asset, in a\n dialog rather than an inline disclosure (EC-301). Shared across every use. */}\n {!many && single && assets ? (\n <AssetOptionsDialog\n assetId={single}\n readOnly={readOnly}\n open={editing}\n onOpenChange={setEditing}\n />\n ) : null}\n\n <AssetPicker\n value={single}\n open={open}\n onOpenChange={setOpen}\n accept={assetAcceptAttr(field.accept)}\n onChange={applyPicked}\n multiple={many}\n {...(many ? { onChangeMany: applyPickedMany } : {})}\n />\n\n {rejected.length > 0 ? (\n <Row gap=\"2\" align=\"center\">\n <AlertTriangle className=\"size-4 shrink-0 text-warning\" />\n <Text size=\"xs\" tone=\"muted\">\n {rejected.length === 1\n ? translate('cms.asset.rejected.one', {\n name: rejected[0]!,\n types: describeAccept(field.accept),\n })\n : translate('cms.asset.rejected.other', {\n count: rejected.length,\n types: describeAccept(field.accept),\n })}\n </Text>\n </Row>\n ) : null}\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-4 text-primary\" />\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.dropToUpload')}\n </Text>\n </Row>\n </div>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * Empty-state placeholder shown when an asset field has no selection (EC-301).\n *\n * EC-395: this was the one asset-field affordance that ignored `readOnly` — every\n * sibling control (`selectButton`, the per-item remove/change actions) already\n * degraded, but the empty-state tile stayed a clickable `<button>` that opened the\n * picker regardless. A locked, empty asset field must be as inert as a filled one:\n * read-only renders a plain (non-interactive, non-focusable) `div` with the same\n * frame — no `onClick`, no hover affordance — instead of a `<button>`.\n */\nfunction EmptyAssetState({\n compact,\n canUpload,\n readOnly,\n onSelect,\n}: {\n compact: boolean\n canUpload: boolean\n readOnly: boolean\n onSelect: () => void\n}) {\n const frame = cn(\n 'grid w-full place-items-center gap-1 rounded-lg border border-dashed border-border bg-muted/30 text-center text-muted-foreground',\n compact ? 'aspect-video' : 'aspect-[5/2]',\n )\n if (readOnly) {\n return <div className={frame}>{emptyAssetStateBody(canUpload)}</div>\n }\n return (\n <button type=\"button\" onClick={onSelect} className={cn(frame, 'transition-colors hover:bg-muted/60')}>\n {emptyAssetStateBody(canUpload)}\n </button>\n )\n}\n\nfunction emptyAssetStateBody(canUpload: boolean) {\n return (\n <>\n <ImagePlus className=\"size-6\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {canUpload ? translate('cms.asset.selectOrDrop') : translate('cms.asset.selectAsset')}\n </Text>\n </>\n )\n}\n\n/** A large focal-aware preview of the selected asset (EC-301). */\nfunction AssetPreview({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n return <AssetThumb record={record} className=\"aspect-[4/3] w-full\" />\n}\n\n/** Filename + dimensions/type for the selected asset — the id is never shown (EC-301). */\nfunction AssetInfo({ assetId, fallbackTitle }: { assetId: string; fallbackTitle?: string }) {\n const record = useAssetRecord(assetId)\n const name = record?.title ?? record?.filename ?? fallbackTitle ?? assetId\n const dims = record?.width && record?.height ? `${record.width}×${record.height}` : null\n const kind = record?.contentType?.split('/')[1]?.toUpperCase()\n const meta = [dims, kind].filter(Boolean).join(' · ')\n return (\n <Stack gap=\"1\">\n <Text size=\"sm\" className=\"truncate font-medium\" title={name}>\n {name}\n </Text>\n {meta ? (\n <Text size=\"xs\" tone=\"subtle\">\n {meta}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The selected image's alt text shown in the field (EC-301) — what an editor most\n * wants at a glance; edited in the options dialog. Reads `meta.alt` with the legacy\n * `alt` column as fallback, matching delivery.\n */\nfunction AssetAltLine({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n const metaAlt = typeof record?.meta?.alt === 'string' ? record.meta.alt : undefined\n const alt = metaAlt ?? record?.alt\n return (\n <Row gap=\"1\" align=\"baseline\" className=\"min-w-0\">\n <Text size=\"xs\" tone=\"subtle\" className=\"shrink-0\">\n {translate('cms.asset.altLabel')}\n </Text>\n <Text\n size=\"xs\"\n tone={alt ? 'muted' : 'subtle'}\n className={cn('min-w-0 truncate', !alt && 'italic')}\n title={alt ?? undefined}\n >\n {alt || translate('cms.asset.notSet')}\n </Text>\n </Row>\n )\n}\n\n/** A single asset reference: focal-aware thumbnail + title, with an optional remove. */\nfunction AssetChip({\n assetId,\n collectionId,\n onRemove,\n}: {\n assetId: string\n collectionId: string\n onRemove?: () => void\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <div className=\"group relative overflow-hidden rounded-lg border border-border bg-muted/40\">\n <AssetThumb record={record} alt={title} className=\"aspect-square w-full rounded-none border-0\" />\n <Row gap=\"1\" align=\"center\" className=\"px-2 py-1\">\n <Text size=\"xs\" tone=\"subtle\" className=\"min-w-0 flex-1 truncate\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <button\n type=\"button\"\n aria-label={translate('common.remove')}\n onClick={onRemove}\n className=\"text-muted-foreground hover:text-destructive\"\n >\n <X className=\"size-3.5\" />\n </button>\n ) : null}\n </Row>\n </div>\n )\n}\n\n/**\n * The selected single asset's preview row (EC-301): large focal-aware preview + info\n * column (filename/dimensions, Select/Edit/Remove, alt). Extracted so it renders either\n * bare (a plain asset field) or inside the grouping card when the field carries per-use\n * sub-fields — the asset + its fields then read as one unit.\n */\nfunction AssetSinglePreview({\n assetId,\n compact,\n readOnly,\n hasLibrary,\n fallbackTitle,\n selectButton,\n onEdit,\n onRemove,\n}: {\n assetId: string\n compact: boolean\n readOnly: boolean\n hasLibrary: boolean\n fallbackTitle?: string\n selectButton: React.ReactNode\n onEdit: () => void\n onRemove: () => void\n}) {\n return (\n <div className={compact ? 'flex flex-col gap-3' : 'flex gap-4'}>\n <div className={compact ? 'w-full' : 'w-1/2 shrink-0'}>\n <AssetPreview assetId={assetId} />\n </div>\n <Stack gap=\"2\" className={cn('min-w-0', !compact && 'flex-1')}>\n <AssetInfo assetId={assetId} fallbackTitle={fallbackTitle} />\n {!readOnly ? (\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n {hasLibrary ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onEdit}>\n <SlidersHorizontal /> {translate('common.edit')}\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"sm\" onClick={onRemove}>\n <Trash2 /> {translate('common.remove')}\n </Button>\n </Row>\n ) : null}\n <AssetAltLine assetId={assetId} />\n </Stack>\n </div>\n )\n}\n\n/**\n * One gallery placement when a `many` asset field declares per-use sub-fields (EC-301):\n * a thumbnail + filename + remove header over the placement's own sub-field form,\n * modelled on a repeater item card so a gallery's placements read as grouped units.\n */\nfunction AssetGalleryItem({\n assetId,\n collectionId,\n fields,\n value,\n onChangeSubs,\n onRemove,\n readOnly,\n idPrefix,\n}: {\n assetId: string\n collectionId: string\n fields: readonly FieldDef[]\n value: Record<string, unknown>\n onChangeSubs: (value: Record<string, unknown>) => void\n onRemove?: () => void\n readOnly: boolean\n idPrefix: string\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <Card className=\"border-l-2 border-l-border p-0\">\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-2 py-1.5\">\n <AssetThumb record={record} alt={title} className=\"size-10 shrink-0\" />\n <Text size=\"sm\" className=\"min-w-0 flex-1 truncate font-medium\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <Button variant=\"ghost\" size=\"sm\" aria-label={translate('common.remove')} onClick={onRemove}>\n <Trash2 className=\"size-3.5\" />\n </Button>\n ) : null}\n </Row>\n <div className=\"px-3 pb-3\">\n <ObjectItemForm\n fields={fields}\n value={value}\n onChange={onChangeSubs}\n readOnly={readOnly}\n idPrefix={idPrefix}\n />\n </div>\n </Card>\n )\n}\n\n/**\n * The image options dialog (EC-301): the focal point (engine-special visual\n * control) + the asset's **schema-driven** metadata, rendered from the\n * `kind:'asset'` collection's `fields` through the same generic field engine\n * documents use — so a project declares asset metadata (alt, copyright, credit, …)\n * in code, not hardcoded here. Edits persist on the asset, shared across every use.\n * Metadata is buffered locally and committed once on close (one write); the focal\n * point commits immediately (it drives a live visual). No-ops without a library.\n */\nfunction AssetOptionsDialog({\n assetId,\n readOnly,\n open,\n onOpenChange,\n}: {\n assetId: string\n readOnly: boolean\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const source = useCollectionView()\n const assets = useOptionalAssets()\n const record = useAssetRecord(assetId)\n const metaFields = useMemo(() => {\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const collection = source.collections.find((c) => c.id === assetCollectionId)\n return collection ? assetMetaFieldsOf(collection) : []\n }, [source])\n\n // Local edit buffers, seeded once per open from the record (so an external edit\n // still wins between opens). Metadata is non-localized in v1 (the asset\n // resolution path is locale-agnostic) — localized asset metadata is a follow-up.\n const [meta, setMeta] = useState<Record<string, unknown>>({})\n const [focal, setFocal] = useState<{ x: number; y: number } | undefined>(undefined)\n const dirty = useRef(false)\n const seeded = useRef(false)\n useEffect(() => {\n if (!open) {\n seeded.current = false\n return\n }\n if (seeded.current || !record) return\n // Seed from the meta bag, but lift the legacy `alt` column in when meta lacks\n // it (EC-301 transition): a not-yet-migrated asset still shows its alt, and\n // saving writes it into meta — a lazy migration-on-edit, matching delivery's\n // `meta.alt ?? record.alt` read fallback.\n const seedMeta = { ...(record.alt !== undefined ? { alt: record.alt } : {}), ...(record.meta ?? {}) }\n setMeta(seedMeta)\n setFocal(record.focalPoint)\n dirty.current = false\n seeded.current = true\n }, [open, record])\n\n function handleOpenChange(next: boolean) {\n // Commit the buffered metadata once, on close (one write, not per keystroke).\n if (!next && !readOnly && assets && dirty.current) {\n void assets.updateMetadata(assetId, { meta: pruneMeta(meta) })\n dirty.current = false\n }\n onOpenChange(next)\n }\n\n function setField(name: string, value: unknown) {\n dirty.current = true\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n // The focal point drives a live crop preview → commit immediately (optimistic\n // marker), not deferred to close.\n function commitFocal(point: { x: number; y: number } | null) {\n if (readOnly || !assets) return\n setFocal(point ?? undefined)\n void assets.updateMetadata(assetId, { focalPoint: point })\n }\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.imageOptions.title')}</DialogTitle>\n <DialogDescription>\n {translate('cms.asset.imageOptions.description')}\n </DialogDescription>\n </DialogHeader>\n <Stack gap=\"3\">\n <Stack gap=\"2\">\n <Row align=\"center\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal && !readOnly ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n <FocalPointPicker\n record={record}\n point={focal}\n onChange={commitFocal}\n onPreview={setFocal}\n disabled={readOnly}\n />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n {metaFields.map((f) => (\n <Field key={f.name} label={fieldLabel(f)} hint={localizedLabel(f.form?.description) || undefined}>\n <FieldControl\n id={`asset-meta-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={readOnly}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n No editable metadata fields are declared on the asset collection. Add fields (e.g.\n <Code tone=\"muted\"> alt</Code>, <Code tone=\"muted\">copyright</Code>) to it in code.\n </Text>\n ) : null}\n </Stack>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
1
+ {"version":3,"file":"asset-field.js","sourceRoot":"","sources":["../../../src/components/cms/asset-field.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ImagePlus, RotateCcw, SlidersHorizontal, Trash2, Upload, X } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { AssetFieldDef, FieldDef } from '@elytracms/core/cms-core'\nimport { assetNode, assetSubfieldsOf, readAssetId } from '@elytracms/core/cms-core'\nimport {\n Button,\n Card,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Field,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport {\n appendAssetSelections,\n applyAssetSelection,\n assetIdsOf,\n assetPickForm,\n singleAssetIdOf,\n} from '../assets/asset-selection'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { AssetThumb, FocalCropPreviews, FocalPointPicker, useAssetRecord } from '../assets/asset-thumb'\nimport { assetPickerBinding, splitByAccept, uploadBatchProgress } from './asset-picker-core'\nimport type { UploadBatch } from './asset-picker-core'\nimport { assetMetaFieldsOf, pruneMeta } from '../assets/asset-metadata'\nimport { fieldLabel } from './cms-labels'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { useFieldDensity } from './field-density'\nimport { AssetPicker, AssetRejectedNotice } from './asset-picker'\nimport { FieldControl } from './field-control'\nimport { ObjectItemForm } from './object-array-field'\n\n/**\n * The asset-field family (EC-152 + EC-248 + EC-301) — split from\n * `field-control.tsx` (EC-330): the {@link AssetField} control plus its\n * preview/gallery/options sub-components. Per-use sub-fields and the options\n * dialog's metadata form render through the shared object item form /\n * `FieldControl`, so asset metadata stays schema-driven.\n */\n\n/**\n * The asset field (EC-152 + EC-248). Beyond picking from the library it now shows\n * a focal-point-aware preview, uploads directly from the field (reusing the EC-152\n * upload path — no parallel uploader), and edits the selected image's alt + focal\n * point inline (persisted on the asset document, where delivery reads them, EC-230).\n * With no `<AssetsProvider>` (a fixture-only session — every project, the playground\n * included, mounts one, `studio-project-screen.tsx`) it degrades to the labelled-badge\n * picker — no preview/upload — so it stays usable without a real library.\n */\nexport function 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 source = useCollectionView()\n const assets = useOptionalAssets()\n const density = useFieldDensity()\n const compact = density === 'compact'\n const [open, setOpen] = useState(false)\n const [editing, setEditing] = useState(false)\n const [dragging, setDragging] = useState(false)\n // Names of files a drop/pick rejected against the field's `accept` (EC-301) — shown\n // as a transient inline issue, cleared on the next valid upload. Never a silent drop.\n const [rejected, setRejected] = useState<string[]>([])\n // The active upload BATCH (EC-460): exactly the upload ids this field's drop\n // started (EC-588 A7), so the effect below attaches the WHOLE batch and nothing\n // another tab or a sibling field uploaded meanwhile.\n const pendingRef = useRef<UploadBatch | null>(null)\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const many = field.multiple\n const ids = many ? assetIdsOf(value) : []\n const single = !many ? singleAssetIdOf(value) : null\n // EC-301 per-use sub-fields: with `field.fields` the value stores asset ENVELOPES\n // carrying the sub-field siblings (`{ type:'asset', id, caption }`); without them it\n // stays bare ids (self-typed on save). Picks/edits preserve a placement's sub-fields.\n const subFields = field.fields ?? []\n const hasSub = subFields.length > 0\n // How a pick writes a NEW placement: an envelope when the field has sub-fields,\n // else a bare id. The table cell reads the same decision (EC-558).\n const form = assetPickForm(field)\n // The raw `many` entries (envelopes or bare ids), index-aligned with `ids`, so each\n // gallery item can read/write its own sub-fields.\n const entries = many && Array.isArray(value) ? (value as unknown[]) : []\n\n // Auto-select what an upload FROM this field added once it lands in the\n // library. EC-460: a `many` field attaches EVERY batch arrival as it lands\n // (the batch stays armed until it settles), so dropping N files attaches all\n // N — the old flow disarmed on the FIRST arrival and silently stranded the\n // rest in the library. A `one` field keeps the first-to-land selection.\n useEffect(() => {\n const batch = pendingRef.current\n if (!batch || !assets) return\n const progress = uploadBatchProgress(batch, assets, field.accept)\n if (progress.added.length === 0) {\n if (progress.settled) pendingRef.current = null // everything failed — disarm\n return\n }\n if (many) {\n const next = appendAssetSelections(value, progress.added, form)\n if (next !== value) onChange(next)\n if (progress.settled) pendingRef.current = null\n return\n }\n pendingRef.current = null\n const next = applyAssetSelection(value, progress.added[0]!, false, form)\n if (next !== value) onChange(next)\n // Reacts to the assets store changing (an upload landing in the library, the\n // queue draining) — `value`/`onChange` are read from the closure on purpose.\n }, [assets, field.accept, many, form, onChange, value])\n\n function uploadFiles(files: File[]) {\n if (!assets || files.length === 0) return\n // EC-301 → EC-588: the drop rule the picker applies too — a drag-drop bypasses\n // the file dialog's `accept` filter, so re-check each file and name the rejects in\n // a visible notice instead of silently uploading the wrong kind.\n const { accepted, rejected: refused } = splitByAccept(files, field.accept)\n setRejected(refused)\n if (accepted.length === 0) return\n pendingRef.current = { uploadIds: assets.upload(accepted) }\n }\n\n // The picker is configured from the field — the SAME binding the table cell uses\n // (EC-570 #1 → EC-588): `accept`, `multiple`, and a pick through the carry rule\n // (EC-558) so a placement's per-use sub-fields (EC-301) survive a swap or an append.\n const pickerBinding = assetPickerBinding(field, value, (next) => {\n if (next !== value) onChange(next)\n })\n\n const canUpload = !readOnly && assets !== null\n\n // Drag-to-upload onto the field (EC-301): a depth counter keeps the overlay\n // steady as the pointer crosses child element boundaries. `dragenter`/`dragleave`\n // fire per descendant, so toggling on a bare `dragleave` blinks constantly; we\n // only hide the overlay when the counter returns to zero (the pointer has truly\n // left the field). The overlay is pointer-events-none, so it never fires its own\n // enter/leave to confuse the count.\n const dragDepth = useRef(0)\n function onDragEnter(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault() // required so a drop is allowed\n }\n function onDragLeave() {\n if (!canUpload) return\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n if (!canUpload) return\n e.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n uploadFiles(Array.from(e.dataTransfer.files))\n }\n // While the picker is open the field is behind the modal, so any drop is happening\n // INSIDE the dialog — detach the field's drag handlers entirely so a drop there can\n // only be handled by the picker, never double-counted by the field too (EC-301). This\n // is the belt to the picker's stopPropagation braces: the field cannot react at all.\n const dragProps = canUpload && !open ? { onDragEnter, onDragOver, onDragLeave, onDrop } : {}\n\n const selectButton = !readOnly ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setOpen(true)}>\n <ImagePlus /> {translate('common.select')}\n </Button>\n ) : null\n\n return (\n <Stack\n gap=\"2\"\n className={cn(\n 'relative rounded-lg transition-shadow',\n dragging && 'ring-2 ring-primary ring-offset-2 ring-offset-background',\n )}\n {...dragProps}\n >\n {many && hasSub ? (\n // Gallery with per-use sub-fields (EC-301): a card per placement — thumbnail +\n // its own sub-field form — reusing the object/repeater item rendering.\n <Stack gap=\"2\">\n {entries.length > 0 ? (\n <Stack gap=\"2\">\n {entries.map((entry, i) => {\n const aid = readAssetId(entry)\n if (!aid) return null\n return (\n <AssetGalleryItem\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n fields={subFields}\n value={assetSubfieldsOf(entry)}\n readOnly={readOnly}\n idPrefix={`asset-${i}`}\n onChangeSubs={(next) => {\n const nextEntries = entries.slice()\n nextEntries[i] = assetNode(aid, next)\n onChange(nextEntries)\n }}\n onRemove={readOnly ? undefined : () => onChange(entries.filter((_, j) => j !== i))}\n />\n )\n })}\n </Stack>\n ) : (\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : many ? (\n // Gallery: a grid of focal-aware previews + the single Select entry.\n <Stack gap=\"2\">\n {ids.length > 0 ? (\n <div\n className={cn(\n 'grid gap-2',\n compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4',\n )}\n >\n {ids.map((aid, i) => (\n <AssetChip\n key={`${aid}-${i}`}\n assetId={aid}\n collectionId={assetCollectionId}\n onRemove={readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid))}\n />\n ))}\n </div>\n ) : (\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n )}\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n ) : single && hasSub ? (\n // With per-use sub-fields the asset + its fields read as ONE grouped unit\n // (like an object), so it's clear they belong together (EC-301 feedback).\n <Card className=\"border-l-2 border-l-border p-3\">\n <Stack gap=\"3\">\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, many, form))}\n />\n <div className=\"border-t border-border/60 pt-3\">\n <ObjectItemForm\n fields={subFields}\n value={assetSubfieldsOf(value)}\n readOnly={readOnly}\n idPrefix=\"asset-sub\"\n onChange={(next) => onChange(assetNode(single, next))}\n />\n </div>\n </Stack>\n </Card>\n ) : single ? (\n <AssetSinglePreview\n assetId={single}\n compact={compact}\n readOnly={readOnly}\n hasLibrary={assets !== null}\n fallbackTitle={source.titleOf({ collection: assetCollectionId, id: single })}\n selectButton={selectButton}\n onEdit={() => setEditing(true)}\n onRemove={() => onChange(applyAssetSelection(value, null, many, form))}\n />\n ) : (\n <Stack gap=\"2\">\n <EmptyAssetState\n compact={compact}\n canUpload={canUpload}\n readOnly={readOnly}\n onSelect={() => setOpen(true)}\n />\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n </Row>\n </Stack>\n )}\n\n {/* Image options (focal point + metadata) for a single selected asset, in a\n dialog rather than an inline disclosure (EC-301). Shared across every use. */}\n {!many && single && assets ? (\n <AssetOptionsDialog\n assetId={single}\n readOnly={readOnly}\n open={editing}\n onOpenChange={setEditing}\n />\n ) : null}\n\n <AssetPicker open={open} onOpenChange={setOpen} {...pickerBinding} />\n\n <AssetRejectedNotice rejected={rejected} accept={field.accept} />\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-4 text-primary\" />\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.dropToUpload')}\n </Text>\n </Row>\n </div>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * Empty-state placeholder shown when an asset field has no selection (EC-301).\n *\n * EC-395: this was the one asset-field affordance that ignored `readOnly` — every\n * sibling control (`selectButton`, the per-item remove/change actions) already\n * degraded, but the empty-state tile stayed a clickable `<button>` that opened the\n * picker regardless. A locked, empty asset field must be as inert as a filled one:\n * read-only renders a plain (non-interactive, non-focusable) `div` with the same\n * frame — no `onClick`, no hover affordance — instead of a `<button>`.\n */\nfunction EmptyAssetState({\n compact,\n canUpload,\n readOnly,\n onSelect,\n}: {\n compact: boolean\n canUpload: boolean\n readOnly: boolean\n onSelect: () => void\n}) {\n const frame = cn(\n 'grid w-full place-items-center gap-1 rounded-lg border border-dashed border-border bg-muted/30 text-center text-muted-foreground',\n compact ? 'aspect-video' : 'aspect-[5/2]',\n )\n if (readOnly) {\n return <div className={frame}>{emptyAssetStateBody(canUpload)}</div>\n }\n return (\n <button type=\"button\" onClick={onSelect} className={cn(frame, 'transition-colors hover:bg-muted/60')}>\n {emptyAssetStateBody(canUpload)}\n </button>\n )\n}\n\nfunction emptyAssetStateBody(canUpload: boolean) {\n return (\n <>\n <ImagePlus className=\"size-6\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {canUpload ? translate('cms.asset.selectOrDrop') : translate('cms.asset.selectAsset')}\n </Text>\n </>\n )\n}\n\n/** A large focal-aware preview of the selected asset (EC-301). */\nfunction AssetPreview({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n return <AssetThumb record={record} className=\"aspect-[4/3] w-full\" />\n}\n\n/** Filename + dimensions/type for the selected asset — the id is never shown (EC-301). */\nfunction AssetInfo({ assetId, fallbackTitle }: { assetId: string; fallbackTitle?: string }) {\n const record = useAssetRecord(assetId)\n const name = record?.title ?? record?.filename ?? fallbackTitle ?? assetId\n const dims = record?.width && record?.height ? `${record.width}×${record.height}` : null\n const kind = record?.contentType?.split('/')[1]?.toUpperCase()\n const meta = [dims, kind].filter(Boolean).join(' · ')\n return (\n <Stack gap=\"1\">\n <Text size=\"sm\" className=\"truncate font-medium\" title={name}>\n {name}\n </Text>\n {meta ? (\n <Text size=\"xs\" tone=\"subtle\">\n {meta}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The selected image's alt text shown in the field (EC-301) — what an editor most\n * wants at a glance; edited in the options dialog. Reads `meta.alt` with the legacy\n * `alt` column as fallback, matching delivery.\n */\nfunction AssetAltLine({ assetId }: { assetId: string }) {\n const record = useAssetRecord(assetId)\n const metaAlt = typeof record?.meta?.alt === 'string' ? record.meta.alt : undefined\n const alt = metaAlt ?? record?.alt\n return (\n <Row gap=\"1\" align=\"baseline\" className=\"min-w-0\">\n <Text size=\"xs\" tone=\"subtle\" className=\"shrink-0\">\n {translate('cms.asset.altLabel')}\n </Text>\n <Text\n size=\"xs\"\n tone={alt ? 'muted' : 'subtle'}\n className={cn('min-w-0 truncate', !alt && 'italic')}\n title={alt ?? undefined}\n >\n {alt || translate('cms.asset.notSet')}\n </Text>\n </Row>\n )\n}\n\n/** A single asset reference: focal-aware thumbnail + title, with an optional remove. */\nfunction AssetChip({\n assetId,\n collectionId,\n onRemove,\n}: {\n assetId: string\n collectionId: string\n onRemove?: () => void\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <div className=\"group relative overflow-hidden rounded-lg border border-border bg-muted/40\">\n <AssetThumb record={record} alt={title} className=\"aspect-square w-full rounded-none border-0\" />\n <Row gap=\"1\" align=\"center\" className=\"px-2 py-1\">\n <Text size=\"xs\" tone=\"subtle\" className=\"min-w-0 flex-1 truncate\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <button\n type=\"button\"\n aria-label={translate('common.remove')}\n onClick={onRemove}\n className=\"text-muted-foreground hover:text-destructive\"\n >\n <X className=\"size-3.5\" />\n </button>\n ) : null}\n </Row>\n </div>\n )\n}\n\n/**\n * The selected single asset's preview row (EC-301): large focal-aware preview + info\n * column (filename/dimensions, Select/Edit/Remove, alt). Extracted so it renders either\n * bare (a plain asset field) or inside the grouping card when the field carries per-use\n * sub-fields — the asset + its fields then read as one unit.\n */\nfunction AssetSinglePreview({\n assetId,\n compact,\n readOnly,\n hasLibrary,\n fallbackTitle,\n selectButton,\n onEdit,\n onRemove,\n}: {\n assetId: string\n compact: boolean\n readOnly: boolean\n hasLibrary: boolean\n fallbackTitle?: string\n selectButton: React.ReactNode\n onEdit: () => void\n onRemove: () => void\n}) {\n return (\n <div className={compact ? 'flex flex-col gap-3' : 'flex gap-4'}>\n <div className={compact ? 'w-full' : 'w-1/2 shrink-0'}>\n <AssetPreview assetId={assetId} />\n </div>\n <Stack gap=\"2\" className={cn('min-w-0', !compact && 'flex-1')}>\n <AssetInfo assetId={assetId} fallbackTitle={fallbackTitle} />\n {!readOnly ? (\n <Row gap=\"2\" wrap align=\"center\">\n {selectButton}\n {hasLibrary ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onEdit}>\n <SlidersHorizontal /> {translate('common.edit')}\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"sm\" onClick={onRemove}>\n <Trash2 /> {translate('common.remove')}\n </Button>\n </Row>\n ) : null}\n <AssetAltLine assetId={assetId} />\n </Stack>\n </div>\n )\n}\n\n/**\n * One gallery placement when a `many` asset field declares per-use sub-fields (EC-301):\n * a thumbnail + filename + remove header over the placement's own sub-field form,\n * modelled on a repeater item card so a gallery's placements read as grouped units.\n */\nfunction AssetGalleryItem({\n assetId,\n collectionId,\n fields,\n value,\n onChangeSubs,\n onRemove,\n readOnly,\n idPrefix,\n}: {\n assetId: string\n collectionId: string\n fields: readonly FieldDef[]\n value: Record<string, unknown>\n onChangeSubs: (value: Record<string, unknown>) => void\n onRemove?: () => void\n readOnly: boolean\n idPrefix: string\n}) {\n const source = useCollectionView()\n const record = useAssetRecord(assetId)\n const title = source.titleOf({ collection: collectionId, id: assetId })\n return (\n <Card className=\"border-l-2 border-l-border p-0\">\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-2 py-1.5\">\n <AssetThumb record={record} alt={title} className=\"size-10 shrink-0\" />\n <Text size=\"sm\" className=\"min-w-0 flex-1 truncate font-medium\" title={title}>\n {title}\n </Text>\n {onRemove ? (\n <Button variant=\"ghost\" size=\"sm\" aria-label={translate('common.remove')} onClick={onRemove}>\n <Trash2 className=\"size-3.5\" />\n </Button>\n ) : null}\n </Row>\n <div className=\"px-3 pb-3\">\n <ObjectItemForm\n fields={fields}\n value={value}\n onChange={onChangeSubs}\n readOnly={readOnly}\n idPrefix={idPrefix}\n />\n </div>\n </Card>\n )\n}\n\n/**\n * The image options dialog (EC-301): the focal point (engine-special visual\n * control) + the asset's **schema-driven** metadata, rendered from the\n * `kind:'asset'` collection's `fields` through the same generic field engine\n * documents use — so a project declares asset metadata (alt, copyright, credit, …)\n * in code, not hardcoded here. Edits persist on the asset, shared across every use.\n * Metadata is buffered locally and committed once on close (one write); the focal\n * point commits immediately (it drives a live visual). No-ops without a library.\n */\nfunction AssetOptionsDialog({\n assetId,\n readOnly,\n open,\n onOpenChange,\n}: {\n assetId: string\n readOnly: boolean\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const source = useCollectionView()\n const assets = useOptionalAssets()\n const record = useAssetRecord(assetId)\n const metaFields = useMemo(() => {\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const collection = source.collections.find((c) => c.id === assetCollectionId)\n return collection ? assetMetaFieldsOf(collection) : []\n }, [source])\n\n // Local edit buffers, seeded once per open from the record (so an external edit\n // still wins between opens). Metadata is non-localized in v1 (the asset\n // resolution path is locale-agnostic) — localized asset metadata is a follow-up.\n const [meta, setMeta] = useState<Record<string, unknown>>({})\n const [focal, setFocal] = useState<{ x: number; y: number } | undefined>(undefined)\n const dirty = useRef(false)\n const seeded = useRef(false)\n useEffect(() => {\n if (!open) {\n seeded.current = false\n return\n }\n if (seeded.current || !record) return\n // Seed from the meta bag, but lift the legacy `alt` column in when meta lacks\n // it (EC-301 transition): a not-yet-migrated asset still shows its alt, and\n // saving writes it into meta — a lazy migration-on-edit, matching delivery's\n // `meta.alt ?? record.alt` read fallback.\n const seedMeta = { ...(record.alt !== undefined ? { alt: record.alt } : {}), ...(record.meta ?? {}) }\n setMeta(seedMeta)\n setFocal(record.focalPoint)\n dirty.current = false\n seeded.current = true\n }, [open, record])\n\n function handleOpenChange(next: boolean) {\n // Commit the buffered metadata once, on close (one write, not per keystroke).\n if (!next && !readOnly && assets && dirty.current) {\n void assets.updateMetadata(assetId, { meta: pruneMeta(meta) })\n dirty.current = false\n }\n onOpenChange(next)\n }\n\n function setField(name: string, value: unknown) {\n dirty.current = true\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n // The focal point drives a live crop preview → commit immediately (optimistic\n // marker), not deferred to close.\n function commitFocal(point: { x: number; y: number } | null) {\n if (readOnly || !assets) return\n setFocal(point ?? undefined)\n void assets.updateMetadata(assetId, { focalPoint: point })\n }\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.imageOptions.title')}</DialogTitle>\n <DialogDescription>\n {translate('cms.asset.imageOptions.description')}\n </DialogDescription>\n </DialogHeader>\n <Stack gap=\"3\">\n <Stack gap=\"2\">\n <Row align=\"center\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal && !readOnly ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n <FocalPointPicker\n record={record}\n point={focal}\n onChange={commitFocal}\n onPreview={setFocal}\n disabled={readOnly}\n />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n {metaFields.map((f) => (\n <Field key={f.name} label={fieldLabel(f)} hint={localizedLabel(f.form?.description) || undefined}>\n <FieldControl\n id={`asset-meta-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={readOnly}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n No editable metadata fields are declared on the asset collection. Add fields (e.g.\n <Code tone=\"muted\"> alt</Code>, <Code tone=\"muted\">copyright</Code>) to it in code.\n </Text>\n ) : null}\n </Stack>\n </DialogContent>\n </Dialog>\n )\n}\n"]}