@elytracms/studio 0.0.90 → 0.0.91

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 (159) hide show
  1. package/convex/asset-revalidate.test.ts +14 -1
  2. package/convex/cms.ts +3 -4
  3. package/convex/document-revalidate.test.ts +14 -1
  4. package/convex/redirect-revalidate.test.ts +14 -1
  5. package/dist/components/assets/asset-selection.d.ts +8 -9
  6. package/dist/components/assets/asset-selection.js +26 -16
  7. package/dist/components/assets/asset-selection.js.map +1 -1
  8. package/dist/components/builder/blocks-model.d.ts +7 -5
  9. package/dist/components/builder/blocks-model.js +11 -9
  10. package/dist/components/builder/blocks-model.js.map +1 -1
  11. package/dist/components/builder/blocks-panel.js +1 -1
  12. package/dist/components/builder/blocks-panel.js.map +1 -1
  13. package/dist/components/builder/builder-store.d.ts +11 -3
  14. package/dist/components/builder/builder-store.js +12 -5
  15. package/dist/components/builder/builder-store.js.map +1 -1
  16. package/dist/components/builder/canvas-dnd.d.ts +7 -1
  17. package/dist/components/builder/canvas-dnd.js +6 -3
  18. package/dist/components/builder/canvas-dnd.js.map +1 -1
  19. package/dist/components/builder/canvas-render-context.d.ts +13 -1
  20. package/dist/components/builder/canvas-render-context.js +28 -18
  21. package/dist/components/builder/canvas-render-context.js.map +1 -1
  22. package/dist/components/builder/canvas.js +1 -1
  23. package/dist/components/builder/canvas.js.map +1 -1
  24. package/dist/components/builder/clipboard-actions.d.ts +0 -6
  25. package/dist/components/builder/clipboard-actions.js +9 -4
  26. package/dist/components/builder/clipboard-actions.js.map +1 -1
  27. package/dist/components/builder/composition-field-editor.d.ts +22 -0
  28. package/dist/components/builder/composition-field-editor.js +47 -8
  29. package/dist/components/builder/composition-field-editor.js.map +1 -1
  30. package/dist/components/builder/inline-edit.d.ts +7 -4
  31. package/dist/components/builder/inline-edit.js +11 -8
  32. package/dist/components/builder/inline-edit.js.map +1 -1
  33. package/dist/components/builder/insert-context.js +9 -5
  34. package/dist/components/builder/insert-context.js.map +1 -1
  35. package/dist/components/builder/layout-inspector.d.ts +2 -2
  36. package/dist/components/builder/layout-inspector.js +8 -6
  37. package/dist/components/builder/layout-inspector.js.map +1 -1
  38. package/dist/components/builder/node-factory.d.ts +31 -14
  39. package/dist/components/builder/node-factory.js +73 -93
  40. package/dist/components/builder/node-factory.js.map +1 -1
  41. package/dist/components/builder/page-tree.d.ts +3 -1
  42. package/dist/components/builder/page-tree.js +6 -4
  43. package/dist/components/builder/page-tree.js.map +1 -1
  44. package/dist/components/builder/property-inspector.js +2 -1
  45. package/dist/components/builder/property-inspector.js.map +1 -1
  46. package/dist/components/builder/reuse.d.ts +1 -1
  47. package/dist/components/builder/reuse.js +8 -5
  48. package/dist/components/builder/reuse.js.map +1 -1
  49. package/dist/components/builder/selection-overlay.d.ts +2 -2
  50. package/dist/components/builder/selection-overlay.js +3 -3
  51. package/dist/components/builder/selection-overlay.js.map +1 -1
  52. package/dist/components/builder/spacing-handles.d.ts +2 -2
  53. package/dist/components/builder/spacing-handles.js +3 -3
  54. package/dist/components/builder/spacing-handles.js.map +1 -1
  55. package/dist/components/cms/asset-field.d.ts +1 -1
  56. package/dist/components/cms/asset-field.js +15 -13
  57. package/dist/components/cms/asset-field.js.map +1 -1
  58. package/dist/components/cms/asset-picker-core.js +3 -4
  59. package/dist/components/cms/asset-picker-core.js.map +1 -1
  60. package/dist/components/cms/detail-editor.js +10 -5
  61. package/dist/components/cms/detail-editor.js.map +1 -1
  62. package/dist/components/cms/discard-draft.d.ts +1 -2
  63. package/dist/components/cms/discard-draft.js +1 -1
  64. package/dist/components/cms/discard-draft.js.map +1 -1
  65. package/dist/components/cms/editable-cell.js +11 -7
  66. package/dist/components/cms/editable-cell.js.map +1 -1
  67. package/dist/components/cms/field-control-lib.d.ts +10 -0
  68. package/dist/components/cms/field-control-lib.js +20 -0
  69. package/dist/components/cms/field-control-lib.js.map +1 -1
  70. package/dist/components/cms/field-control.js +3 -3
  71. package/dist/components/cms/field-control.js.map +1 -1
  72. package/dist/components/cms/object-array-field.js +3 -5
  73. package/dist/components/cms/object-array-field.js.map +1 -1
  74. package/dist/components/cms/prop-fields.d.ts +16 -3
  75. package/dist/components/cms/prop-fields.js +57 -12
  76. package/dist/components/cms/prop-fields.js.map +1 -1
  77. package/dist/components/cms/rich-text/embed-props.d.ts +6 -1
  78. package/dist/components/cms/rich-text/embed-props.js +7 -12
  79. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  80. package/dist/components/cms/rich-text/insert.d.ts +13 -6
  81. package/dist/components/cms/rich-text/insert.js +16 -17
  82. package/dist/components/cms/rich-text/insert.js.map +1 -1
  83. package/dist/components/cms/use-cell-edit.js +7 -12
  84. package/dist/components/cms/use-cell-edit.js.map +1 -1
  85. package/dist/components/cms/use-discard-controller.d.ts +1 -2
  86. package/dist/components/cms/use-discard-controller.js +1 -2
  87. package/dist/components/cms/use-discard-controller.js.map +1 -1
  88. package/dist/components/cms/versions-pane.js +2 -2
  89. package/dist/components/cms/versions-pane.js.map +1 -1
  90. package/dist/components/components/component-detail.d.ts +1 -1
  91. package/dist/components/components/component-detail.js +2 -4
  92. package/dist/components/components/component-detail.js.map +1 -1
  93. package/dist/components/components/component-preview.js +9 -4
  94. package/dist/components/components/component-preview.js.map +1 -1
  95. package/dist/components/components/components-index.d.ts +2 -2
  96. package/dist/components/components/components-index.js +3 -6
  97. package/dist/components/components/components-index.js.map +1 -1
  98. package/dist/components/components/field-inspector.js +5 -2
  99. package/dist/components/components/field-inspector.js.map +1 -1
  100. package/dist/components/components/registry-browser.d.ts +6 -5
  101. package/dist/components/components/registry-browser.js +11 -37
  102. package/dist/components/components/registry-browser.js.map +1 -1
  103. package/dist/components/components/taxonomy.d.ts +14 -21
  104. package/dist/components/components/taxonomy.js +5 -29
  105. package/dist/components/components/taxonomy.js.map +1 -1
  106. package/dist/components/components/transfer.d.ts +10 -8
  107. package/dist/components/components/transfer.js +17 -13
  108. package/dist/components/components/transfer.js.map +1 -1
  109. package/dist/lib/ai/constraints.d.ts +16 -14
  110. package/dist/lib/ai/constraints.js +64 -48
  111. package/dist/lib/ai/constraints.js.map +1 -1
  112. package/dist/lib/ai/update-fields.js +5 -3
  113. package/dist/lib/ai/update-fields.js.map +1 -1
  114. package/dist/lib/assets/asset-placements.d.ts +3 -10
  115. package/dist/lib/assets/asset-placements.js +17 -8
  116. package/dist/lib/assets/asset-placements.js.map +1 -1
  117. package/dist/lib/clipboard/paste-target.js +3 -3
  118. package/dist/lib/clipboard/paste-target.js.map +1 -1
  119. package/dist/lib/cms-views/view-model.js +11 -8
  120. package/dist/lib/cms-views/view-model.js.map +1 -1
  121. package/dist/lib/data/collection-content.d.ts +10 -7
  122. package/dist/lib/data/collection-content.js +34 -15
  123. package/dist/lib/data/collection-content.js.map +1 -1
  124. package/dist/lib/dnd/drop-rules.d.ts +22 -7
  125. package/dist/lib/dnd/drop-rules.js +21 -14
  126. package/dist/lib/dnd/drop-rules.js.map +1 -1
  127. package/dist/lib/engine-version.d.ts +1 -1
  128. package/dist/lib/engine-version.js +1 -1
  129. package/dist/lib/engine-version.js.map +1 -1
  130. package/dist/lib/i18n/messages.de.js +25 -9
  131. package/dist/lib/i18n/messages.de.js.map +1 -1
  132. package/dist/lib/i18n/messages.en.d.ts +21 -7
  133. package/dist/lib/i18n/messages.en.js +28 -9
  134. package/dist/lib/i18n/messages.en.js.map +1 -1
  135. package/dist/lib/operations/cms-persistence.js +7 -4
  136. package/dist/lib/operations/cms-persistence.js.map +1 -1
  137. package/dist/lib/persona/labels.d.ts +4 -9
  138. package/dist/lib/persona/labels.js +5 -12
  139. package/dist/lib/persona/labels.js.map +1 -1
  140. package/dist/lib/references/reference-model.d.ts +0 -2
  141. package/dist/lib/references/reference-model.js +3 -5
  142. package/dist/lib/references/reference-model.js.map +1 -1
  143. package/dist/lib/registry/__fixtures__/demo-components.d.ts +32 -0
  144. package/dist/lib/registry/{project-components.js → __fixtures__/demo-components.js} +18 -5
  145. package/dist/lib/registry/__fixtures__/demo-components.js.map +1 -0
  146. package/dist/lib/registry/index.d.ts +9 -13
  147. package/dist/lib/registry/index.js +34 -61
  148. package/dist/lib/registry/index.js.map +1 -1
  149. package/dist/lib/registry/preview.d.ts +22 -7
  150. package/dist/lib/registry/preview.js +26 -89
  151. package/dist/lib/registry/preview.js.map +1 -1
  152. package/dist/previews/enumerate.d.ts +23 -27
  153. package/dist/previews/enumerate.js +27 -48
  154. package/dist/previews/enumerate.js.map +1 -1
  155. package/dist/previews/harness.js +1 -1
  156. package/dist/previews/harness.js.map +1 -1
  157. package/package.json +4 -3
  158. package/dist/lib/registry/project-components.d.ts +0 -16
  159. package/dist/lib/registry/project-components.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"spacing-handles.js","sourceRoot":"","sources":["../../../src/components/builder/spacing-handles.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { DEFAULT_SLOT, isLayoutContainerNode, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { containerFlowAxis } from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { FlowAxis } from '@elytracms/studio/lib/dnd/drop-rules'\nimport {\n containerGapStep,\n gapHandleSpots,\n gapStepCss,\n gapStepFromDrag,\n} from '@elytracms/studio/lib/dnd/gap-drag'\nimport type { GapRect, GapStep } from '@elytracms/studio/lib/dnd/gap-drag'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useBuilder } from './builder-store'\nimport { GAP_LABEL_KEYS, GAP_NONE_LABEL_KEY } from './layout-inspector'\nimport { activeDrag, onDragSessionEnd, onDragSessionStart } from './drag-session'\nimport {\n createRectTracker,\n measureElementRect,\n scheduleFrame,\n subscribeCanvasLayout,\n} from './canvas-rect-tracking'\nimport type { FrameScheduler, RectTracker } from './canvas-rect-tracking'\n\n/**\n * Tokenized spacing handles (EC-163, vision §3.1 / AD-10): when a Stack/Grid\n * container is selected on the canvas, draggable gap handles appear between\n * its children, axis-aware via the shared `containerFlowAxis`. Dragging snaps\n * through the EC-161 spacing-token scale (`lib/dnd/gap-drag` — pure math, no\n * pixel value can escape it); the static gap tags (EC-435) stay put and read\n * the LIVE token + re-measured px during the drag (EC-438 — no\n * pointer-following label). The drag previews imperatively on the rendered\n * element; releasing commits exactly ONE builder dispatch — `setProp` with the\n * token, or `removeProp` for \"no gap\", the identical values the inspector's\n * gap select writes — so one undo restores, and the graph stays the single\n * source of truth. Esc mid-drag cancels: preview reverts, nothing dispatches.\n */\n\n/** Plain-language label for a gap step — the inspector's vocabulary. */\nexport function gapStepLabel(step: GapStep): string {\n return step === null ? translate(GAP_NONE_LABEL_KEY) : translate(GAP_LABEL_KEYS[step])\n}\n\nexport interface HandleSpotView {\n index: number\n /** Document-coordinate position of the handle's center. */\n x: number\n y: number\n crossStart: number\n crossLength: number\n /** Leading edge of the gap region along the flow axis (EC-435 hatch). */\n gapStart: number\n /** Rendered extent of the gap along the flow axis (0 for a closed gap). */\n gapLength: number\n}\n\n/**\n * The hatched gap REGION for a spot (EC-435 AD-2): the rectangle between child\n * N's trailing edge and child N+1's leading edge along the flow axis, spanning\n * the pair's shared cross extent — the same measured child-rect basis the\n * handle spots come from (`gapHandleSpots`), in the same document coordinates.\n * Pure over the spot; a closed gap yields a zero-extent rect (nothing to paint).\n */\nexport function gapRegionRect(spot: HandleSpotView, axis: FlowAxis): GapRect {\n return axis === 'horizontal'\n ? { left: spot.gapStart, top: spot.crossStart, width: spot.gapLength, height: spot.crossLength }\n : { left: spot.crossStart, top: spot.gapStart, width: spot.crossLength, height: spot.gapLength }\n}\n\ninterface ActiveDrag {\n index: number\n startStep: GapStep\n step: GapStep\n /** Pointer start along the flow axis (client coords). */\n startCoord: number\n}\n\n/**\n * The container's rendered element (the Stack/Grid div carrying the gap style).\n * Resilient to legitimate intermediate wrappers between the editable\n * `[data-ec-node-id]` wrapper and the primitive (an error-boundary/provider DOM\n * node, a future decorator layer): the FIRST `[data-ec-primitive]` inside the\n * wrapper whose NEAREST node wrapper is THIS node — so it tolerates a wrapper\n * above the primitive but never descends into a CHILD node's subtree and grabs\n * that nested container's own `[data-ec-primitive]`. Mirrors `measurePropRect`'s\n * nearest-node scoping (EC-408). Today no such wrapper exists — `decorate` wraps\n * `<RenderErrorBoundary>` which renders its children with no DOM element, so the\n * primitive is a direct child — but the scoping makes 2(b) unregressable.\n */\nexport function containerElementFor(doc: Document, nodeId: string): HTMLElement | null {\n const wrapper = doc.querySelector(`[data-ec-node-id=\"${nodeId}\"]`)\n if (!wrapper) return null\n for (const primitive of wrapper.querySelectorAll('[data-ec-primitive]')) {\n if (primitive.closest('[data-ec-node-id]') === wrapper) return primitive as HTMLElement\n }\n return null\n}\n\n/** The container's rendered child node-wrappers — the DOM child basis. */\nfunction childWrappers(container: Element): HTMLElement[] {\n return Array.from(container.querySelectorAll(':scope > [data-ec-node-id]')) as HTMLElement[]\n}\n\ninterface Measured {\n spots: HandleSpotView[]\n /** The axis the children are *rendered* along (drag direction follows it). */\n axis: FlowAxis\n}\n\nconst EMPTY_MEASURED = (axis: FlowAxis): Measured => ({ spots: [], axis })\n\n/**\n * Measure the handle spots between the container's rendered children — the\n * SINGLE source of truth for whether handles show (spots) and where. Eligibility\n * and measurement agree on the SAME (DOM) child basis by construction: a\n * null-rendering graph child (condition-gated → no `[data-ec-node-id]` wrapper)\n * simply isn't counted, so it can neither block handles when ≥2 children render\n * nor phantom them when <2 do; kept fresh by the live tracker (EC-404) so a child\n * add/remove or resize re-runs this. The graph-level flow axis\n * (`containerFlowAxis`) is tried first; when the rendered layout disagrees — a\n * stacking Grid below the mobile breakpoint lays its \"columns\" out vertically —\n * the cross axis is measured instead, so handles always sit in the gaps the\n * editor actually sees.\n */\nexport function measureSpots(doc: Document, nodeId: string, flowAxis: FlowAxis): Measured {\n const container = containerElementFor(doc, nodeId)\n const win = doc.defaultView\n if (!container || !win) return EMPTY_MEASURED(flowAxis)\n const children = childWrappers(container)\n if (children.length < 2) return EMPTY_MEASURED(flowAxis)\n const rects = children.map((el) => measureElementRect(el, win))\n const along = gapHandleSpots(rects, flowAxis)\n if (along.length > 0) return { spots: along, axis: flowAxis }\n const cross: FlowAxis = flowAxis === 'horizontal' ? 'vertical' : 'horizontal'\n const across = gapHandleSpots(rects, cross)\n return across.length > 0 ? { spots: across, axis: cross } : EMPTY_MEASURED(flowAxis)\n}\n\n/** Structural equality of two spot lists — suppresses redundant tracker emits. */\nfunction spotsEqual(a: readonly HandleSpotView[], b: readonly HandleSpotView[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i]!\n const y = b[i]!\n if (\n x.index !== y.index ||\n x.x !== y.x ||\n x.y !== y.y ||\n x.crossStart !== y.crossStart ||\n x.crossLength !== y.crossLength ||\n x.gapStart !== y.gapStart ||\n x.gapLength !== y.gapLength\n ) {\n return false\n }\n }\n return true\n}\n\n/** True when two measurements are equivalent (same axis, same spots). */\nexport function measuredEqual(a: Measured, b: Measured): boolean {\n return a.axis === b.axis && spotsEqual(a.spots, b.spots)\n}\n\n/** The elements the tracker's `ResizeObserver` watches: the container + its children. */\nfunction spacingTargets(doc: Document, nodeId: string): Element[] {\n const container = containerElementFor(doc, nodeId)\n if (!container) return []\n return [container, ...childWrappers(container)]\n}\n\n/**\n * Wire a live rect tracker for the spacing handles onto the shared EC-404 core:\n * `subscribeCanvasLayout` re-measures under child resize (image/font decode),\n * child add/remove (async population, conditional render — `MutationObserver`\n * `childList`), container resize, and scroll/viewport change; `scheduleFrame`\n * coalesces on the PARENT window's frame loop with the mandatory 100 ms timeout\n * backstop (the iframe's rAF is paused offscreen / never fires under automation).\n * `dispose` tears down every observer + listener — no leaks on unmount. The\n * once-only `useLayoutEffect` measure this replaces went stale the instant any of\n * those settled after first paint (EC-406 evidence).\n */\nexport function createSpacingTracker(\n doc: Document,\n nodeId: string,\n flowAxis: FlowAxis,\n host: FrameScheduler,\n onChange: (measured: Measured) => void,\n): RectTracker {\n return createRectTracker<Measured>({\n measure: () => measureSpots(doc, nodeId, flowAxis),\n equals: measuredEqual,\n onChange,\n schedule: (flush) => scheduleFrame(host, flush),\n subscribe: (invalidate) => subscribeCanvasLayout(doc, () => spacingTargets(doc, nodeId), invalidate),\n })\n}\n\n/**\n * True when the node is a layout container the handles can attach to — a cheap,\n * NECESSARY graph precondition for the mount (the authoritative \"are there ≥2\n * gaps to space\" answer is the live DOM {@link measureSpots}). Because each graph\n * child renders at most one `[data-ec-node-id]` wrapper, rendered DOM children ⊆\n * graph children, so requiring ≥2 graph children can never wrongly hide handles\n * that a fresh DOM measure would show.\n */\nexport function hasSpacingHandles(node: ComponentNode | null): node is ComponentNode {\n return (\n node !== null &&\n isLayoutContainerNode(node) &&\n (node.slots?.[DEFAULT_SLOT]?.length ?? 0) >= 2\n )\n}\n\nconst HANDLE_THICKNESS = 14\nconst HANDLE_BAR = 4\nconst HANDLE_MAX_LENGTH = 48\n\n/** Everything the portal-free handle markup needs (chrome contract surface). */\nexport interface SpacingHandlesViewProps {\n spots: HandleSpotView[]\n horizontal: boolean\n /**\n * Token label for the container's COMMITTED gap (EC-435 AD-2) — the SAME\n * plain-language vocabulary the layout inspector resolves (`gapStepLabel`),\n * stamped on every hatched gap region as a static size tag while idle.\n */\n gapLabel: string\n /**\n * The live drag: the active handle index (tint) and the live step's token\n * label — the static tags read it in place while dragging (EC-438).\n */\n drag: { index: number; label: string } | null\n onPointerDown: (spot: HandleSpotView) => (e: React.PointerEvent<HTMLDivElement>) => void\n onPointerMove: (e: React.PointerEvent<HTMLDivElement>) => void\n onPointerUp: () => void\n onPointerCancel: () => void\n}\n\n/**\n * The spacing handles' rendered markup, portal-free so the AD-D chrome\n * contract test can walk it with `renderToStaticMarkup`. In-frame chrome\n * contract (EC-414 AD-B): `ec-*`/`data-ec-*` plus inline styles only; the root\n * carries the `.ec-chrome` containment recipe.\n *\n * EC-435 AD-2: every open gap region renders a pointer-transparent hatched\n * rectangle (diagonal accent stripes, Figma-grid style) under the handles,\n * each with a small static token-label size tag beside its handle. During a\n * gap drag the tags STAY PUT and read live (EC-438 — a gap drag previews ONE\n * container-wide gap, so every tag shows the live step's token + its own\n * re-measured px); no label follows the pointer. The gap handles remain the\n * only interactive element.\n */\nexport function SpacingHandlesView(props: SpacingHandlesViewProps) {\n const { spots, horizontal, gapLabel, drag } = props\n const axis: FlowAxis = horizontal ? 'horizontal' : 'vertical'\n return (\n <div data-ec-spacing-handles=\"\" className=\"ec-chrome\">\n {spots.map((spot) => {\n const region = gapRegionRect(spot, axis)\n if (spot.gapLength < 1) return null\n return (\n <div\n key={spot.index}\n data-ec-gap-hatch={spot.index}\n style={{\n position: 'absolute',\n left: region.left,\n top: region.top,\n width: region.width,\n height: region.height,\n zIndex: 10,\n pointerEvents: 'none',\n background:\n 'repeating-linear-gradient(45deg, rgba(91,140,255,0.28) 0px, rgba(91,140,255,0.28) 3px, rgba(91,140,255,0.06) 3px, rgba(91,140,255,0.06) 9px)',\n boxShadow: 'inset 0 0 0 1px rgba(91,140,255,0.3)',\n }}\n />\n )\n })}\n {spots.map((spot) => {\n if (spot.gapLength < 1) return null\n const length = Math.min(spot.crossLength, HANDLE_MAX_LENGTH)\n return (\n <div\n key={spot.index}\n data-ec-gap-hatch-label={spot.index}\n style={{\n position: 'absolute',\n left: horizontal ? spot.x : spot.x + length / 2 + 6,\n top: horizontal ? spot.y + length / 2 + 6 : spot.y,\n transform: horizontal ? 'translateX(-50%)' : 'translateY(-50%)',\n zIndex: 45,\n pointerEvents: 'none',\n padding: '1px 6px',\n borderRadius: 5,\n fontSize: 10,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n color: '#fff',\n backgroundColor: 'rgba(91,140,255,0.92)',\n boxShadow: '0 1px 4px rgba(8,12,24,0.3)',\n }}\n >\n {drag ? drag.label : gapLabel} · {Math.round(spot.gapLength)}px\n </div>\n )\n })}\n {spots.map((spot) => {\n const length = Math.min(spot.crossLength, HANDLE_MAX_LENGTH)\n const active = drag?.index === spot.index\n return (\n <div\n key={spot.index}\n data-ec-gap-handle={spot.index}\n role=\"separator\"\n aria-label={translate('builder.spacing.gapAfterChild', { index: spot.index + 1 })}\n aria-orientation={horizontal ? 'vertical' : 'horizontal'}\n onPointerDown={props.onPointerDown(spot)}\n onPointerMove={props.onPointerMove}\n onPointerUp={props.onPointerUp}\n onPointerCancel={props.onPointerCancel}\n style={{\n position: 'absolute',\n left: spot.x - (horizontal ? HANDLE_THICKNESS / 2 : length / 2),\n top: spot.y - (horizontal ? length / 2 : HANDLE_THICKNESS / 2),\n width: horizontal ? HANDLE_THICKNESS : length,\n height: horizontal ? length : HANDLE_THICKNESS,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n cursor: horizontal ? 'col-resize' : 'row-resize',\n zIndex: 40,\n touchAction: 'none',\n }}\n >\n <div\n style={{\n width: horizontal ? HANDLE_BAR : '100%',\n height: horizontal ? '100%' : HANDLE_BAR,\n borderRadius: 999,\n backgroundColor: active ? '#5b8cff' : 'rgba(91, 140, 255, 0.65)',\n boxShadow: '0 0 0 1px rgba(8, 12, 24, 0.55)',\n }}\n />\n </div>\n )\n })}\n </div>\n )\n}\n\nexport function SpacingHandles({ doc, node }: { doc: Document; node: ComponentNode }) {\n const builder = useBuilder()\n const { dispatch } = builder\n const flowAxis = containerFlowAxis(node)\n const [measured, setMeasured] = useState<Measured>(() => ({ spots: [], axis: flowAxis }))\n const [drag, setDrag] = useState<ActiveDrag | null>(null)\n const dragRef = useRef<ActiveDrag | null>(null)\n const trackerRef = useRef<RectTracker | null>(null)\n // The container's pre-drag inline gap, restored on cancel/commit (the graph\n // re-render owns the committed value; the preview is imperative-only).\n const inlineGapRef = useRef<string>('')\n\n // Measurement lifetime (EC-406): ride the shared rect-tracking core instead of\n // the once-only useLayoutEffect + lone resize listener that went stale. The\n // tracker re-measures under child resize (image/font decode), child add/remove\n // (async population, conditional render), container resize, and scroll/viewport\n // change — every settle the old code missed. Re-subscribed on a graph/axis/\n // viewport change so the ResizeObserver re-points at the current children.\n useLayoutEffect(() => {\n const win = doc.defaultView\n if (!win) return\n const host = (win.parent ?? win) as unknown as FrameScheduler\n const tracker = createSpacingTracker(doc, node.id, flowAxis, host, setMeasured)\n trackerRef.current = tracker\n return () => {\n tracker.dispose()\n trackerRef.current = null\n }\n }, [doc, node.id, flowAxis, builder.present, builder.viewport])\n\n // The gap-drag previews imperatively on `container.style.gap` (the tracker's\n // MutationObserver is childList-only and ignores attribute writes, by design),\n // so nudge a coalesced re-measure per step to keep every handle glued to the\n // previewed gap as the children shift. Harmless off-drag (an unchanged measure\n // emits nothing).\n useEffect(() => {\n trackerRef.current?.invalidate()\n }, [drag?.step])\n\n // Scope 4: yield the handles during an active CONTENT drag (node move/insert) —\n // the DnD before/after/into indicators own the \"where it lands\" story then. The\n // handles' OWN gap drag uses pointer capture, never `beginDrag`, so it does not\n // trip this. Inline-editing suppression is handled at the canvas mount (the\n // handles unmount while a text/rich-text session owns the node).\n const [contentDragging, setContentDragging] = useState(() => activeDrag() !== null)\n useEffect(() => {\n const offStart = onDragSessionStart(() => setContentDragging(true))\n const offEnd = onDragSessionEnd(() => setContentDragging(false))\n return () => {\n offStart()\n offEnd()\n }\n }, [])\n\n const { spots: measuredSpots, axis } = measured\n const spots = contentDragging ? [] : measuredSpots\n\n const applyPreview = useCallback(\n (step: GapStep) => {\n const container = containerElementFor(doc, node.id)\n if (container) container.style.gap = gapStepCss(step)\n },\n [doc, node.id],\n )\n\n const finish = useCallback(\n (mode: 'commit' | 'cancel') => {\n const active = dragRef.current\n if (!active) return\n dragRef.current = null\n setDrag(null)\n // Drop the imperative preview; the rendered graph value takes over.\n const container = containerElementFor(doc, node.id)\n if (container) container.style.gap = inlineGapRef.current\n if (mode === 'commit' && active.step !== active.startStep) {\n // Exactly one dispatch — one operations command, one undo step — and\n // the identical prop value the inspector's gap select produces.\n if (active.step === null) {\n dispatch({ type: 'removeProp', id: node.id, name: 'gap' })\n } else {\n dispatch({ type: 'setProp', id: node.id, name: 'gap', value: staticProp(active.step) })\n }\n }\n },\n [doc, node.id, dispatch],\n )\n\n // Esc cancels mid-drag — listen on both documents (focus may sit either side\n // of the iframe boundary while the pointer is captured).\n useEffect(() => {\n if (!drag) return\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n finish('cancel')\n }\n }\n doc.addEventListener('keydown', onKeyDown, true)\n document.addEventListener('keydown', onKeyDown, true)\n return () => {\n doc.removeEventListener('keydown', onKeyDown, true)\n document.removeEventListener('keydown', onKeyDown, true)\n }\n }, [drag, doc, finish])\n\n // Cleanup on unmount/selection change mid-drag: revert, never dispatch.\n useEffect(() => () => finish('cancel'), [finish])\n\n const onPointerDown = (spot: HandleSpotView) => (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return\n e.preventDefault()\n e.stopPropagation()\n const startStep = containerGapStep(node)\n const container = containerElementFor(doc, node.id)\n inlineGapRef.current = container?.style.gap ?? ''\n try {\n e.currentTarget.setPointerCapture(e.pointerId)\n } catch {\n // Synthetic pointer events (tests, automation) have no active pointer\n // to capture; move/up still arrive via the handle's own listeners.\n }\n const next: ActiveDrag = {\n index: spot.index,\n startStep,\n step: startStep,\n startCoord: axis === 'horizontal' ? e.clientX : e.clientY,\n }\n dragRef.current = next\n setDrag(next)\n }\n\n const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const active = dragRef.current\n if (!active) return\n const coord = axis === 'horizontal' ? e.clientX : e.clientY\n const step = gapStepFromDrag(active.startStep, coord - active.startCoord)\n const next: ActiveDrag = { ...active, step }\n dragRef.current = next\n setDrag(next)\n applyPreview(step)\n }\n\n const horizontal = axis === 'horizontal'\n\n return createPortal(\n <SpacingHandlesView\n spots={spots}\n horizontal={horizontal}\n // The static size tags share ONE vocabulary with the inspector's gap\n // select: the token label of the node's committed gap step (EC-435\n // AD-2); during a drag they read the live step instead (EC-438).\n gapLabel={gapStepLabel(containerGapStep(node))}\n drag={drag ? { index: drag.index, label: gapStepLabel(drag.step) } : null}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={() => finish('commit')}\n onPointerCancel={() => finish('cancel')}\n />,\n doc.body,\n )\n}\n"]}
1
+ {"version":3,"file":"spacing-handles.js","sourceRoot":"","sources":["../../../src/components/builder/spacing-handles.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { DEFAULT_SLOT, isLayoutContainerNode, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, LayoutContainerIds } from '@elytracms/core/project-graph'\nimport { containerFlowAxis } from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { FlowAxis } from '@elytracms/studio/lib/dnd/drop-rules'\nimport {\n containerGapStep,\n gapHandleSpots,\n gapStepCss,\n gapStepFromDrag,\n} from '@elytracms/studio/lib/dnd/gap-drag'\nimport type { GapRect, GapStep } from '@elytracms/studio/lib/dnd/gap-drag'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useBuilder } from './builder-store'\nimport { GAP_LABEL_KEYS, GAP_NONE_LABEL_KEY } from './layout-inspector'\nimport { activeDrag, onDragSessionEnd, onDragSessionStart } from './drag-session'\nimport {\n createRectTracker,\n measureElementRect,\n scheduleFrame,\n subscribeCanvasLayout,\n} from './canvas-rect-tracking'\nimport type { FrameScheduler, RectTracker } from './canvas-rect-tracking'\n\n/**\n * Tokenized spacing handles (EC-163, vision §3.1 / AD-10): when a Stack/Grid\n * container is selected on the canvas, draggable gap handles appear between\n * its children, axis-aware via the shared `containerFlowAxis`. Dragging snaps\n * through the EC-161 spacing-token scale (`lib/dnd/gap-drag` — pure math, no\n * pixel value can escape it); the static gap tags (EC-435) stay put and read\n * the LIVE token + re-measured px during the drag (EC-438 — no\n * pointer-following label). The drag previews imperatively on the rendered\n * element; releasing commits exactly ONE builder dispatch — `setProp` with the\n * token, or `removeProp` for \"no gap\", the identical values the inspector's\n * gap select writes — so one undo restores, and the graph stays the single\n * source of truth. Esc mid-drag cancels: preview reverts, nothing dispatches.\n */\n\n/** Plain-language label for a gap step — the inspector's vocabulary. */\nexport function gapStepLabel(step: GapStep): string {\n return step === null ? translate(GAP_NONE_LABEL_KEY) : translate(GAP_LABEL_KEYS[step])\n}\n\nexport interface HandleSpotView {\n index: number\n /** Document-coordinate position of the handle's center. */\n x: number\n y: number\n crossStart: number\n crossLength: number\n /** Leading edge of the gap region along the flow axis (EC-435 hatch). */\n gapStart: number\n /** Rendered extent of the gap along the flow axis (0 for a closed gap). */\n gapLength: number\n}\n\n/**\n * The hatched gap REGION for a spot (EC-435 AD-2): the rectangle between child\n * N's trailing edge and child N+1's leading edge along the flow axis, spanning\n * the pair's shared cross extent — the same measured child-rect basis the\n * handle spots come from (`gapHandleSpots`), in the same document coordinates.\n * Pure over the spot; a closed gap yields a zero-extent rect (nothing to paint).\n */\nexport function gapRegionRect(spot: HandleSpotView, axis: FlowAxis): GapRect {\n return axis === 'horizontal'\n ? { left: spot.gapStart, top: spot.crossStart, width: spot.gapLength, height: spot.crossLength }\n : { left: spot.crossStart, top: spot.gapStart, width: spot.crossLength, height: spot.gapLength }\n}\n\ninterface ActiveDrag {\n index: number\n startStep: GapStep\n step: GapStep\n /** Pointer start along the flow axis (client coords). */\n startCoord: number\n}\n\n/**\n * The container's rendered element (the Stack/Grid div carrying the gap style).\n * Resilient to legitimate intermediate wrappers between the editable\n * `[data-ec-node-id]` wrapper and the primitive (an error-boundary/provider DOM\n * node, a future decorator layer): the FIRST `[data-ec-primitive]` inside the\n * wrapper whose NEAREST node wrapper is THIS node — so it tolerates a wrapper\n * above the primitive but never descends into a CHILD node's subtree and grabs\n * that nested container's own `[data-ec-primitive]`. Mirrors `measurePropRect`'s\n * nearest-node scoping (EC-408). Today no such wrapper exists — `decorate` wraps\n * `<RenderErrorBoundary>` which renders its children with no DOM element, so the\n * primitive is a direct child — but the scoping makes 2(b) unregressable.\n */\nexport function containerElementFor(doc: Document, nodeId: string): HTMLElement | null {\n const wrapper = doc.querySelector(`[data-ec-node-id=\"${nodeId}\"]`)\n if (!wrapper) return null\n for (const primitive of wrapper.querySelectorAll('[data-ec-primitive]')) {\n if (primitive.closest('[data-ec-node-id]') === wrapper) return primitive as HTMLElement\n }\n return null\n}\n\n/** The container's rendered child node-wrappers — the DOM child basis. */\nfunction childWrappers(container: Element): HTMLElement[] {\n return Array.from(container.querySelectorAll(':scope > [data-ec-node-id]')) as HTMLElement[]\n}\n\ninterface Measured {\n spots: HandleSpotView[]\n /** The axis the children are *rendered* along (drag direction follows it). */\n axis: FlowAxis\n}\n\nconst EMPTY_MEASURED = (axis: FlowAxis): Measured => ({ spots: [], axis })\n\n/**\n * Measure the handle spots between the container's rendered children — the\n * SINGLE source of truth for whether handles show (spots) and where. Eligibility\n * and measurement agree on the SAME (DOM) child basis by construction: a\n * null-rendering graph child (condition-gated → no `[data-ec-node-id]` wrapper)\n * simply isn't counted, so it can neither block handles when ≥2 children render\n * nor phantom them when <2 do; kept fresh by the live tracker (EC-404) so a child\n * add/remove or resize re-runs this. The graph-level flow axis\n * (`containerFlowAxis`) is tried first; when the rendered layout disagrees — a\n * stacking Grid below the mobile breakpoint lays its \"columns\" out vertically —\n * the cross axis is measured instead, so handles always sit in the gaps the\n * editor actually sees.\n */\nexport function measureSpots(doc: Document, nodeId: string, flowAxis: FlowAxis): Measured {\n const container = containerElementFor(doc, nodeId)\n const win = doc.defaultView\n if (!container || !win) return EMPTY_MEASURED(flowAxis)\n const children = childWrappers(container)\n if (children.length < 2) return EMPTY_MEASURED(flowAxis)\n const rects = children.map((el) => measureElementRect(el, win))\n const along = gapHandleSpots(rects, flowAxis)\n if (along.length > 0) return { spots: along, axis: flowAxis }\n const cross: FlowAxis = flowAxis === 'horizontal' ? 'vertical' : 'horizontal'\n const across = gapHandleSpots(rects, cross)\n return across.length > 0 ? { spots: across, axis: cross } : EMPTY_MEASURED(flowAxis)\n}\n\n/** Structural equality of two spot lists — suppresses redundant tracker emits. */\nfunction spotsEqual(a: readonly HandleSpotView[], b: readonly HandleSpotView[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i]!\n const y = b[i]!\n if (\n x.index !== y.index ||\n x.x !== y.x ||\n x.y !== y.y ||\n x.crossStart !== y.crossStart ||\n x.crossLength !== y.crossLength ||\n x.gapStart !== y.gapStart ||\n x.gapLength !== y.gapLength\n ) {\n return false\n }\n }\n return true\n}\n\n/** True when two measurements are equivalent (same axis, same spots). */\nexport function measuredEqual(a: Measured, b: Measured): boolean {\n return a.axis === b.axis && spotsEqual(a.spots, b.spots)\n}\n\n/** The elements the tracker's `ResizeObserver` watches: the container + its children. */\nfunction spacingTargets(doc: Document, nodeId: string): Element[] {\n const container = containerElementFor(doc, nodeId)\n if (!container) return []\n return [container, ...childWrappers(container)]\n}\n\n/**\n * Wire a live rect tracker for the spacing handles onto the shared EC-404 core:\n * `subscribeCanvasLayout` re-measures under child resize (image/font decode),\n * child add/remove (async population, conditional render — `MutationObserver`\n * `childList`), container resize, and scroll/viewport change; `scheduleFrame`\n * coalesces on the PARENT window's frame loop with the mandatory 100 ms timeout\n * backstop (the iframe's rAF is paused offscreen / never fires under automation).\n * `dispose` tears down every observer + listener — no leaks on unmount. The\n * once-only `useLayoutEffect` measure this replaces went stale the instant any of\n * those settled after first paint (EC-406 evidence).\n */\nexport function createSpacingTracker(\n doc: Document,\n nodeId: string,\n flowAxis: FlowAxis,\n host: FrameScheduler,\n onChange: (measured: Measured) => void,\n): RectTracker {\n return createRectTracker<Measured>({\n measure: () => measureSpots(doc, nodeId, flowAxis),\n equals: measuredEqual,\n onChange,\n schedule: (flush) => scheduleFrame(host, flush),\n subscribe: (invalidate) => subscribeCanvasLayout(doc, () => spacingTargets(doc, nodeId), invalidate),\n })\n}\n\n/**\n * True when the node is a layout container the handles can attach to — a cheap,\n * NECESSARY graph precondition for the mount (the authoritative \"are there ≥2\n * gaps to space\" answer is the live DOM {@link measureSpots}). Because each graph\n * child renders at most one `[data-ec-node-id]` wrapper, rendered DOM children ⊆\n * graph children, so requiring ≥2 graph children can never wrongly hide handles\n * that a fresh DOM measure would show.\n */\nexport function hasSpacingHandles(\n node: ComponentNode | null,\n layout: LayoutContainerIds,\n): node is ComponentNode {\n return (\n node !== null &&\n isLayoutContainerNode(node, layout) &&\n (node.slots?.[DEFAULT_SLOT]?.length ?? 0) >= 2\n )\n}\n\nconst HANDLE_THICKNESS = 14\nconst HANDLE_BAR = 4\nconst HANDLE_MAX_LENGTH = 48\n\n/** Everything the portal-free handle markup needs (chrome contract surface). */\nexport interface SpacingHandlesViewProps {\n spots: HandleSpotView[]\n horizontal: boolean\n /**\n * Token label for the container's COMMITTED gap (EC-435 AD-2) — the SAME\n * plain-language vocabulary the layout inspector resolves (`gapStepLabel`),\n * stamped on every hatched gap region as a static size tag while idle.\n */\n gapLabel: string\n /**\n * The live drag: the active handle index (tint) and the live step's token\n * label — the static tags read it in place while dragging (EC-438).\n */\n drag: { index: number; label: string } | null\n onPointerDown: (spot: HandleSpotView) => (e: React.PointerEvent<HTMLDivElement>) => void\n onPointerMove: (e: React.PointerEvent<HTMLDivElement>) => void\n onPointerUp: () => void\n onPointerCancel: () => void\n}\n\n/**\n * The spacing handles' rendered markup, portal-free so the AD-D chrome\n * contract test can walk it with `renderToStaticMarkup`. In-frame chrome\n * contract (EC-414 AD-B): `ec-*`/`data-ec-*` plus inline styles only; the root\n * carries the `.ec-chrome` containment recipe.\n *\n * EC-435 AD-2: every open gap region renders a pointer-transparent hatched\n * rectangle (diagonal accent stripes, Figma-grid style) under the handles,\n * each with a small static token-label size tag beside its handle. During a\n * gap drag the tags STAY PUT and read live (EC-438 — a gap drag previews ONE\n * container-wide gap, so every tag shows the live step's token + its own\n * re-measured px); no label follows the pointer. The gap handles remain the\n * only interactive element.\n */\nexport function SpacingHandlesView(props: SpacingHandlesViewProps) {\n const { spots, horizontal, gapLabel, drag } = props\n const axis: FlowAxis = horizontal ? 'horizontal' : 'vertical'\n return (\n <div data-ec-spacing-handles=\"\" className=\"ec-chrome\">\n {spots.map((spot) => {\n const region = gapRegionRect(spot, axis)\n if (spot.gapLength < 1) return null\n return (\n <div\n key={spot.index}\n data-ec-gap-hatch={spot.index}\n style={{\n position: 'absolute',\n left: region.left,\n top: region.top,\n width: region.width,\n height: region.height,\n zIndex: 10,\n pointerEvents: 'none',\n background:\n 'repeating-linear-gradient(45deg, rgba(91,140,255,0.28) 0px, rgba(91,140,255,0.28) 3px, rgba(91,140,255,0.06) 3px, rgba(91,140,255,0.06) 9px)',\n boxShadow: 'inset 0 0 0 1px rgba(91,140,255,0.3)',\n }}\n />\n )\n })}\n {spots.map((spot) => {\n if (spot.gapLength < 1) return null\n const length = Math.min(spot.crossLength, HANDLE_MAX_LENGTH)\n return (\n <div\n key={spot.index}\n data-ec-gap-hatch-label={spot.index}\n style={{\n position: 'absolute',\n left: horizontal ? spot.x : spot.x + length / 2 + 6,\n top: horizontal ? spot.y + length / 2 + 6 : spot.y,\n transform: horizontal ? 'translateX(-50%)' : 'translateY(-50%)',\n zIndex: 45,\n pointerEvents: 'none',\n padding: '1px 6px',\n borderRadius: 5,\n fontSize: 10,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n color: '#fff',\n backgroundColor: 'rgba(91,140,255,0.92)',\n boxShadow: '0 1px 4px rgba(8,12,24,0.3)',\n }}\n >\n {drag ? drag.label : gapLabel} · {Math.round(spot.gapLength)}px\n </div>\n )\n })}\n {spots.map((spot) => {\n const length = Math.min(spot.crossLength, HANDLE_MAX_LENGTH)\n const active = drag?.index === spot.index\n return (\n <div\n key={spot.index}\n data-ec-gap-handle={spot.index}\n role=\"separator\"\n aria-label={translate('builder.spacing.gapAfterChild', { index: spot.index + 1 })}\n aria-orientation={horizontal ? 'vertical' : 'horizontal'}\n onPointerDown={props.onPointerDown(spot)}\n onPointerMove={props.onPointerMove}\n onPointerUp={props.onPointerUp}\n onPointerCancel={props.onPointerCancel}\n style={{\n position: 'absolute',\n left: spot.x - (horizontal ? HANDLE_THICKNESS / 2 : length / 2),\n top: spot.y - (horizontal ? length / 2 : HANDLE_THICKNESS / 2),\n width: horizontal ? HANDLE_THICKNESS : length,\n height: horizontal ? length : HANDLE_THICKNESS,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n cursor: horizontal ? 'col-resize' : 'row-resize',\n zIndex: 40,\n touchAction: 'none',\n }}\n >\n <div\n style={{\n width: horizontal ? HANDLE_BAR : '100%',\n height: horizontal ? '100%' : HANDLE_BAR,\n borderRadius: 999,\n backgroundColor: active ? '#5b8cff' : 'rgba(91, 140, 255, 0.65)',\n boxShadow: '0 0 0 1px rgba(8, 12, 24, 0.55)',\n }}\n />\n </div>\n )\n })}\n </div>\n )\n}\n\nexport function SpacingHandles({ doc, node }: { doc: Document; node: ComponentNode }) {\n const builder = useBuilder()\n const { dispatch } = builder\n const flowAxis = containerFlowAxis(node, builder.layout)\n const [measured, setMeasured] = useState<Measured>(() => ({ spots: [], axis: flowAxis }))\n const [drag, setDrag] = useState<ActiveDrag | null>(null)\n const dragRef = useRef<ActiveDrag | null>(null)\n const trackerRef = useRef<RectTracker | null>(null)\n // The container's pre-drag inline gap, restored on cancel/commit (the graph\n // re-render owns the committed value; the preview is imperative-only).\n const inlineGapRef = useRef<string>('')\n\n // Measurement lifetime (EC-406): ride the shared rect-tracking core instead of\n // the once-only useLayoutEffect + lone resize listener that went stale. The\n // tracker re-measures under child resize (image/font decode), child add/remove\n // (async population, conditional render), container resize, and scroll/viewport\n // change — every settle the old code missed. Re-subscribed on a graph/axis/\n // viewport change so the ResizeObserver re-points at the current children.\n useLayoutEffect(() => {\n const win = doc.defaultView\n if (!win) return\n const host = (win.parent ?? win) as unknown as FrameScheduler\n const tracker = createSpacingTracker(doc, node.id, flowAxis, host, setMeasured)\n trackerRef.current = tracker\n return () => {\n tracker.dispose()\n trackerRef.current = null\n }\n }, [doc, node.id, flowAxis, builder.present, builder.viewport])\n\n // The gap-drag previews imperatively on `container.style.gap` (the tracker's\n // MutationObserver is childList-only and ignores attribute writes, by design),\n // so nudge a coalesced re-measure per step to keep every handle glued to the\n // previewed gap as the children shift. Harmless off-drag (an unchanged measure\n // emits nothing).\n useEffect(() => {\n trackerRef.current?.invalidate()\n }, [drag?.step])\n\n // Scope 4: yield the handles during an active CONTENT drag (node move/insert) —\n // the DnD before/after/into indicators own the \"where it lands\" story then. The\n // handles' OWN gap drag uses pointer capture, never `beginDrag`, so it does not\n // trip this. Inline-editing suppression is handled at the canvas mount (the\n // handles unmount while a text/rich-text session owns the node).\n const [contentDragging, setContentDragging] = useState(() => activeDrag() !== null)\n useEffect(() => {\n const offStart = onDragSessionStart(() => setContentDragging(true))\n const offEnd = onDragSessionEnd(() => setContentDragging(false))\n return () => {\n offStart()\n offEnd()\n }\n }, [])\n\n const { spots: measuredSpots, axis } = measured\n const spots = contentDragging ? [] : measuredSpots\n\n const applyPreview = useCallback(\n (step: GapStep) => {\n const container = containerElementFor(doc, node.id)\n if (container) container.style.gap = gapStepCss(step)\n },\n [doc, node.id],\n )\n\n const finish = useCallback(\n (mode: 'commit' | 'cancel') => {\n const active = dragRef.current\n if (!active) return\n dragRef.current = null\n setDrag(null)\n // Drop the imperative preview; the rendered graph value takes over.\n const container = containerElementFor(doc, node.id)\n if (container) container.style.gap = inlineGapRef.current\n if (mode === 'commit' && active.step !== active.startStep) {\n // Exactly one dispatch — one operations command, one undo step — and\n // the identical prop value the inspector's gap select produces.\n if (active.step === null) {\n dispatch({ type: 'removeProp', id: node.id, name: 'gap' })\n } else {\n dispatch({ type: 'setProp', id: node.id, name: 'gap', value: staticProp(active.step) })\n }\n }\n },\n [doc, node.id, dispatch],\n )\n\n // Esc cancels mid-drag — listen on both documents (focus may sit either side\n // of the iframe boundary while the pointer is captured).\n useEffect(() => {\n if (!drag) return\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n finish('cancel')\n }\n }\n doc.addEventListener('keydown', onKeyDown, true)\n document.addEventListener('keydown', onKeyDown, true)\n return () => {\n doc.removeEventListener('keydown', onKeyDown, true)\n document.removeEventListener('keydown', onKeyDown, true)\n }\n }, [drag, doc, finish])\n\n // Cleanup on unmount/selection change mid-drag: revert, never dispatch.\n useEffect(() => () => finish('cancel'), [finish])\n\n const onPointerDown = (spot: HandleSpotView) => (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return\n e.preventDefault()\n e.stopPropagation()\n const startStep = containerGapStep(node)\n const container = containerElementFor(doc, node.id)\n inlineGapRef.current = container?.style.gap ?? ''\n try {\n e.currentTarget.setPointerCapture(e.pointerId)\n } catch {\n // Synthetic pointer events (tests, automation) have no active pointer\n // to capture; move/up still arrive via the handle's own listeners.\n }\n const next: ActiveDrag = {\n index: spot.index,\n startStep,\n step: startStep,\n startCoord: axis === 'horizontal' ? e.clientX : e.clientY,\n }\n dragRef.current = next\n setDrag(next)\n }\n\n const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const active = dragRef.current\n if (!active) return\n const coord = axis === 'horizontal' ? e.clientX : e.clientY\n const step = gapStepFromDrag(active.startStep, coord - active.startCoord)\n const next: ActiveDrag = { ...active, step }\n dragRef.current = next\n setDrag(next)\n applyPreview(step)\n }\n\n const horizontal = axis === 'horizontal'\n\n return createPortal(\n <SpacingHandlesView\n spots={spots}\n horizontal={horizontal}\n // The static size tags share ONE vocabulary with the inspector's gap\n // select: the token label of the node's committed gap step (EC-435\n // AD-2); during a drag they read the live step instead (EC-438).\n gapLabel={gapStepLabel(containerGapStep(node))}\n drag={drag ? { index: drag.index, label: gapStepLabel(drag.step) } : null}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={() => finish('commit')}\n onPointerCancel={() => finish('cancel')}\n />,\n doc.body,\n )\n}\n"]}
@@ -15,7 +15,7 @@ import type { AssetFieldDef } from '@elytracms/core/cms-core';
15
15
  * included, mounts one, `studio-project-screen.tsx`) it degrades to the labelled-badge
16
16
  * picker — no preview/upload — so it stays usable without a real library.
17
17
  */
18
- export declare function AssetField({ field, value, onChange, readOnly, }: {
18
+ export declare function AssetField({ field, value: stored, onChange, readOnly, }: {
19
19
  field: AssetFieldDef;
20
20
  value: unknown;
21
21
  onChange: (value: unknown) => void;
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ImagePlus, RotateCcw, SlidersHorizontal, Trash2, Upload, X } from 'lucide-react';
3
3
  import { useEffect, useMemo, useRef, useState } from 'react';
4
- import { assetNode, assetSubfieldsOf, readAssetId } from '@elytracms/core/cms-core';
4
+ import { assetNode, assetSubfieldsOf, readAssetId, selfTypeFieldValue } 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
- import { appendAssetSelections, applyAssetSelection, assetIdsOf, assetPickForm, singleAssetIdOf, } from '../assets/asset-selection.js';
8
+ import { appendAssetSelections, applyAssetSelection, assetIdsOf, removeAssetPlacementAt, 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
11
  import { assetPickerBinding, splitByAccept, uploadBatchProgress } from './asset-picker-core.js';
@@ -32,7 +32,12 @@ import { ObjectItemForm } from './object-array-field.js';
32
32
  * included, mounts one, `studio-project-screen.tsx`) it degrades to the labelled-badge
33
33
  * picker — no preview/upload — so it stays usable without a real library.
34
34
  */
35
- export function AssetField({ field, value, onChange, readOnly, }) {
35
+ export function AssetField({ field, value: stored, onChange, readOnly, }) {
36
+ // EC-586/EC-581 (ASSETS D4): the field reads its value through THE canonicaliser — the
37
+ // one cardinality rule — so a lone placement left in a field since declared `multiple`
38
+ // shows as a one-item gallery (and the next pick keeps it) instead of an empty one that
39
+ // drops it; picks and removes then write from that canonical list.
40
+ const value = useMemo(() => selfTypeFieldValue(field, stored), [field, stored]);
36
41
  const source = useCollectionView();
37
42
  const assets = useOptionalAssets();
38
43
  const density = useFieldDensity();
@@ -51,14 +56,11 @@ export function AssetField({ field, value, onChange, readOnly, }) {
51
56
  const many = field.multiple;
52
57
  const ids = many ? assetIdsOf(value) : [];
53
58
  const single = !many ? singleAssetIdOf(value) : null;
54
- // EC-301 per-use sub-fields: with `field.fields` the value stores asset ENVELOPES
55
- // carrying the sub-field siblings (`{ type:'asset', id, caption }`); without them it
56
- // stays bare ids (self-typed on save). Picks/edits preserve a placement's sub-fields.
59
+ // EC-301 per-use sub-fields: the value stores asset ENVELOPES (`{ type:'asset', id }`,
60
+ // EC-586 — the canonical form, sub-fields or not) carrying any sub-field siblings
61
+ // (`caption`). Picks/edits/removes preserve every other placement's sub-fields.
57
62
  const subFields = field.fields ?? [];
58
63
  const hasSub = subFields.length > 0;
59
- // How a pick writes a NEW placement: an envelope when the field has sub-fields,
60
- // else a bare id. The table cell reads the same decision (EC-558).
61
- const form = assetPickForm(field);
62
64
  // The raw `many` entries (envelopes or bare ids), index-aligned with `ids`, so each
63
65
  // gallery item can read/write its own sub-fields.
64
66
  const entries = many && Array.isArray(value) ? value : [];
@@ -78,7 +80,7 @@ export function AssetField({ field, value, onChange, readOnly, }) {
78
80
  return;
79
81
  }
80
82
  if (many) {
81
- const next = appendAssetSelections(value, progress.added, form);
83
+ const next = appendAssetSelections(value, progress.added);
82
84
  if (next !== value)
83
85
  onChange(next);
84
86
  if (progress.settled)
@@ -86,12 +88,12 @@ export function AssetField({ field, value, onChange, readOnly, }) {
86
88
  return;
87
89
  }
88
90
  pendingRef.current = null;
89
- const next = applyAssetSelection(value, progress.added[0], false, form);
91
+ const next = applyAssetSelection(value, progress.added[0], false);
90
92
  if (next !== value)
91
93
  onChange(next);
92
94
  // Reacts to the assets store changing (an upload landing in the library, the
93
95
  // queue draining) — `value`/`onChange` are read from the closure on purpose.
94
- }, [assets, field.accept, many, form, onChange, value]);
96
+ }, [assets, field.accept, many, onChange, value]);
95
97
  function uploadFiles(files) {
96
98
  if (!assets || files.length === 0)
97
99
  return;
@@ -164,7 +166,7 @@ export function AssetField({ field, value, onChange, readOnly, }) {
164
166
  nextEntries[i] = assetNode(aid, next);
165
167
  onChange(nextEntries);
166
168
  }, onRemove: readOnly ? undefined : () => onChange(entries.filter((_, j) => j !== i)) }, `${aid}-${i}`));
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] }));
169
+ }) })) : (_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(removeAssetPlacementAt(value, i)) }, `${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)) }), _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)) })) : (_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] }));
168
170
  }
169
171
  /**
170
172
  * 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 { 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"]}
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, selfTypeFieldValue } 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 removeAssetPlacementAt,\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: stored,\n onChange,\n readOnly,\n}: {\n field: AssetFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n // EC-586/EC-581 (ASSETS D4): the field reads its value through THE canonicaliser — the\n // one cardinality rule — so a lone placement left in a field since declared `multiple`\n // shows as a one-item gallery (and the next pick keeps it) instead of an empty one that\n // drops it; picks and removes then write from that canonical list.\n const value = useMemo(() => selfTypeFieldValue(field, stored), [field, stored])\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: the value stores asset ENVELOPES (`{ type:'asset', id }`,\n // EC-586 — the canonical form, sub-fields or not) carrying any sub-field siblings\n // (`caption`). Picks/edits/removes preserve every other placement's sub-fields.\n const subFields = field.fields ?? []\n const hasSub = subFields.length > 0\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)\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)\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, 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(removeAssetPlacementAt(value, i))}\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))}\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))}\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"]}
@@ -1,6 +1,6 @@
1
1
  import { assetMatchesAccept } from '@elytracms/core/cms-core';
2
2
  import { filterAssets } from '../assets/asset-search.js';
3
- import { appendAssetSelections, applyAssetSelection, assetPickForm, singleAssetIdOf, } from '../assets/asset-selection.js';
3
+ import { appendAssetSelections, applyAssetSelection, singleAssetIdOf, } from '../assets/asset-selection.js';
4
4
  /**
5
5
  * Pure selection/filter core for the asset picker (EC-432). DOM-free and
6
6
  * side-effect-free so the media browser's "what shows", "what an upload may send" and
@@ -104,14 +104,13 @@ export function commitPickerSelectionMany(assetIds, { onChangeMany, onOpenChange
104
104
  * next stored value; the caller decides how to land it (form state, cell commit).
105
105
  */
106
106
  export function assetPickerBinding(field, value, write) {
107
- const form = assetPickForm(field);
108
107
  const multiple = field.multiple;
109
108
  return {
110
109
  value: multiple ? null : singleAssetIdOf(value),
111
110
  ...(field.accept ? { accept: field.accept } : {}),
112
111
  multiple,
113
- onChange: (picked) => write(applyAssetSelection(value, picked, multiple, form)),
114
- ...(multiple ? { onChangeMany: (picked) => write(appendAssetSelections(value, picked, form)) } : {}),
112
+ onChange: (picked) => write(applyAssetSelection(value, picked, multiple)),
113
+ ...(multiple ? { onChangeMany: (picked) => write(appendAssetSelections(value, picked)) } : {}),
115
114
  };
116
115
  }
117
116
  //# sourceMappingURL=asset-picker-core.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"asset-picker-core.js","sourceRoot":"","sources":["../../../src/components/cms/asset-picker-core.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssetFieldDef } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { assetMatchesAccept } from '@elytracms/core/cms-core'\nimport { filterAssets } from '../assets/asset-search'\nimport {\n appendAssetSelections,\n applyAssetSelection,\n assetPickForm,\n singleAssetIdOf,\n} from '../assets/asset-selection'\n\n/**\n * Pure selection/filter core for the asset picker (EC-432). DOM-free and\n * side-effect-free so the media browser's \"what shows\", \"what an upload may send\" and\n * \"what a pick/upload commits\" are unit-testable without a browser — the picker\n * component is just the stage shell around these.\n *\n * EC-588: the picker takes the field's STRUCTURED `accept` list — the one shape the\n * schema declares — and derives everything from it here or in core: the grid filter\n * ({@link visiblePickerAssets}), the drop/upload split ({@link splitByAccept}), the\n * file-input attribute (`assetAcceptAttr`, core), and the commit filter\n * ({@link uploadBatchProgress}). An absent `accept` is unrestricted everywhere; there is\n * no hidden `image/*` default.\n */\n\n/**\n * The stage width preset the media browser mounts with (EC-432 / EC-431 AD-A):\n * the roomy `grid` measure (`min(1240px, 94vw)`) — browsing assets is spatial.\n * Exported so the geometry contract is pinnable without rendering the portal\n * (which is `null` in the node test env).\n */\nexport const ASSET_PICKER_STAGE_PRESET = 'grid' as const\n\n/**\n * The assets shown in the picker grid: the shipped search filter (EC-152) then\n * the `accept` filter (EC-432) — an image field's picker browses only images,\n * never the project's PDFs. Order follows the library.\n */\nexport function visiblePickerAssets(\n assets: readonly AssetRecord[],\n query: string,\n accept: readonly string[] | undefined,\n): AssetRecord[] {\n return filterAssets(assets, query).filter((asset) => assetMatchesAccept(asset, accept))\n}\n\n/**\n * THE drop rule (EC-301 → EC-588): split files about to be uploaded into the ones the\n * field's `accept` admits and the NAMES of the ones it refuses. A drag-drop bypasses\n * the file dialog's soft filter, so every upload path — the field's drop zone, a drop\n * into the open picker, the picker's Upload dialog — passes through this, and the\n * caller names the refused files in a visible notice instead of uploading them.\n */\nexport function splitByAccept<F extends { name: string; type: string }>(\n files: readonly F[],\n accept: readonly string[] | undefined,\n): { accepted: F[]; rejected: string[] } {\n const accepted: F[] = []\n const rejected: string[] = []\n for (const file of files) {\n if (assetMatchesAccept({ contentType: file.type, filename: file.name }, accept)) accepted.push(file)\n else rejected.push(file.name)\n }\n return { accepted, rejected }\n}\n\n/**\n * One upload BATCH (EC-460): the upload-queue ids THIS interaction started.\n *\n * EC-588 A7: it used to be a pre-upload library snapshot, and whatever the library\n * gained since was committed — so an upload from another tab (re-listed with ours) or\n * from a sibling field's drop could land in this field. A batch now names exactly its\n * own uploads, and the store reports which asset each one landed as.\n */\nexport interface UploadBatch {\n uploadIds: readonly string[]\n}\n\n/** What batch progress reads from the assets store (`AssetsStore` satisfies it). */\nexport interface UploadSource {\n assets: readonly AssetRecord[]\n uploads: readonly { id: string; state: { phase: string } }[]\n /** The asset id an upload landed as, once its record is confirmed. */\n landedAssetId(uploadId: string): string | undefined\n}\n\nexport interface UploadBatchProgress {\n /** The batch's landed asset ids that the field accepts, in upload order. */\n added: string[]\n /**\n * The batch is finished: none of its uploads is still running, and every one that\n * landed is in the library. A failed (or dismissed) upload is terminal — it stays in\n * the queue for retry and is never awaited forever. Commit/attach decisions happen\n * only once settled for `many` targets; a single-select target commits on the first\n * arrival.\n */\n settled: boolean\n}\n\n/**\n * Where a batch stands. An upload is in one of four states: running (in the queue,\n * not failed), landed-but-not-listed (the store confirmed it, the library re-list has\n * not arrived), landed (attachable), or terminal (failed / dismissed). Only landed\n * uploads whose RECORD satisfies `accept` are `added` (A7: the commit is filtered,\n * not just the dialog). Pure — the picker and the asset field both drive their\n * effects with this.\n */\nexport function uploadBatchProgress(\n batch: UploadBatch,\n source: UploadSource,\n accept: readonly string[] | undefined,\n): UploadBatchProgress {\n const running = new Set(source.uploads.filter((u) => u.state.phase !== 'failed').map((u) => u.id))\n const library = new Map(source.assets.map((asset) => [asset.id, asset]))\n const added: string[] = []\n let pending = false\n for (const uploadId of batch.uploadIds) {\n if (running.has(uploadId)) {\n pending = true\n continue\n }\n const assetId = source.landedAssetId(uploadId)\n if (assetId === undefined) continue // failed or dismissed — terminal\n const record = library.get(assetId)\n if (!record) {\n pending = true // confirmed, but the library re-list has not landed yet\n continue\n }\n if (assetMatchesAccept(record, accept)) added.push(assetId)\n }\n return { added, settled: !pending }\n}\n\n/** The picker's commit callbacks — a pick fires `onChange` then closes the stage. */\nexport interface PickerCommit {\n onChange: (assetId: string | null) => void\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * Commit a picked asset (or a clear, `assetId === null`) and close the picker —\n * the selection semantic (click = pick, fire `onChange`, close). Both the grid\n * card and the Clear affordance route through this.\n */\nexport function commitPickerSelection(assetId: string | null, { onChange, onOpenChange }: PickerCommit): void {\n onChange(assetId)\n onOpenChange(false)\n}\n\n/** The multi-commit callbacks (EC-460): a settled upload batch on a `many` field. */\nexport interface PickerCommitMany {\n onChangeMany: (assetIds: string[]) => void\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * Commit EVERY asset a settled upload batch added (EC-460) and close the picker —\n * the `many`-field counterpart of {@link commitPickerSelection}, so selecting N\n * files in one picker interaction attaches all N, not just the first to land.\n */\nexport function commitPickerSelectionMany(\n assetIds: string[],\n { onChangeMany, onOpenChange }: PickerCommitMany,\n): void {\n onChangeMany(assetIds)\n onOpenChange(false)\n}\n\n/**\n * The picker props a field's value position needs, derived from the field alone\n * (EC-570 #1 → EC-588): the selected id, the field's structured `accept`, whether it\n * holds many, and the commits — a pick through the carry rule (EC-558), a settled\n * upload batch appended for a `many` field (EC-460).\n */\nexport interface AssetPickerBinding {\n value: string | null\n accept?: readonly string[]\n multiple: boolean\n onChange: (assetId: string | null) => void\n onChangeMany?: (assetIds: string[]) => void\n}\n\n/**\n * ONE picker configuration for every surface that edits an asset field — the detail\n * editor and the table cell — so the two cannot drift. Before, the cell opened the\n * picker with no `accept`, no `multiple` and no batch commit: its grid offered PDFs to\n * an image field and an N-file upload attached one (EC-570 #1). `write` receives the\n * next stored value; the caller decides how to land it (form state, cell commit).\n */\nexport function assetPickerBinding(\n field: Pick<AssetFieldDef, 'multiple' | 'accept' | 'fields'>,\n value: unknown,\n write: (next: unknown) => void,\n): AssetPickerBinding {\n const form = assetPickForm(field)\n const multiple = field.multiple\n return {\n value: multiple ? null : singleAssetIdOf(value),\n ...(field.accept ? { accept: field.accept } : {}),\n multiple,\n onChange: (picked) => write(applyAssetSelection(value, picked, multiple, form)),\n ...(multiple ? { onChangeMany: (picked: string[]) => write(appendAssetSelections(value, picked, form)) } : {}),\n }\n}\n"]}
1
+ {"version":3,"file":"asset-picker-core.js","sourceRoot":"","sources":["../../../src/components/cms/asset-picker-core.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssetFieldDef } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { assetMatchesAccept } from '@elytracms/core/cms-core'\nimport { filterAssets } from '../assets/asset-search'\nimport {\n appendAssetSelections,\n applyAssetSelection,\n singleAssetIdOf,\n} from '../assets/asset-selection'\n\n/**\n * Pure selection/filter core for the asset picker (EC-432). DOM-free and\n * side-effect-free so the media browser's \"what shows\", \"what an upload may send\" and\n * \"what a pick/upload commits\" are unit-testable without a browser — the picker\n * component is just the stage shell around these.\n *\n * EC-588: the picker takes the field's STRUCTURED `accept` list — the one shape the\n * schema declares — and derives everything from it here or in core: the grid filter\n * ({@link visiblePickerAssets}), the drop/upload split ({@link splitByAccept}), the\n * file-input attribute (`assetAcceptAttr`, core), and the commit filter\n * ({@link uploadBatchProgress}). An absent `accept` is unrestricted everywhere; there is\n * no hidden `image/*` default.\n */\n\n/**\n * The stage width preset the media browser mounts with (EC-432 / EC-431 AD-A):\n * the roomy `grid` measure (`min(1240px, 94vw)`) — browsing assets is spatial.\n * Exported so the geometry contract is pinnable without rendering the portal\n * (which is `null` in the node test env).\n */\nexport const ASSET_PICKER_STAGE_PRESET = 'grid' as const\n\n/**\n * The assets shown in the picker grid: the shipped search filter (EC-152) then\n * the `accept` filter (EC-432) — an image field's picker browses only images,\n * never the project's PDFs. Order follows the library.\n */\nexport function visiblePickerAssets(\n assets: readonly AssetRecord[],\n query: string,\n accept: readonly string[] | undefined,\n): AssetRecord[] {\n return filterAssets(assets, query).filter((asset) => assetMatchesAccept(asset, accept))\n}\n\n/**\n * THE drop rule (EC-301 → EC-588): split files about to be uploaded into the ones the\n * field's `accept` admits and the NAMES of the ones it refuses. A drag-drop bypasses\n * the file dialog's soft filter, so every upload path — the field's drop zone, a drop\n * into the open picker, the picker's Upload dialog — passes through this, and the\n * caller names the refused files in a visible notice instead of uploading them.\n */\nexport function splitByAccept<F extends { name: string; type: string }>(\n files: readonly F[],\n accept: readonly string[] | undefined,\n): { accepted: F[]; rejected: string[] } {\n const accepted: F[] = []\n const rejected: string[] = []\n for (const file of files) {\n if (assetMatchesAccept({ contentType: file.type, filename: file.name }, accept)) accepted.push(file)\n else rejected.push(file.name)\n }\n return { accepted, rejected }\n}\n\n/**\n * One upload BATCH (EC-460): the upload-queue ids THIS interaction started.\n *\n * EC-588 A7: it used to be a pre-upload library snapshot, and whatever the library\n * gained since was committed — so an upload from another tab (re-listed with ours) or\n * from a sibling field's drop could land in this field. A batch now names exactly its\n * own uploads, and the store reports which asset each one landed as.\n */\nexport interface UploadBatch {\n uploadIds: readonly string[]\n}\n\n/** What batch progress reads from the assets store (`AssetsStore` satisfies it). */\nexport interface UploadSource {\n assets: readonly AssetRecord[]\n uploads: readonly { id: string; state: { phase: string } }[]\n /** The asset id an upload landed as, once its record is confirmed. */\n landedAssetId(uploadId: string): string | undefined\n}\n\nexport interface UploadBatchProgress {\n /** The batch's landed asset ids that the field accepts, in upload order. */\n added: string[]\n /**\n * The batch is finished: none of its uploads is still running, and every one that\n * landed is in the library. A failed (or dismissed) upload is terminal — it stays in\n * the queue for retry and is never awaited forever. Commit/attach decisions happen\n * only once settled for `many` targets; a single-select target commits on the first\n * arrival.\n */\n settled: boolean\n}\n\n/**\n * Where a batch stands. An upload is in one of four states: running (in the queue,\n * not failed), landed-but-not-listed (the store confirmed it, the library re-list has\n * not arrived), landed (attachable), or terminal (failed / dismissed). Only landed\n * uploads whose RECORD satisfies `accept` are `added` (A7: the commit is filtered,\n * not just the dialog). Pure — the picker and the asset field both drive their\n * effects with this.\n */\nexport function uploadBatchProgress(\n batch: UploadBatch,\n source: UploadSource,\n accept: readonly string[] | undefined,\n): UploadBatchProgress {\n const running = new Set(source.uploads.filter((u) => u.state.phase !== 'failed').map((u) => u.id))\n const library = new Map(source.assets.map((asset) => [asset.id, asset]))\n const added: string[] = []\n let pending = false\n for (const uploadId of batch.uploadIds) {\n if (running.has(uploadId)) {\n pending = true\n continue\n }\n const assetId = source.landedAssetId(uploadId)\n if (assetId === undefined) continue // failed or dismissed — terminal\n const record = library.get(assetId)\n if (!record) {\n pending = true // confirmed, but the library re-list has not landed yet\n continue\n }\n if (assetMatchesAccept(record, accept)) added.push(assetId)\n }\n return { added, settled: !pending }\n}\n\n/** The picker's commit callbacks — a pick fires `onChange` then closes the stage. */\nexport interface PickerCommit {\n onChange: (assetId: string | null) => void\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * Commit a picked asset (or a clear, `assetId === null`) and close the picker —\n * the selection semantic (click = pick, fire `onChange`, close). Both the grid\n * card and the Clear affordance route through this.\n */\nexport function commitPickerSelection(assetId: string | null, { onChange, onOpenChange }: PickerCommit): void {\n onChange(assetId)\n onOpenChange(false)\n}\n\n/** The multi-commit callbacks (EC-460): a settled upload batch on a `many` field. */\nexport interface PickerCommitMany {\n onChangeMany: (assetIds: string[]) => void\n onOpenChange: (open: boolean) => void\n}\n\n/**\n * Commit EVERY asset a settled upload batch added (EC-460) and close the picker —\n * the `many`-field counterpart of {@link commitPickerSelection}, so selecting N\n * files in one picker interaction attaches all N, not just the first to land.\n */\nexport function commitPickerSelectionMany(\n assetIds: string[],\n { onChangeMany, onOpenChange }: PickerCommitMany,\n): void {\n onChangeMany(assetIds)\n onOpenChange(false)\n}\n\n/**\n * The picker props a field's value position needs, derived from the field alone\n * (EC-570 #1 → EC-588): the selected id, the field's structured `accept`, whether it\n * holds many, and the commits — a pick through the carry rule (EC-558), a settled\n * upload batch appended for a `many` field (EC-460).\n */\nexport interface AssetPickerBinding {\n value: string | null\n accept?: readonly string[]\n multiple: boolean\n onChange: (assetId: string | null) => void\n onChangeMany?: (assetIds: string[]) => void\n}\n\n/**\n * ONE picker configuration for every surface that edits an asset field — the detail\n * editor and the table cell — so the two cannot drift. Before, the cell opened the\n * picker with no `accept`, no `multiple` and no batch commit: its grid offered PDFs to\n * an image field and an N-file upload attached one (EC-570 #1). `write` receives the\n * next stored value; the caller decides how to land it (form state, cell commit).\n */\nexport function assetPickerBinding(\n field: Pick<AssetFieldDef, 'multiple' | 'accept' | 'fields'>,\n value: unknown,\n write: (next: unknown) => void,\n): AssetPickerBinding {\n const multiple = field.multiple\n return {\n value: multiple ? null : singleAssetIdOf(value),\n ...(field.accept ? { accept: field.accept } : {}),\n multiple,\n onChange: (picked) => write(applyAssetSelection(value, picked, multiple)),\n ...(multiple ? { onChangeMany: (picked: string[]) => write(appendAssetSelections(value, picked)) } : {}),\n }\n}\n"]}