@elytracms/studio 0.0.38 → 0.0.40

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 (189) hide show
  1. package/convex/cms.ts +8 -2
  2. package/dist/components/app-views/default-modules.d.ts +60 -5
  3. package/dist/components/app-views/default-modules.js +53 -15
  4. package/dist/components/app-views/default-modules.js.map +1 -1
  5. package/dist/components/app-views/module.d.ts +2 -0
  6. package/dist/components/app-views/module.js +8 -0
  7. package/dist/components/app-views/module.js.map +1 -1
  8. package/dist/components/assets/asset-detail.d.ts +24 -0
  9. package/dist/components/assets/asset-detail.js +163 -0
  10. package/dist/components/assets/asset-detail.js.map +1 -0
  11. package/dist/components/assets/asset-grid.d.ts +28 -0
  12. package/dist/components/assets/asset-grid.js +68 -0
  13. package/dist/components/assets/asset-grid.js.map +1 -0
  14. package/dist/components/assets/asset-metadata.d.ts +19 -0
  15. package/dist/components/assets/asset-metadata.js +44 -0
  16. package/dist/components/assets/asset-metadata.js.map +1 -1
  17. package/dist/components/assets/assets-collection-view-source.d.ts +54 -0
  18. package/dist/components/assets/assets-collection-view-source.js +186 -0
  19. package/dist/components/assets/assets-collection-view-source.js.map +1 -0
  20. package/dist/components/assets/assets-module.d.ts +18 -0
  21. package/dist/components/assets/assets-module.js +94 -0
  22. package/dist/components/assets/assets-module.js.map +1 -0
  23. package/dist/components/assets/assets-store.js +2 -7
  24. package/dist/components/assets/assets-store.js.map +1 -1
  25. package/dist/components/assets/assets-view-preference.d.ts +15 -0
  26. package/dist/components/assets/assets-view-preference.js +22 -0
  27. package/dist/components/assets/assets-view-preference.js.map +1 -0
  28. package/dist/components/builder/builder-store.d.ts +12 -0
  29. package/dist/components/builder/builder-store.js +18 -16
  30. package/dist/components/builder/builder-store.js.map +1 -1
  31. package/dist/components/builder/clipboard-actions.js +5 -14
  32. package/dist/components/builder/clipboard-actions.js.map +1 -1
  33. package/dist/components/builder/composition-field-editor-lazy.d.ts +19 -0
  34. package/dist/components/builder/composition-field-editor-lazy.js +29 -0
  35. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -0
  36. package/dist/components/builder/composition-field-editor.js +1 -4
  37. package/dist/components/builder/composition-field-editor.js.map +1 -1
  38. package/dist/components/builder/insert-context.d.ts +8 -0
  39. package/dist/components/builder/insert-context.js +1 -3
  40. package/dist/components/builder/insert-context.js.map +1 -1
  41. package/dist/components/builder/page-tree.js +1 -3
  42. package/dist/components/builder/page-tree.js.map +1 -1
  43. package/dist/components/builder/property-inspector.js +7 -134
  44. package/dist/components/builder/property-inspector.js.map +1 -1
  45. package/dist/components/cms/cms-store.d.ts +15 -4
  46. package/dist/components/cms/cms-store.js +42 -24
  47. package/dist/components/cms/cms-store.js.map +1 -1
  48. package/dist/components/cms/collection-table.d.ts +8 -1
  49. package/dist/components/cms/collection-table.js +75 -11
  50. package/dist/components/cms/collection-table.js.map +1 -1
  51. package/dist/components/cms/column-width.d.ts +25 -0
  52. package/dist/components/cms/column-width.js +47 -0
  53. package/dist/components/cms/column-width.js.map +1 -0
  54. package/dist/components/cms/detail-editor.js +143 -79
  55. package/dist/components/cms/detail-editor.js.map +1 -1
  56. package/dist/components/cms/details-pane.d.ts +21 -8
  57. package/dist/components/cms/details-pane.js +31 -11
  58. package/dist/components/cms/details-pane.js.map +1 -1
  59. package/dist/components/cms/discard-draft.d.ts +32 -0
  60. package/dist/components/cms/discard-draft.js +13 -0
  61. package/dist/components/cms/discard-draft.js.map +1 -0
  62. package/dist/components/cms/document-staging.d.ts +7 -0
  63. package/dist/components/cms/document-staging.js +44 -0
  64. package/dist/components/cms/document-staging.js.map +1 -0
  65. package/dist/components/cms/field-connection.d.ts +22 -0
  66. package/dist/components/cms/field-connection.js +37 -0
  67. package/dist/components/cms/field-connection.js.map +1 -0
  68. package/dist/components/cms/field-control.js +167 -34
  69. package/dist/components/cms/field-control.js.map +1 -1
  70. package/dist/components/cms/prop-fields.d.ts +43 -0
  71. package/dist/components/cms/prop-fields.js +154 -0
  72. package/dist/components/cms/prop-fields.js.map +1 -0
  73. package/dist/components/cms/rich-text/component-embed.d.ts +17 -3
  74. package/dist/components/cms/rich-text/component-embed.js +29 -7
  75. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  76. package/dist/components/cms/rich-text/embed-props.d.ts +49 -0
  77. package/dist/components/cms/rich-text/embed-props.js +106 -0
  78. package/dist/components/cms/rich-text/embed-props.js.map +1 -0
  79. package/dist/components/cms/rich-text/index.d.ts +6 -1
  80. package/dist/components/cms/rich-text/index.js +6 -1
  81. package/dist/components/cms/rich-text/index.js.map +1 -1
  82. package/dist/components/cms/rich-text/lazy.d.ts +19 -0
  83. package/dist/components/cms/rich-text/lazy.js +26 -0
  84. package/dist/components/cms/rich-text/lazy.js.map +1 -0
  85. package/dist/components/cms/rich-text/link-control.js +3 -1
  86. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  87. package/dist/components/cms/rich-text/toolbar-state.d.ts +45 -0
  88. package/dist/components/cms/rich-text/toolbar-state.js +66 -0
  89. package/dist/components/cms/rich-text/toolbar-state.js.map +1 -0
  90. package/dist/components/cms/rich-text/toolbar.js +7 -15
  91. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  92. package/dist/components/cms/row-actions.d.ts +28 -0
  93. package/dist/components/cms/row-actions.js +20 -0
  94. package/dist/components/cms/row-actions.js.map +1 -0
  95. package/dist/components/cms/use-document-versions.d.ts +8 -1
  96. package/dist/components/cms/use-document-versions.js +31 -4
  97. package/dist/components/cms/use-document-versions.js.map +1 -1
  98. package/dist/components/cms/version-compare-lazy.d.ts +1 -0
  99. package/dist/components/cms/version-compare-lazy.js +23 -0
  100. package/dist/components/cms/version-compare-lazy.js.map +1 -0
  101. package/dist/components/cms/version-compare-text.d.ts +46 -0
  102. package/dist/components/cms/version-compare-text.js +78 -0
  103. package/dist/components/cms/version-compare-text.js.map +1 -1
  104. package/dist/components/cms/version-compare.d.ts +9 -5
  105. package/dist/components/cms/version-compare.js +217 -64
  106. package/dist/components/cms/version-compare.js.map +1 -1
  107. package/dist/components/cms/versions-pane.js +10 -4
  108. package/dist/components/cms/versions-pane.js.map +1 -1
  109. package/dist/components/components/component-detail.d.ts +5 -3
  110. package/dist/components/components/component-detail.js +41 -8
  111. package/dist/components/components/component-detail.js.map +1 -1
  112. package/dist/components/components/component-preview.d.ts +97 -0
  113. package/dist/components/components/component-preview.js +202 -0
  114. package/dist/components/components/component-preview.js.map +1 -0
  115. package/dist/components/components/components-index.d.ts +5 -3
  116. package/dist/components/components/components-index.js +19 -24
  117. package/dist/components/components/components-index.js.map +1 -1
  118. package/dist/components/components/registry-browser.d.ts +7 -0
  119. package/dist/components/components/registry-browser.js +20 -36
  120. package/dist/components/components/registry-browser.js.map +1 -1
  121. package/dist/components/components/taxonomy.d.ts +51 -0
  122. package/dist/components/components/taxonomy.js +73 -0
  123. package/dist/components/components/taxonomy.js.map +1 -0
  124. package/dist/components/components/transfer-dialog.d.ts +20 -0
  125. package/dist/components/components/transfer-dialog.js +64 -0
  126. package/dist/components/components/transfer-dialog.js.map +1 -0
  127. package/dist/components/components/transfer.d.ts +43 -0
  128. package/dist/components/components/transfer.js +31 -0
  129. package/dist/components/components/transfer.js.map +1 -0
  130. package/dist/components/project-list.js +3 -4
  131. package/dist/components/project-list.js.map +1 -1
  132. package/dist/components/project-switcher.js +3 -2
  133. package/dist/components/project-switcher.js.map +1 -1
  134. package/dist/components/rail-store-status.d.ts +21 -0
  135. package/dist/components/rail-store-status.js +21 -0
  136. package/dist/components/rail-store-status.js.map +1 -0
  137. package/dist/components/redirects/redirects-module.js +4 -8
  138. package/dist/components/redirects/redirects-module.js.map +1 -1
  139. package/dist/components/redirects/redirects-store.js +2 -7
  140. package/dist/components/redirects/redirects-store.js.map +1 -1
  141. package/dist/components/sign-in-needed-badge.d.ts +6 -0
  142. package/dist/components/sign-in-needed-badge.js +13 -0
  143. package/dist/components/sign-in-needed-badge.js.map +1 -0
  144. package/dist/components/studio-project-screen.js +1 -4
  145. package/dist/components/studio-project-screen.js.map +1 -1
  146. package/dist/components/studio-shell.js +1 -4
  147. package/dist/components/studio-shell.js.map +1 -1
  148. package/dist/components/users/users-module.js +5 -7
  149. package/dist/components/users/users-module.js.map +1 -1
  150. package/dist/components/users/users-store.js +2 -7
  151. package/dist/components/users/users-store.js.map +1 -1
  152. package/dist/lib/auth/index.js +1 -1
  153. package/dist/lib/auth/index.js.map +1 -1
  154. package/dist/lib/clipboard/paste-target.js +1 -3
  155. package/dist/lib/clipboard/paste-target.js.map +1 -1
  156. package/dist/lib/cms-views/view-model.d.ts +0 -2
  157. package/dist/lib/cms-views/view-model.js +0 -7
  158. package/dist/lib/cms-views/view-model.js.map +1 -1
  159. package/dist/lib/diff-view-preference.d.ts +7 -0
  160. package/dist/lib/diff-view-preference.js +21 -0
  161. package/dist/lib/diff-view-preference.js.map +1 -0
  162. package/dist/lib/i18n/messages.en.js +94 -33
  163. package/dist/lib/i18n/messages.en.js.map +1 -1
  164. package/dist/lib/local-preference.d.ts +11 -0
  165. package/dist/lib/local-preference.js +26 -0
  166. package/dist/lib/local-preference.js.map +1 -0
  167. package/dist/lib/locale/fallback.d.ts +45 -0
  168. package/dist/lib/locale/fallback.js +85 -0
  169. package/dist/lib/locale/fallback.js.map +1 -1
  170. package/dist/lib/persona/labels.d.ts +6 -0
  171. package/dist/lib/persona/labels.js +8 -0
  172. package/dist/lib/persona/labels.js.map +1 -1
  173. package/dist/lib/query/index.d.ts +1 -1
  174. package/dist/lib/query/index.js +1 -1
  175. package/dist/lib/query/index.js.map +1 -1
  176. package/dist/lib/query/operations-query.d.ts +14 -2
  177. package/dist/lib/query/operations-query.js +11 -6
  178. package/dist/lib/query/operations-query.js.map +1 -1
  179. package/dist/lib/rail.d.ts +0 -6
  180. package/dist/lib/rail.js +6 -25
  181. package/dist/lib/rail.js.map +1 -1
  182. package/dist/lib/theme.d.ts +0 -6
  183. package/dist/lib/theme.js +10 -13
  184. package/dist/lib/theme.js.map +1 -1
  185. package/dist/router/match.d.ts +1 -0
  186. package/dist/router/match.js +4 -0
  187. package/dist/router/match.js.map +1 -1
  188. package/dist/ui/button.d.ts +1 -1
  189. package/package.json +22 -25
@@ -0,0 +1,43 @@
1
+ import type { JsonValue, PropValue } from '@elytracms/core/project-graph';
2
+ import type { PropField } from '@elytracms/core/component-registry';
3
+ /**
4
+ * The EC-190 "props-as-fields" value control — the single renderer for a
5
+ * manifest-declared prop field, shared by the canvas property inspector
6
+ * (`builder/property-inspector.tsx`) and the rich-text component-embed prop editor
7
+ * (`rich-text/embed-props.tsx`). Extracted here so both surfaces render prop fields
8
+ * through *one* control set (asset → library picker, relation → document picker,
9
+ * object → nested form, …) rather than two parallel form systems.
10
+ *
11
+ * It is deliberately store-agnostic: it reads a prop's current value and reports
12
+ * edits through `onSet(rawValue)` / `onClear()`. The caller owns *where* the value
13
+ * lands — the inspector dispatches a builder command; the embed editor rewrites a
14
+ * ProseMirror node attr — and is responsible for wrapping the raw value in a
15
+ * `staticProp` envelope.
16
+ */
17
+ /**
18
+ * The inspector control to render for a prop field-def. The `control.kind`
19
+ * vocabulary derives from the field `type` plus the optional `form.control`
20
+ * presentation hint on `text` fields.
21
+ */
22
+ export type ControlKind = 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'json' | 'color' | 'richtext' | 'asset' | 'object' | 'relation';
23
+ export declare function controlKind(spec: PropField): ControlKind;
24
+ export interface PropValueControlProps {
25
+ /** DOM id for the control's primary input (label `htmlFor` target). */
26
+ id: string;
27
+ /** The prop's field name — needed to synthesize the `FieldDef` for reused controls. */
28
+ name: string;
29
+ /** The manifest prop field-def driving the control kind + validation. */
30
+ spec: PropField;
31
+ /** The prop's current value envelope (or `undefined` when unset). */
32
+ value: PropValue | undefined;
33
+ /** Commit a new raw value (the caller wraps it in a `staticProp`). */
34
+ onSet: (value: JsonValue) => void;
35
+ /** Remove the prop (revert to the manifest default). */
36
+ onClear: () => void;
37
+ }
38
+ /**
39
+ * Render + edit a single manifest prop value. Store-agnostic: emits through
40
+ * `onSet`/`onClear`. Shows a validation issue when the current value fails the
41
+ * prop's field-def validator.
42
+ */
43
+ export declare function PropValueControl({ id, name, spec, value, onSet, onClear }: PropValueControlProps): import("react").JSX.Element;
@@ -0,0 +1,154 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { isStaticProp } from '@elytracms/core/project-graph';
4
+ import { fieldValueValidator } from '@elytracms/core/cms-core';
5
+ import { Input, Row, Select, Stack, Text } from '@elytracms/studio/ui';
6
+ import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
7
+ import { FieldControl } from './field-control';
8
+ import { FieldDensityProvider } from './field-density';
9
+ import { useOptionalCollectionView } from '../collection-view/collection-view-context';
10
+ export function controlKind(spec) {
11
+ switch (spec.type) {
12
+ case 'boolean':
13
+ return 'boolean';
14
+ case 'number':
15
+ return 'number';
16
+ case 'select':
17
+ return 'select';
18
+ case 'richText':
19
+ return 'richtext';
20
+ // EC-195: an `asset`-typed prop (e.g. the Image `src`) picks from the asset
21
+ // library — never a raw asset id typed into a text box (the EC-192 persona).
22
+ case 'asset':
23
+ return 'asset';
24
+ // EC-253: an `object`/repeater prop reuses the CMS repeater control below.
25
+ case 'object':
26
+ return 'object';
27
+ // EC-262: a `relation`-typed prop (e.g. a ProductGrid's `category`) reuses the
28
+ // CMS relation picker — never a text box that stringifies the ref to "[object Object]".
29
+ case 'relation':
30
+ return 'relation';
31
+ case 'text': {
32
+ const c = spec.form?.control;
33
+ if (c === 'textarea')
34
+ return 'textarea';
35
+ if (c === 'color')
36
+ return 'color';
37
+ if (c === 'json')
38
+ return 'json';
39
+ return 'text';
40
+ }
41
+ default:
42
+ return 'text';
43
+ }
44
+ }
45
+ /**
46
+ * Render + edit a single manifest prop value. Store-agnostic: emits through
47
+ * `onSet`/`onClear`. Shows a validation issue when the current value fails the
48
+ * prop's field-def validator.
49
+ */
50
+ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
51
+ const current = isStaticProp(value) ? value.value : spec.default;
52
+ // The `asset`/`relation`/`object` controls delegate to the CMS `FieldControl`,
53
+ // which reads the ambient `CollectionViewSource` (asset library, relation
54
+ // candidates, nested form). The component-registry preview surface has no
55
+ // document/collection context, so outside a provider those controls must degrade
56
+ // to a read-only value display instead of throwing "useCollectionView must be
57
+ // used within a <CollectionViewProvider>" (the Konditorei Components-view crash).
58
+ const hasWorkspaceContext = useOptionalCollectionView() != null;
59
+ // EC-190: validate with the prop's field-def value validator.
60
+ const valid = current === undefined || fieldValueValidator({ ...spec, name })(current);
61
+ // EC-190: derive the editor control from the field `type` + optional `form.control`.
62
+ const kind = controlKind(spec);
63
+ // Select options live on the `select` field-def.
64
+ const options = spec.type === 'select' ? spec.options : [];
65
+ let control;
66
+ if (kind === 'boolean') {
67
+ control = (_jsxs("label", { className: "flex h-8 items-center gap-2 text-sm", children: [_jsx("input", { id: id, type: "checkbox", checked: current === true, onChange: (e) => onSet(e.target.checked), className: "size-4 accent-primary" }), _jsx(Text, { size: "sm", tone: "muted", children: current === true ? translate('common.true') : translate('common.false') })] }));
68
+ }
69
+ else if (kind === 'select') {
70
+ control = (_jsx(Select, { id: id, value: String(current ?? ''), onChange: (e) => {
71
+ // EC-190: select option values are strings (field-def `options`).
72
+ const opt = options.find((o) => o.value === e.target.value);
73
+ onSet(opt ? opt.value : e.target.value);
74
+ }, children: options.map((opt) => (_jsx("option", { value: opt.value, children: localizedLabel(opt.label, opt.value) }, opt.value))) }));
75
+ }
76
+ else if (kind === 'number') {
77
+ control = _jsx(NumberControl, { id: id, current: current, onSet: onSet, onClear: onClear });
78
+ }
79
+ else if (kind === 'json') {
80
+ control = _jsx(JsonControl, { id: id, current: current, onSet: onSet });
81
+ }
82
+ else if (kind === 'color') {
83
+ control = (_jsxs(Row, { gap: "2", children: [_jsx("input", { id: id, type: "color", value: typeof current === 'string' && /^#[0-9a-f]{6}$/i.test(current) ? current : '#000000', onChange: (e) => onSet(e.target.value), className: "h-8 w-10 shrink-0 rounded-md border border-input bg-background" }), _jsx(Input, { value: typeof current === 'string' ? current : '', onChange: (e) => onSet(e.target.value) })] }));
84
+ }
85
+ else if (kind === 'textarea' || kind === 'richtext') {
86
+ control = (_jsx("textarea", { id: id, rows: 2, value: typeof current === 'string' ? current : '', onChange: (e) => onSet(e.target.value), className: "flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none" }));
87
+ }
88
+ else if (kind === 'asset' || kind === 'object' || kind === 'relation') {
89
+ // EC-195/EC-253/EC-262: reuse the CMS field control. `asset` = library picker +
90
+ // preview; `object` = the nested/repeater form; `relation` = the document picker
91
+ // (resolves the ref to its title, instead of stringifying it to "[object Object]").
92
+ // All edit a structured static prop value through the same shared control documents use.
93
+ // Without a workspace context (the Components preview), degrade to a read-only
94
+ // value display so the pane renders instead of throwing.
95
+ control = hasWorkspaceContext ? (_jsx(FieldDensityProvider, { density: "compact", children: _jsx(FieldControl, { id: id, field: { ...spec, name }, value: current, onChange: (v) => (v === undefined || v === null ? onClear() : onSet(v)), readOnly: false }) })) : (_jsx(WorkspaceValueFallback, { value: current }));
96
+ }
97
+ else {
98
+ control = (_jsx(Input, { id: id, value: typeof current === 'string' ? current : String(current ?? ''), onChange: (e) => onSet(e.target.value) }));
99
+ }
100
+ return (_jsxs(Stack, { gap: "1", children: [control, !valid ? (_jsx(Text, { size: "xs", className: "text-destructive", children: translate('builder.inspector.invalidValue') })) : null] }));
101
+ }
102
+ /**
103
+ * Read-only stand-in for an `asset`/`relation`/`object` prop when there is no
104
+ * ambient `CollectionViewSource` (the component-registry preview, which has no
105
+ * document/collection context). Shows the current value and a hint that it is
106
+ * editable inside a document — never the live picker, which needs a workspace.
107
+ */
108
+ function WorkspaceValueFallback({ value }) {
109
+ const text = value === undefined || value === null
110
+ ? '—'
111
+ : typeof value === 'string'
112
+ ? value
113
+ : JSON.stringify(value);
114
+ return (_jsxs(Stack, { gap: "1", children: [_jsx("div", { className: "truncate rounded-md border border-input bg-muted/30 px-2.5 py-1.5 text-sm text-muted-foreground", children: text }), _jsx(Text, { size: "xs", tone: "muted", children: translate('components.preview.propNeedsDocument') })] }));
115
+ }
116
+ /** Number input with a local buffer: commits only finite values, never NaN; empty clears the prop. */
117
+ function NumberControl({ id, current, onSet, onClear, }) {
118
+ const [raw, setRaw] = useState(typeof current === 'number' ? String(current) : '');
119
+ useEffect(() => {
120
+ setRaw(typeof current === 'number' ? String(current) : '');
121
+ }, [current]);
122
+ return (_jsx(Input, { id: id, type: "number", value: raw, onChange: (e) => {
123
+ const v = e.target.value;
124
+ setRaw(v);
125
+ if (v.trim() === '') {
126
+ onClear();
127
+ return;
128
+ }
129
+ const n = Number(v);
130
+ if (Number.isFinite(n))
131
+ onSet(n);
132
+ } }));
133
+ }
134
+ /** JSON textarea with a local buffer: commits only when the text parses; flags parse errors. */
135
+ function JsonControl({ id, current, onSet, }) {
136
+ const [raw, setRaw] = useState(() => JSON.stringify(current ?? null));
137
+ const [error, setError] = useState(null);
138
+ useEffect(() => {
139
+ setRaw(JSON.stringify(current ?? null));
140
+ setError(null);
141
+ }, [current]);
142
+ return (_jsxs(Stack, { gap: "1", children: [_jsx("textarea", { id: id, rows: 3, value: raw, onChange: (e) => {
143
+ const v = e.target.value;
144
+ setRaw(v);
145
+ try {
146
+ onSet(JSON.parse(v));
147
+ setError(null);
148
+ }
149
+ catch {
150
+ setError(translate('builder.inspector.invalidJson'));
151
+ }
152
+ }, className: "flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none" }), error ? (_jsx(Text, { size: "xs", className: "text-destructive", children: error })) : null] }));
153
+ }
154
+ //# sourceMappingURL=prop-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prop-fields.js","sourceRoot":"","sources":["../../../src/components/cms/prop-fields.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAG5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAA;AAmCtF,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAA;QAClB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAA;QACjB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAA;QACjB,KAAK,UAAU;YACb,OAAO,UAAU,CAAA;QACnB,4EAA4E;QAC5E,6EAA6E;QAC7E,KAAK,OAAO;YACV,OAAO,OAAO,CAAA;QAChB,2EAA2E;QAC3E,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAA;QACjB,+EAA+E;QAC/E,wFAAwF;QACxF,KAAK,UAAU;YACb,OAAO,UAAU,CAAA;QACnB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAA;YAC5B,IAAI,CAAC,KAAK,UAAU;gBAAE,OAAO,UAAU,CAAA;YACvC,IAAI,CAAC,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAA;YACjC,IAAI,CAAC,KAAK,MAAM;gBAAE,OAAO,MAAM,CAAA;YAC/B,OAAO,MAAM,CAAA;QACf,CAAC;QACD;YACE,OAAO,MAAM,CAAA;IACjB,CAAC;AACH,CAAC;AAiBD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAyB;IAC/F,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAE,IAAI,CAAC,OAAiC,CAAA;IAC3F,+EAA+E;IAC/E,0EAA0E;IAC1E,0EAA0E;IAC1E,iFAAiF;IACjF,8EAA8E;IAC9E,kFAAkF;IAClF,MAAM,mBAAmB,GAAG,yBAAyB,EAAE,IAAI,IAAI,CAAA;IAE/D,8DAA8D;IAC9D,MAAM,KAAK,GAAG,OAAO,KAAK,SAAS,IAAI,mBAAmB,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAc,CAAC,CAAC,OAAO,CAAC,CAAA;IAClG,qFAAqF;IACrF,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC9B,iDAAiD;IACjD,MAAM,OAAO,GAA4B,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IAEnF,IAAI,OAAO,CAAA;IACX,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,GAAG,CACR,iBAAO,SAAS,EAAC,qCAAqC,aACpD,gBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,OAAO,KAAK,IAAI,EACzB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EACxC,SAAS,EAAC,uBAAuB,GACjC,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,GACnE,IACD,CACT,CAAA;IACH,CAAC;SAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,GAAG,CACR,KAAC,MAAM,IACL,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EAC5B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;gBACd,kEAAkE;gBAClE,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gBAC3D,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACzC,CAAC,YAEA,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CACpB,iBAAwB,KAAK,EAAE,GAAG,CAAC,KAAK,YACrC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAD1B,GAAG,CAAC,KAAK,CAEb,CACV,CAAC,GACK,CACV,CAAA;IACH,CAAC;SAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,GAAG,KAAC,aAAa,IAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CAAA;IACvF,CAAC;SAAM,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,OAAO,GAAG,KAAC,WAAW,IAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAI,CAAA;IACnE,CAAC;SAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,OAAO,GAAG,CACR,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,gBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,OAAO,EACZ,KAAK,EAAE,OAAO,OAAO,KAAK,QAAQ,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAC3F,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACtC,SAAS,EAAC,gEAAgE,GAC1E,EACF,KAAC,KAAK,IAAC,KAAK,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAI,IAChG,CACP,CAAA;IACH,CAAC;SAAM,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACtD,OAAO,GAAG,CACR,mBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EACjD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACtC,SAAS,EAAC,iMAAiM,GAC3M,CACH,CAAA;IACH,CAAC;SAAM,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxE,gFAAgF;QAChF,iFAAiF;QACjF,oFAAoF;QACpF,yFAAyF;QACzF,+EAA+E;QAC/E,yDAAyD;QACzD,OAAO,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAC9B,KAAC,oBAAoB,IAAC,OAAO,EAAC,SAAS,YACrC,KAAC,YAAY,IACX,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAc,EACpC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAc,CAAC,CAAC,EACpF,QAAQ,EAAE,KAAK,GACf,GACmB,CACxB,CAAC,CAAC,CAAC,CACF,KAAC,sBAAsB,IAAC,KAAK,EAAE,OAAO,GAAI,CAC3C,CAAA;IACH,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CACR,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EACpE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GACtC,CACH,CAAA;IACH,CAAC;IAED,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACX,OAAO,EACP,CAAC,KAAK,CAAC,CAAC,CAAC,CACR,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,YACzC,SAAS,CAAC,gCAAgC,CAAC,GACvC,CACR,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,sBAAsB,CAAC,EAAE,KAAK,EAAoC;IACzE,MAAM,IAAI,GACR,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QACnC,CAAC,CAAC,GAAG;QACL,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ;YACzB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC7B,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,cAAK,SAAS,EAAC,iGAAiG,YAC7G,IAAI,GACD,EACN,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,sCAAsC,CAAC,GAC7C,IACD,CACT,CAAA;AACH,CAAC;AAED,sGAAsG;AACtG,SAAS,aAAa,CAAC,EACrB,EAAE,EACF,OAAO,EACP,KAAK,EACL,OAAO,GAMR;IACC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAClF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC5D,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IACb,OAAO,CACL,KAAC,KAAK,IACJ,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACd,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAA;YACxB,MAAM,CAAC,CAAC,CAAC,CAAA;YACT,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACpB,OAAO,EAAE,CAAA;gBACT,OAAM;YACR,CAAC;YACD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;YACnB,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAAE,KAAK,CAAC,CAAC,CAAC,CAAA;QAClC,CAAC,GACD,CACH,CAAA;AACH,CAAC;AAED,gGAAgG;AAChG,SAAS,WAAW,CAAC,EACnB,EAAE,EACF,OAAO,EACP,KAAK,GAKN;IACC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAA;IACrE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAA;QACvC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAChB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IACb,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,mBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;oBACd,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAA;oBACxB,MAAM,CAAC,CAAC,CAAC,CAAA;oBACT,IAAI,CAAC;wBACH,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAc,CAAC,CAAA;wBACjC,QAAQ,CAAC,IAAI,CAAC,CAAA;oBAChB,CAAC;oBAAC,MAAM,CAAC;wBACP,QAAQ,CAAC,SAAS,CAAC,+BAA+B,CAAC,CAAC,CAAA;oBACtD,CAAC;gBACH,CAAC,EACD,SAAS,EAAC,2MAA2M,GACrN,EACD,KAAK,CAAC,CAAC,CAAC,CACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,YACzC,KAAK,GACD,CACR,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC","sourcesContent":["import { useEffect, useState } from 'react'\nimport { isStaticProp } from '@elytracms/core/project-graph'\nimport type { JsonValue, PropValue } from '@elytracms/core/project-graph'\nimport type { PropField } from '@elytracms/core/component-registry'\nimport { fieldValueValidator } from '@elytracms/core/cms-core'\nimport type { FieldDef, SelectOption } from '@elytracms/core/cms-core'\nimport { Input, Row, Select, Stack, Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { FieldControl } from './field-control'\nimport { FieldDensityProvider } from './field-density'\nimport { useOptionalCollectionView } from '../collection-view/collection-view-context'\n\n/**\n * The EC-190 \"props-as-fields\" value control — the single renderer for a\n * manifest-declared prop field, shared by the canvas property inspector\n * (`builder/property-inspector.tsx`) and the rich-text component-embed prop editor\n * (`rich-text/embed-props.tsx`). Extracted here so both surfaces render prop fields\n * through *one* control set (asset → library picker, relation → document picker,\n * object → nested form, …) rather than two parallel form systems.\n *\n * It is deliberately store-agnostic: it reads a prop's current value and reports\n * edits through `onSet(rawValue)` / `onClear()`. The caller owns *where* the value\n * lands — the inspector dispatches a builder command; the embed editor rewrites a\n * ProseMirror node attr — and is responsible for wrapping the raw value in a\n * `staticProp` envelope.\n */\n\n/**\n * The inspector control to render for a prop field-def. The `control.kind`\n * vocabulary derives from the field `type` plus the optional `form.control`\n * presentation hint on `text` fields.\n */\nexport type ControlKind =\n | 'text'\n | 'textarea'\n | 'number'\n | 'boolean'\n | 'select'\n | 'json'\n | 'color'\n | 'richtext'\n | 'asset'\n | 'object'\n | 'relation'\n\nexport function controlKind(spec: PropField): ControlKind {\n switch (spec.type) {\n case 'boolean':\n return 'boolean'\n case 'number':\n return 'number'\n case 'select':\n return 'select'\n case 'richText':\n return 'richtext'\n // EC-195: an `asset`-typed prop (e.g. the Image `src`) picks from the asset\n // library — never a raw asset id typed into a text box (the EC-192 persona).\n case 'asset':\n return 'asset'\n // EC-253: an `object`/repeater prop reuses the CMS repeater control below.\n case 'object':\n return 'object'\n // EC-262: a `relation`-typed prop (e.g. a ProductGrid's `category`) reuses the\n // CMS relation picker — never a text box that stringifies the ref to \"[object Object]\".\n case 'relation':\n return 'relation'\n case 'text': {\n const c = spec.form?.control\n if (c === 'textarea') return 'textarea'\n if (c === 'color') return 'color'\n if (c === 'json') return 'json'\n return 'text'\n }\n default:\n return 'text'\n }\n}\n\nexport interface PropValueControlProps {\n /** DOM id for the control's primary input (label `htmlFor` target). */\n id: string\n /** The prop's field name — needed to synthesize the `FieldDef` for reused controls. */\n name: string\n /** The manifest prop field-def driving the control kind + validation. */\n spec: PropField\n /** The prop's current value envelope (or `undefined` when unset). */\n value: PropValue | undefined\n /** Commit a new raw value (the caller wraps it in a `staticProp`). */\n onSet: (value: JsonValue) => void\n /** Remove the prop (revert to the manifest default). */\n onClear: () => void\n}\n\n/**\n * Render + edit a single manifest prop value. Store-agnostic: emits through\n * `onSet`/`onClear`. Shows a validation issue when the current value fails the\n * prop's field-def validator.\n */\nexport function PropValueControl({ id, name, spec, value, onSet, onClear }: PropValueControlProps) {\n const current = isStaticProp(value) ? value.value : (spec.default as JsonValue | undefined)\n // The `asset`/`relation`/`object` controls delegate to the CMS `FieldControl`,\n // which reads the ambient `CollectionViewSource` (asset library, relation\n // candidates, nested form). The component-registry preview surface has no\n // document/collection context, so outside a provider those controls must degrade\n // to a read-only value display instead of throwing \"useCollectionView must be\n // used within a <CollectionViewProvider>\" (the Konditorei Components-view crash).\n const hasWorkspaceContext = useOptionalCollectionView() != null\n\n // EC-190: validate with the prop's field-def value validator.\n const valid = current === undefined || fieldValueValidator({ ...spec, name } as FieldDef)(current)\n // EC-190: derive the editor control from the field `type` + optional `form.control`.\n const kind = controlKind(spec)\n // Select options live on the `select` field-def.\n const options: readonly SelectOption[] = spec.type === 'select' ? spec.options : []\n\n let control\n if (kind === 'boolean') {\n control = (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={current === true}\n onChange={(e) => onSet(e.target.checked)}\n className=\"size-4 accent-primary\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {current === true ? translate('common.true') : translate('common.false')}\n </Text>\n </label>\n )\n } else if (kind === 'select') {\n control = (\n <Select\n id={id}\n value={String(current ?? '')}\n onChange={(e) => {\n // EC-190: select option values are strings (field-def `options`).\n const opt = options.find((o) => o.value === e.target.value)\n onSet(opt ? opt.value : e.target.value)\n }}\n >\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n } else if (kind === 'number') {\n control = <NumberControl id={id} current={current} onSet={onSet} onClear={onClear} />\n } else if (kind === 'json') {\n control = <JsonControl id={id} current={current} onSet={onSet} />\n } else if (kind === 'color') {\n control = (\n <Row gap=\"2\">\n <input\n id={id}\n type=\"color\"\n value={typeof current === 'string' && /^#[0-9a-f]{6}$/i.test(current) ? current : '#000000'}\n onChange={(e) => onSet(e.target.value)}\n className=\"h-8 w-10 shrink-0 rounded-md border border-input bg-background\"\n />\n <Input value={typeof current === 'string' ? current : ''} onChange={(e) => onSet(e.target.value)} />\n </Row>\n )\n } else if (kind === 'textarea' || kind === 'richtext') {\n control = (\n <textarea\n id={id}\n rows={2}\n value={typeof current === 'string' ? current : ''}\n onChange={(e) => onSet(e.target.value)}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none\"\n />\n )\n } else if (kind === 'asset' || kind === 'object' || kind === 'relation') {\n // EC-195/EC-253/EC-262: reuse the CMS field control. `asset` = library picker +\n // preview; `object` = the nested/repeater form; `relation` = the document picker\n // (resolves the ref to its title, instead of stringifying it to \"[object Object]\").\n // All edit a structured static prop value through the same shared control documents use.\n // Without a workspace context (the Components preview), degrade to a read-only\n // value display so the pane renders instead of throwing.\n control = hasWorkspaceContext ? (\n <FieldDensityProvider density=\"compact\">\n <FieldControl\n id={id}\n field={{ ...spec, name } as FieldDef}\n value={current}\n onChange={(v) => (v === undefined || v === null ? onClear() : onSet(v as JsonValue))}\n readOnly={false}\n />\n </FieldDensityProvider>\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else {\n control = (\n <Input\n id={id}\n value={typeof current === 'string' ? current : String(current ?? '')}\n onChange={(e) => onSet(e.target.value)}\n />\n )\n }\n\n return (\n <Stack gap=\"1\">\n {control}\n {!valid ? (\n <Text size=\"xs\" className=\"text-destructive\">\n {translate('builder.inspector.invalidValue')}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * Read-only stand-in for an `asset`/`relation`/`object` prop when there is no\n * ambient `CollectionViewSource` (the component-registry preview, which has no\n * document/collection context). Shows the current value and a hint that it is\n * editable inside a document — never the live picker, which needs a workspace.\n */\nfunction WorkspaceValueFallback({ value }: { value: JsonValue | undefined }) {\n const text =\n value === undefined || value === null\n ? '—'\n : typeof value === 'string'\n ? value\n : JSON.stringify(value)\n return (\n <Stack gap=\"1\">\n <div className=\"truncate rounded-md border border-input bg-muted/30 px-2.5 py-1.5 text-sm text-muted-foreground\">\n {text}\n </div>\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.preview.propNeedsDocument')}\n </Text>\n </Stack>\n )\n}\n\n/** Number input with a local buffer: commits only finite values, never NaN; empty clears the prop. */\nfunction NumberControl({\n id,\n current,\n onSet,\n onClear,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: number) => void\n onClear: () => void\n}) {\n const [raw, setRaw] = useState(typeof current === 'number' ? String(current) : '')\n useEffect(() => {\n setRaw(typeof current === 'number' ? String(current) : '')\n }, [current])\n return (\n <Input\n id={id}\n type=\"number\"\n value={raw}\n onChange={(e) => {\n const v = e.target.value\n setRaw(v)\n if (v.trim() === '') {\n onClear()\n return\n }\n const n = Number(v)\n if (Number.isFinite(n)) onSet(n)\n }}\n />\n )\n}\n\n/** JSON textarea with a local buffer: commits only when the text parses; flags parse errors. */\nfunction JsonControl({\n id,\n current,\n onSet,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: JsonValue) => void\n}) {\n const [raw, setRaw] = useState(() => JSON.stringify(current ?? null))\n const [error, setError] = useState<string | null>(null)\n useEffect(() => {\n setRaw(JSON.stringify(current ?? null))\n setError(null)\n }, [current])\n return (\n <Stack gap=\"1\">\n <textarea\n id={id}\n rows={3}\n value={raw}\n onChange={(e) => {\n const v = e.target.value\n setRaw(v)\n try {\n onSet(JSON.parse(v) as JsonValue)\n setError(null)\n } catch {\n setError(translate('builder.inspector.invalidJson'))\n }\n }}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none\"\n />\n {error ? (\n <Text size=\"xs\" className=\"text-destructive\">\n {error}\n </Text>\n ) : null}\n </Stack>\n )\n}\n"]}
@@ -8,8 +8,22 @@ import { Node } from '@tiptap/react';
8
8
  * so the extractor still indexes the embedded component's references, and the delivery
9
9
  * renderer renders it exactly as a `blocks` value.
10
10
  *
11
- * The editor shows a read-only placeholder card naming the embedded component (a live
12
- * in-editor preview would need the runtime renderer + resolvers; deferred). Insert and
13
- * deeper prop editing are wired from the toolbar / inspector.
11
+ * The editor shows a placeholder card naming the embedded component (a live in-editor
12
+ * preview would need the runtime renderer + resolvers; deferred). Insertion is wired
13
+ * from the toolbar / slash menu; the card's Edit affordance opens the EC-314 phase-4
14
+ * prop editor ({@link EmbedPropsDialog}), which rewrites `attrs.node` through a proper
15
+ * ProseMirror transaction (undoable). `readOnly` renders an inert card (no edit/delete).
14
16
  */
15
17
  export declare const ComponentEmbed: Node<any, any>;
18
+ /**
19
+ * The presentational embed card — component identity, an optional one-line prop
20
+ * summary, and (only when a handler is supplied) edit / delete affordances. It is
21
+ * kept free of TipTap and registry context so it renders + asserts standalone: an
22
+ * inert (read-only) card is simply the card with no `onEdit`/`onDelete`.
23
+ */
24
+ export declare function EmbedCard({ displayName, summary, onEdit, onDelete, }: {
25
+ displayName: string;
26
+ summary?: string | undefined;
27
+ onEdit?: (() => void) | undefined;
28
+ onDelete?: (() => void) | undefined;
29
+ }): import("react").JSX.Element;
@@ -1,10 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react';
3
- import { Blocks, Trash2 } from 'lucide-react';
4
+ import { Blocks, Pencil, Trash2 } from 'lucide-react';
4
5
  import { COMPONENT_EMBED_NODE_TYPE, embeddedComponentNode } from '@elytracms/core/rich-text';
6
+ import { getStudioRegistry } from '@elytracms/studio/lib/registry';
5
7
  import { Button } from '@elytracms/studio/ui';
6
8
  import { translate } from '@elytracms/studio/lib/i18n';
9
+ import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
7
10
  import { cn } from '@elytracms/studio/lib/utils';
11
+ import { EmbedPropsDialog, embedPropsSummary } from './embed-props';
8
12
  /**
9
13
  * The rich-text component-embed node (EC-189/EC-314): a block atom holding a single
10
14
  * composition value at `attrs.node`. It is the third place constrained composition
@@ -14,9 +18,11 @@ import { cn } from '@elytracms/studio/lib/utils';
14
18
  * so the extractor still indexes the embedded component's references, and the delivery
15
19
  * renderer renders it exactly as a `blocks` value.
16
20
  *
17
- * The editor shows a read-only placeholder card naming the embedded component (a live
18
- * in-editor preview would need the runtime renderer + resolvers; deferred). Insert and
19
- * deeper prop editing are wired from the toolbar / inspector.
21
+ * The editor shows a placeholder card naming the embedded component (a live in-editor
22
+ * preview would need the runtime renderer + resolvers; deferred). Insertion is wired
23
+ * from the toolbar / slash menu; the card's Edit affordance opens the EC-314 phase-4
24
+ * prop editor ({@link EmbedPropsDialog}), which rewrites `attrs.node` through a proper
25
+ * ProseMirror transaction (undoable). `readOnly` renders an inert card (no edit/delete).
20
26
  */
21
27
  export const ComponentEmbed = Node.create({
22
28
  name: COMPONENT_EMBED_NODE_TYPE,
@@ -45,10 +51,26 @@ function readEmbedded(attrNode) {
45
51
  attrs: { node: attrNode },
46
52
  });
47
53
  }
48
- function ComponentEmbedView({ node, deleteNode, selected, editor }) {
54
+ /**
55
+ * The presentational embed card — component identity, an optional one-line prop
56
+ * summary, and (only when a handler is supplied) edit / delete affordances. It is
57
+ * kept free of TipTap and registry context so it renders + asserts standalone: an
58
+ * inert (read-only) card is simply the card with no `onEdit`/`onDelete`.
59
+ */
60
+ export function EmbedCard({ displayName, summary, onEdit, onDelete, }) {
61
+ return (_jsxs("div", { className: "flex items-center gap-2.5 rounded-md border border-border bg-muted/40 px-3 py-2.5 text-sm", children: [_jsx(Blocks, { className: "size-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate font-medium text-foreground", children: displayName }), summary ? _jsx("span", { className: "truncate text-xs text-muted-foreground", children: summary }) : null] }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: translate('richText.embed.badge') }), onEdit ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", contentEditable: false, "aria-label": translate('richText.embed.editProps'), onClick: onEdit, children: _jsx(Pencil, { className: "size-3.5" }) })) : null, onDelete ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", contentEditable: false, "aria-label": translate('common.remove'), onClick: onDelete, children: _jsx(Trash2, { className: "size-3.5" }) })) : null] }));
62
+ }
63
+ function ComponentEmbedView({ node, updateAttributes, deleteNode, selected, editor }) {
49
64
  const embedded = readEmbedded(node.attrs.node);
50
- const componentId = embedded?.componentId ?? translate('richText.embed.unknown');
65
+ const manifest = embedded ? getStudioRegistry().registry.getManifest(embedded.componentId) : undefined;
51
66
  const editable = editor.isEditable;
52
- return (_jsxs(NodeViewWrapper, { "data-drag-handle": true, className: cn('my-2 flex items-center gap-2.5 rounded-md border border-border bg-muted/40 px-3 py-2.5 text-sm', selected && 'ring-2 ring-ring/50'), children: [_jsx(Blocks, { className: "size-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: componentId }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: translate('richText.embed.badge') }), editable ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", contentEditable: false, "aria-label": translate('common.remove'), onClick: () => deleteNode(), children: _jsx(Trash2, { className: "size-3.5" }) })) : null] }));
67
+ const [open, setOpen] = useState(false);
68
+ const displayName = embedded
69
+ ? componentDisplayName(embedded.componentId, manifest)
70
+ : translate('richText.embed.unknown');
71
+ const summary = embedded ? embedPropsSummary(embedded, manifest) : undefined;
72
+ // Editing needs a manifest to know which prop fields to render.
73
+ const canEdit = editable && embedded != null && manifest != null;
74
+ return (_jsxs(NodeViewWrapper, { "data-drag-handle": true, className: cn('my-2', selected && 'rounded-md ring-2 ring-ring/50'), children: [_jsx(EmbedCard, { displayName: displayName, summary: summary, onEdit: canEdit ? () => setOpen(true) : undefined, onDelete: editable ? () => deleteNode() : undefined }), canEdit && embedded && manifest ? (_jsx(EmbedPropsDialog, { open: open, onOpenChange: setOpen, node: embedded, manifest: manifest, onSave: (next) => updateAttributes({ node: next }) })) : null] }));
53
75
  }
54
76
  //# sourceMappingURL=component-embed.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component-embed.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/component-embed.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE7F,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AAE5F,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAEhD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;IACxC,IAAI,EAAE,yBAAyB;IAC/B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IAEf,aAAa;QACX,+EAA+E;QAC/E,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAA;IACrD,CAAC;IAED,SAAS;QACP,OAAO,CAAC,EAAE,GAAG,EAAE,YAAY,yBAAyB,GAAG,EAAE,CAAC,CAAA;IAC5D,CAAC;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,cAAc,EAAE,EAAE,CAAC,QAAQ,yBAAyB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAChG,CAAC;IAED,WAAW;QACT,OAAO,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IAClD,CAAC;CACF,CAAC,CAAA;AAEF,iFAAiF;AACjF,SAAS,YAAY,CAAC,QAAiB;IACrC,OAAO,qBAAqB,CAAC;QAC3B,IAAI,EAAE,yBAAyB;QAC/B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAqB,EAAE;KACvC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAsB;IACpF,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC9C,MAAM,WAAW,GAAG,QAAQ,EAAE,WAAW,IAAI,SAAS,CAAC,wBAAwB,CAAC,CAAA;IAChF,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAA;IAElC,OAAO,CACL,MAAC,eAAe,8BAEd,SAAS,EAAE,EAAE,CACX,gGAAgG,EAChG,QAAQ,IAAI,qBAAqB,CAClC,aAED,KAAC,MAAM,IAAC,SAAS,EAAC,uCAAuC,GAAG,EAC5D,eAAM,SAAS,EAAC,qDAAqD,YAAE,WAAW,GAAQ,EAC1F,eAAM,SAAS,EAAC,wCAAwC,YAAE,SAAS,CAAC,sBAAsB,CAAC,GAAQ,EAClG,QAAQ,CAAC,CAAC,CAAC,CACV,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,EACd,eAAe,EAAE,KAAK,gBACV,SAAS,CAAC,eAAe,CAAC,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,YAE3B,KAAC,MAAM,IAAC,SAAS,EAAC,UAAU,GAAG,GACxB,CACV,CAAC,CAAC,CAAC,IAAI,IACQ,CACnB,CAAA;AACH,CAAC","sourcesContent":["import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react'\nimport type { ReactNodeViewProps } from '@tiptap/react'\nimport { Blocks, Trash2 } from 'lucide-react'\nimport { COMPONENT_EMBED_NODE_TYPE, embeddedComponentNode } from '@elytracms/core/rich-text'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport { Button } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * The rich-text component-embed node (EC-189/EC-314): a block atom holding a single\n * composition value at `attrs.node`. It is the third place constrained composition\n * lives (after the page collection and `blocks` fields) — a registry component\n * embedded in prose. The node carries no ProseMirror content (the embedded component\n * owns its own children through composition slots); `attrs.node` round-trips verbatim\n * so the extractor still indexes the embedded component's references, and the delivery\n * renderer renders it exactly as a `blocks` value.\n *\n * The editor shows a read-only placeholder card naming the embedded component (a live\n * in-editor preview would need the runtime renderer + resolvers; deferred). Insert and\n * deeper prop editing are wired from the toolbar / inspector.\n */\nexport const ComponentEmbed = Node.create({\n name: COMPONENT_EMBED_NODE_TYPE,\n group: 'block',\n atom: true,\n selectable: true,\n draggable: true,\n\n addAttributes() {\n // `node` holds the embedded ComponentNode JSON; storage/JSON only, not markup.\n return { node: { default: null, rendered: false } }\n },\n\n parseHTML() {\n return [{ tag: `div[data-${COMPONENT_EMBED_NODE_TYPE}]` }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['div', mergeAttributes(HTMLAttributes, { [`data-${COMPONENT_EMBED_NODE_TYPE}`]: '' })]\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(ComponentEmbedView)\n },\n})\n\n/** Read the embedded ComponentNode from a ProseMirror node's attrs, if valid. */\nfunction readEmbedded(attrNode: unknown): ComponentNode | undefined {\n return embeddedComponentNode({\n type: COMPONENT_EMBED_NODE_TYPE,\n attrs: { node: attrNode as JsonValue },\n })\n}\n\nfunction ComponentEmbedView({ node, deleteNode, selected, editor }: ReactNodeViewProps) {\n const embedded = readEmbedded(node.attrs.node)\n const componentId = embedded?.componentId ?? translate('richText.embed.unknown')\n const editable = editor.isEditable\n\n return (\n <NodeViewWrapper\n data-drag-handle\n className={cn(\n 'my-2 flex items-center gap-2.5 rounded-md border border-border bg-muted/40 px-3 py-2.5 text-sm',\n selected && 'ring-2 ring-ring/50',\n )}\n >\n <Blocks className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate font-medium text-foreground\">{componentId}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{translate('richText.embed.badge')}</span>\n {editable ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n contentEditable={false}\n aria-label={translate('common.remove')}\n onClick={() => deleteNode()}\n >\n <Trash2 className=\"size-3.5\" />\n </Button>\n ) : null}\n </NodeViewWrapper>\n )\n}\n"]}
1
+ {"version":3,"file":"component-embed.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/component-embed.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react'\nimport type { ReactNodeViewProps } from '@tiptap/react'\nimport { Blocks, Pencil, Trash2 } from 'lucide-react'\nimport { COMPONENT_EMBED_NODE_TYPE, embeddedComponentNode } from '@elytracms/core/rich-text'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { Button } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { EmbedPropsDialog, embedPropsSummary } from './embed-props'\n\n/**\n * The rich-text component-embed node (EC-189/EC-314): a block atom holding a single\n * composition value at `attrs.node`. It is the third place constrained composition\n * lives (after the page collection and `blocks` fields) — a registry component\n * embedded in prose. The node carries no ProseMirror content (the embedded component\n * owns its own children through composition slots); `attrs.node` round-trips verbatim\n * so the extractor still indexes the embedded component's references, and the delivery\n * renderer renders it exactly as a `blocks` value.\n *\n * The editor shows a placeholder card naming the embedded component (a live in-editor\n * preview would need the runtime renderer + resolvers; deferred). Insertion is wired\n * from the toolbar / slash menu; the card's Edit affordance opens the EC-314 phase-4\n * prop editor ({@link EmbedPropsDialog}), which rewrites `attrs.node` through a proper\n * ProseMirror transaction (undoable). `readOnly` renders an inert card (no edit/delete).\n */\nexport const ComponentEmbed = Node.create({\n name: COMPONENT_EMBED_NODE_TYPE,\n group: 'block',\n atom: true,\n selectable: true,\n draggable: true,\n\n addAttributes() {\n // `node` holds the embedded ComponentNode JSON; storage/JSON only, not markup.\n return { node: { default: null, rendered: false } }\n },\n\n parseHTML() {\n return [{ tag: `div[data-${COMPONENT_EMBED_NODE_TYPE}]` }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['div', mergeAttributes(HTMLAttributes, { [`data-${COMPONENT_EMBED_NODE_TYPE}`]: '' })]\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(ComponentEmbedView)\n },\n})\n\n/** Read the embedded ComponentNode from a ProseMirror node's attrs, if valid. */\nfunction readEmbedded(attrNode: unknown): ComponentNode | undefined {\n return embeddedComponentNode({\n type: COMPONENT_EMBED_NODE_TYPE,\n attrs: { node: attrNode as JsonValue },\n })\n}\n\n/**\n * The presentational embed card — component identity, an optional one-line prop\n * summary, and (only when a handler is supplied) edit / delete affordances. It is\n * kept free of TipTap and registry context so it renders + asserts standalone: an\n * inert (read-only) card is simply the card with no `onEdit`/`onDelete`.\n */\nexport function EmbedCard({\n displayName,\n summary,\n onEdit,\n onDelete,\n}: {\n displayName: string\n summary?: string | undefined\n onEdit?: (() => void) | undefined\n onDelete?: (() => void) | undefined\n}) {\n return (\n <div className=\"flex items-center gap-2.5 rounded-md border border-border bg-muted/40 px-3 py-2.5 text-sm\">\n <Blocks className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate font-medium text-foreground\">{displayName}</span>\n {summary ? <span className=\"truncate text-xs text-muted-foreground\">{summary}</span> : null}\n </span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">\n {translate('richText.embed.badge')}\n </span>\n {onEdit ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n contentEditable={false}\n aria-label={translate('richText.embed.editProps')}\n onClick={onEdit}\n >\n <Pencil className=\"size-3.5\" />\n </Button>\n ) : null}\n {onDelete ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n contentEditable={false}\n aria-label={translate('common.remove')}\n onClick={onDelete}\n >\n <Trash2 className=\"size-3.5\" />\n </Button>\n ) : null}\n </div>\n )\n}\n\nfunction ComponentEmbedView({ node, updateAttributes, deleteNode, selected, editor }: ReactNodeViewProps) {\n const embedded = readEmbedded(node.attrs.node)\n const manifest = embedded ? getStudioRegistry().registry.getManifest(embedded.componentId) : undefined\n const editable = editor.isEditable\n const [open, setOpen] = useState(false)\n\n const displayName = embedded\n ? componentDisplayName(embedded.componentId, manifest)\n : translate('richText.embed.unknown')\n const summary = embedded ? embedPropsSummary(embedded, manifest) : undefined\n // Editing needs a manifest to know which prop fields to render.\n const canEdit = editable && embedded != null && manifest != null\n\n return (\n <NodeViewWrapper data-drag-handle className={cn('my-2', selected && 'rounded-md ring-2 ring-ring/50')}>\n <EmbedCard\n displayName={displayName}\n summary={summary}\n onEdit={canEdit ? () => setOpen(true) : undefined}\n onDelete={editable ? () => deleteNode() : undefined}\n />\n {canEdit && embedded && manifest ? (\n <EmbedPropsDialog\n open={open}\n onOpenChange={setOpen}\n node={embedded}\n manifest={manifest}\n onSave={(next) => updateAttributes({ node: next })}\n />\n ) : null}\n </NodeViewWrapper>\n )\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import type { ComponentNode, JsonValue } from '@elytracms/core/project-graph';
2
+ import type { ComponentManifest, PropField } from '@elytracms/core/component-registry';
3
+ /**
4
+ * Prop editing for rich-text component embeds (EC-314 phase 4).
5
+ *
6
+ * An embedded component is a full `ComponentNode` stored verbatim at
7
+ * `componentEmbed.attrs.node`. This dialog edits that node's *props* through the
8
+ * SAME EC-190 "props-as-fields" control stack the canvas inspector and `blocks`
9
+ * field use ({@link PropValueControl}) — asset props open the library picker,
10
+ * relation props the document picker, etc. — never a parallel form system.
11
+ *
12
+ * Fidelity contract: a working `draft` is seeded from the stored node when the
13
+ * dialog opens; the dialog only writes back on Apply, and only when the draft is
14
+ * *structurally different* ({@link componentNodesEqual}) — so opening and closing
15
+ * without a real change never dirties the field. The upstream write goes through a
16
+ * single ProseMirror `updateAttributes` transaction (undoable), never a storage
17
+ * rewrite; the embedded node's `id`/`componentId`/`slots` pass through untouched.
18
+ *
19
+ * `blocks`-typed props (nested composition) are NOT edited here — that needs the
20
+ * canvas, not a popover. They are excluded from the form and surfaced as a note.
21
+ */
22
+ /** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */
23
+ export declare function setComponentNodeProp(node: ComponentNode, name: string, value: JsonValue): ComponentNode;
24
+ /** Remove a prop (revert to the manifest default), preserving everything else. */
25
+ export declare function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode;
26
+ /** Key-order-insensitive structural equality — a redundant "save" must not write. */
27
+ export declare function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean;
28
+ /** The manifest props editable inline: every non-`blocks` prop (composition needs the canvas). */
29
+ export declare function editableEmbedProps(manifest: ComponentManifest): Array<[string, PropField]>;
30
+ /** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */
31
+ export declare function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]>;
32
+ /**
33
+ * A lean one-line summary of an embed's static scalar props (`Label: value`,
34
+ * up to three), for the card. Skips unset props and structured values (refs /
35
+ * objects / arrays) — those are edited in the dialog, not summarised.
36
+ */
37
+ export declare function embedPropsSummary(node: ComponentNode, manifest?: ComponentManifest): string | undefined;
38
+ export interface EmbedPropsDialogProps {
39
+ open: boolean;
40
+ onOpenChange: (open: boolean) => void;
41
+ /** The embedded component node whose props are being edited. */
42
+ node: ComponentNode;
43
+ /** Its manifest — drives which prop fields render. */
44
+ manifest: ComponentManifest;
45
+ /** Commit the edited node (upstream writes it through one ProseMirror transaction). */
46
+ onSave: (node: ComponentNode) => void;
47
+ }
48
+ /** The edit-props dialog opened from an embed card. */
49
+ export declare function EmbedPropsDialog({ open, onOpenChange, node, manifest, onSave }: EmbedPropsDialogProps): import("react").JSX.Element;
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { isStaticProp, staticProp } from '@elytracms/core/project-graph';
4
+ import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Field, Stack, Text, } from '@elytracms/studio/ui';
5
+ import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
6
+ import { humanizeName } from '@elytracms/studio/lib/utils';
7
+ import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
8
+ import { PropValueControl } from '../prop-fields';
9
+ /**
10
+ * Prop editing for rich-text component embeds (EC-314 phase 4).
11
+ *
12
+ * An embedded component is a full `ComponentNode` stored verbatim at
13
+ * `componentEmbed.attrs.node`. This dialog edits that node's *props* through the
14
+ * SAME EC-190 "props-as-fields" control stack the canvas inspector and `blocks`
15
+ * field use ({@link PropValueControl}) — asset props open the library picker,
16
+ * relation props the document picker, etc. — never a parallel form system.
17
+ *
18
+ * Fidelity contract: a working `draft` is seeded from the stored node when the
19
+ * dialog opens; the dialog only writes back on Apply, and only when the draft is
20
+ * *structurally different* ({@link componentNodesEqual}) — so opening and closing
21
+ * without a real change never dirties the field. The upstream write goes through a
22
+ * single ProseMirror `updateAttributes` transaction (undoable), never a storage
23
+ * rewrite; the embedded node's `id`/`componentId`/`slots` pass through untouched.
24
+ *
25
+ * `blocks`-typed props (nested composition) are NOT edited here — that needs the
26
+ * canvas, not a popover. They are excluded from the form and surfaced as a note.
27
+ */
28
+ /** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */
29
+ export function setComponentNodeProp(node, name, value) {
30
+ return { ...node, props: { ...node.props, [name]: staticProp(value) } };
31
+ }
32
+ /** Remove a prop (revert to the manifest default), preserving everything else. */
33
+ export function clearComponentNodeProp(node, name) {
34
+ const props = { ...node.props };
35
+ delete props[name];
36
+ return { ...node, props };
37
+ }
38
+ /** Key-order-insensitive structural equality — a redundant "save" must not write. */
39
+ export function componentNodesEqual(a, b) {
40
+ return stableStringify(a) === stableStringify(b);
41
+ }
42
+ function stableStringify(value) {
43
+ if (value === null || typeof value !== 'object')
44
+ return JSON.stringify(value) ?? 'null';
45
+ if (Array.isArray(value))
46
+ return `[${value.map(stableStringify).join(',')}]`;
47
+ const entries = Object.entries(value)
48
+ .filter(([, v]) => v !== undefined)
49
+ .sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0))
50
+ .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`);
51
+ return `{${entries.join(',')}}`;
52
+ }
53
+ /** The manifest props editable inline: every non-`blocks` prop (composition needs the canvas). */
54
+ export function editableEmbedProps(manifest) {
55
+ return Object.entries(manifest.props).filter(([, spec]) => spec.type !== 'blocks');
56
+ }
57
+ /** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */
58
+ export function blocksEmbedProps(manifest) {
59
+ return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks');
60
+ }
61
+ /**
62
+ * A lean one-line summary of an embed's static scalar props (`Label: value`,
63
+ * up to three), for the card. Skips unset props and structured values (refs /
64
+ * objects / arrays) — those are edited in the dialog, not summarised.
65
+ */
66
+ export function embedPropsSummary(node, manifest) {
67
+ const parts = [];
68
+ for (const [name, value] of Object.entries(node.props ?? {})) {
69
+ if (!isStaticProp(value))
70
+ continue;
71
+ const raw = value.value;
72
+ if (raw === null || raw === undefined || typeof raw === 'object')
73
+ continue;
74
+ const label = manifest?.props[name]?.form?.label;
75
+ const labelText = label ? localizedLabel(label, name) : humanizeName(name);
76
+ parts.push(`${labelText}: ${String(raw)}`);
77
+ if (parts.length >= 3)
78
+ break;
79
+ }
80
+ return parts.length > 0 ? parts.join(' · ') : undefined;
81
+ }
82
+ /** The edit-props dialog opened from an embed card. */
83
+ export function EmbedPropsDialog({ open, onOpenChange, node, manifest, onSave }) {
84
+ const [draft, setDraft] = useState(node);
85
+ // Re-seed the working draft from the stored node whenever the dialog opens, so a
86
+ // cancelled edit is discarded and a reopen starts from the committed value.
87
+ useEffect(() => {
88
+ if (open)
89
+ setDraft(node);
90
+ }, [open, node]);
91
+ const editable = editableEmbedProps(manifest);
92
+ const blocks = blocksEmbedProps(manifest);
93
+ function apply() {
94
+ // No structural change ⇒ never write (opening + closing must not dirty the field).
95
+ if (!componentNodesEqual(draft, node))
96
+ onSave(draft);
97
+ onOpenChange(false);
98
+ }
99
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: translate('richText.embed.editTitle', {
100
+ name: componentDisplayName(node.componentId, manifest),
101
+ }) }) }), _jsxs(Stack, { gap: "4", className: "max-h-[60vh] overflow-y-auto", children: [editable.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", children: translate('richText.embed.noProps') })) : (editable.map(([name, spec]) => {
102
+ const id = `embed-${node.id}-${name}`;
103
+ return (_jsx(Field, { label: localizedLabel(spec.form?.label, name), htmlFor: id, children: _jsx(PropValueControl, { id: id, name: name, spec: spec, value: draft.props?.[name], onSet: (v) => setDraft((d) => setComponentNodeProp(d, name, v)), onClear: () => setDraft((d) => clearComponentNodeProp(d, name)) }) }, name));
104
+ })), blocks.length > 0 ? (_jsx(Text, { size: "xs", tone: "subtle", children: translate('richText.embed.blocksNote') })) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: translate('common.cancel') }), _jsx(Button, { type: "button", onClick: apply, children: translate('common.apply') })] })] }) }));
105
+ }
106
+ //# sourceMappingURL=embed-props.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed-props.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-props.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from 'react'\nimport { isStaticProp, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { humanizeName } from '@elytracms/studio/lib/utils'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { PropValueControl } from '../prop-fields'\n\n/**\n * Prop editing for rich-text component embeds (EC-314 phase 4).\n *\n * An embedded component is a full `ComponentNode` stored verbatim at\n * `componentEmbed.attrs.node`. This dialog edits that node's *props* through the\n * SAME EC-190 \"props-as-fields\" control stack the canvas inspector and `blocks`\n * field use ({@link PropValueControl}) — asset props open the library picker,\n * relation props the document picker, etc. — never a parallel form system.\n *\n * Fidelity contract: a working `draft` is seeded from the stored node when the\n * dialog opens; the dialog only writes back on Apply, and only when the draft is\n * *structurally different* ({@link componentNodesEqual}) — so opening and closing\n * without a real change never dirties the field. The upstream write goes through a\n * single ProseMirror `updateAttributes` transaction (undoable), never a storage\n * rewrite; the embedded node's `id`/`componentId`/`slots` pass through untouched.\n *\n * `blocks`-typed props (nested composition) are NOT edited here — that needs the\n * canvas, not a popover. They are excluded from the form and surfaced as a note.\n */\n\n/** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */\nexport function setComponentNodeProp(\n node: ComponentNode,\n name: string,\n value: JsonValue,\n): ComponentNode {\n return { ...node, props: { ...node.props, [name]: staticProp(value) } }\n}\n\n/** Remove a prop (revert to the manifest default), preserving everything else. */\nexport function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode {\n const props = { ...node.props }\n delete props[name]\n return { ...node, props }\n}\n\n/** Key-order-insensitive structural equality — a redundant \"save\" must not write. */\nexport function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0))\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n return `{${entries.join(',')}}`\n}\n\n/** The manifest props editable inline: every non-`blocks` prop (composition needs the canvas). */\nexport function editableEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type !== 'blocks')\n}\n\n/** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */\nexport function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks')\n}\n\n/**\n * A lean one-line summary of an embed's static scalar props (`Label: value`,\n * up to three), for the card. Skips unset props and structured values (refs /\n * objects / arrays) — those are edited in the dialog, not summarised.\n */\nexport function embedPropsSummary(\n node: ComponentNode,\n manifest?: ComponentManifest,\n): string | undefined {\n const parts: string[] = []\n for (const [name, value] of Object.entries(node.props ?? {})) {\n if (!isStaticProp(value)) continue\n const raw = value.value\n if (raw === null || raw === undefined || typeof raw === 'object') continue\n const label = manifest?.props[name]?.form?.label\n const labelText = label ? localizedLabel(label, name) : humanizeName(name)\n parts.push(`${labelText}: ${String(raw)}`)\n if (parts.length >= 3) break\n }\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\nexport interface EmbedPropsDialogProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** The embedded component node whose props are being edited. */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /** Commit the edited node (upstream writes it through one ProseMirror transaction). */\n onSave: (node: ComponentNode) => void\n}\n\n/** The edit-props dialog opened from an embed card. */\nexport function EmbedPropsDialog({ open, onOpenChange, node, manifest, onSave }: EmbedPropsDialogProps) {\n const [draft, setDraft] = useState<ComponentNode>(node)\n\n // Re-seed the working draft from the stored node whenever the dialog opens, so a\n // cancelled edit is discarded and a reopen starts from the committed value.\n useEffect(() => {\n if (open) setDraft(node)\n }, [open, node])\n\n const editable = editableEmbedProps(manifest)\n const blocks = blocksEmbedProps(manifest)\n\n function apply() {\n // No structural change ⇒ never write (opening + closing must not dirty the field).\n if (!componentNodesEqual(draft, node)) onSave(draft)\n onOpenChange(false)\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>\n {translate('richText.embed.editTitle', {\n name: componentDisplayName(node.componentId, manifest),\n })}\n </DialogTitle>\n </DialogHeader>\n <Stack gap=\"4\" className=\"max-h-[60vh] overflow-y-auto\">\n {editable.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n editable.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field key={name} label={localizedLabel(spec.form?.label, name)} htmlFor={id}>\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={draft.props?.[name]}\n onSet={(v) => setDraft((d) => setComponentNodeProp(d, name, v))}\n onClear={() => setDraft((d) => clearComponentNodeProp(d, name))}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n ) : null}\n </Stack>\n <DialogFooter>\n <Button type=\"button\" variant=\"ghost\" onClick={() => onOpenChange(false)}>\n {translate('common.cancel')}\n </Button>\n <Button type=\"button\" onClick={apply}>\n {translate('common.apply')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
@@ -3,5 +3,10 @@
3
3
  * `@elytracms/core/rich-text` storage. Public surface is the {@link RichTextEditor}
4
4
  * component wired into the `richText` field control; everything else (extensions,
5
5
  * toolbar, value bridging) is an implementation detail.
6
+ *
7
+ * EC-322: the public export is the {@link ./lazy lazy-loaded} wrapper — TipTap only
8
+ * downloads when a `richText` field actually renders, keeping it out of the studio's
9
+ * main route chunk. The eager editor stays available as `./editor` for the unit tests
10
+ * and the extension-level modules that import it directly.
6
11
  */
7
- export { RichTextEditor } from './editor';
12
+ export { RichTextEditor } from './lazy';
@@ -3,6 +3,11 @@
3
3
  * `@elytracms/core/rich-text` storage. Public surface is the {@link RichTextEditor}
4
4
  * component wired into the `richText` field control; everything else (extensions,
5
5
  * toolbar, value bridging) is an implementation detail.
6
+ *
7
+ * EC-322: the public export is the {@link ./lazy lazy-loaded} wrapper — TipTap only
8
+ * downloads when a `richText` field actually renders, keeping it out of the studio's
9
+ * main route chunk. The eager editor stays available as `./editor` for the unit tests
10
+ * and the extension-level modules that import it directly.
6
11
  */
7
- export { RichTextEditor } from './editor';
12
+ export { RichTextEditor } from './lazy';
8
13
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA","sourcesContent":["/**\n * The CMS rich-text field editor (EC-314) — a TipTap editor over the shared\n * `@elytracms/core/rich-text` storage. Public surface is the {@link RichTextEditor}\n * component wired into the `richText` field control; everything else (extensions,\n * toolbar, value bridging) is an implementation detail.\n */\nexport { RichTextEditor } from './editor'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA","sourcesContent":["/**\n * The CMS rich-text field editor (EC-314) — a TipTap editor over the shared\n * `@elytracms/core/rich-text` storage. Public surface is the {@link RichTextEditor}\n * component wired into the `richText` field control; everything else (extensions,\n * toolbar, value bridging) is an implementation detail.\n *\n * EC-322: the public export is the {@link ./lazy lazy-loaded} wrapper — TipTap only\n * downloads when a `richText` field actually renders, keeping it out of the studio's\n * main route chunk. The eager editor stays available as `./editor` for the unit tests\n * and the extension-level modules that import it directly.\n */\nexport { RichTextEditor } from './lazy'\n"]}