@elytracms/studio 0.0.64 → 0.0.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/convex/assets.ts +11 -0
- package/convex/delivery.ts +5 -0
- package/convex/schema.ts +23 -0
- package/dist/components/account/self-account-view-source.js +9 -2
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/app-views/default-modules.d.ts +28 -1
- package/dist/components/app-views/default-modules.js +47 -2
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/assets/asset-thumb.d.ts +4 -2
- package/dist/components/assets/asset-thumb.js +26 -4
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-module.js +48 -2
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +11 -1
- package/dist/components/assets/assets-store.js +18 -1
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/auth-context.js +32 -1
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +11 -0
- package/dist/components/builder/blocks-model.js +30 -10
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/canvas-chrome-css.js +90 -8
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-dnd.js +4 -2
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-inline-editing.d.ts +7 -0
- package/dist/components/builder/canvas-inline-editing.js +3 -1
- package/dist/components/builder/canvas-inline-editing.js.map +1 -1
- package/dist/components/builder/canvas-overlay-geometry.d.ts +72 -0
- package/dist/components/builder/canvas-overlay-geometry.js +80 -0
- package/dist/components/builder/canvas-overlay-geometry.js.map +1 -0
- package/dist/components/builder/canvas-rect-tracking.d.ts +124 -0
- package/dist/components/builder/canvas-rect-tracking.js +198 -0
- package/dist/components/builder/canvas-rect-tracking.js.map +1 -0
- package/dist/components/builder/canvas.js +61 -12
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/composition-field-editor.js +22 -3
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/delete-key.d.ts +38 -0
- package/dist/components/builder/delete-key.js +46 -0
- package/dist/components/builder/delete-key.js.map +1 -0
- package/dist/components/builder/drag-session.d.ts +8 -0
- package/dist/components/builder/drag-session.js +15 -0
- package/dist/components/builder/drag-session.js.map +1 -1
- package/dist/components/builder/insert-affordance-context.d.ts +19 -0
- package/dist/components/builder/insert-affordance-context.js +7 -0
- package/dist/components/builder/insert-affordance-context.js.map +1 -0
- package/dist/components/builder/insert-context.d.ts +40 -0
- package/dist/components/builder/insert-context.js +97 -5
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/node-title.d.ts +60 -0
- package/dist/components/builder/node-title.js +135 -0
- package/dist/components/builder/node-title.js.map +1 -0
- package/dist/components/builder/page-tree.js +25 -5
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/prop-selection.d.ts +52 -0
- package/dist/components/builder/prop-selection.js +38 -0
- package/dist/components/builder/prop-selection.js.map +1 -0
- package/dist/components/builder/prop-sync-context.d.ts +49 -0
- package/dist/components/builder/prop-sync-context.js +23 -0
- package/dist/components/builder/prop-sync-context.js.map +1 -0
- package/dist/components/builder/property-inspector.js +24 -1
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +1 -1
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/selection-overlay.d.ts +79 -0
- package/dist/components/builder/selection-overlay.js +244 -0
- package/dist/components/builder/selection-overlay.js.map +1 -0
- package/dist/components/builder/spacing-handles.d.ts +57 -1
- package/dist/components/builder/spacing-handles.js +142 -26
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-field.js +21 -5
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker.js +11 -5
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +22 -3
- package/dist/components/cms/bulk-actions.js +27 -3
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cms-store.js +19 -0
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/detail-editor.js +18 -19
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/prop-fields.js +23 -16
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/publish-clean.d.ts +21 -0
- package/dist/components/cms/publish-clean.js +30 -0
- package/dist/components/cms/publish-clean.js.map +1 -0
- package/dist/components/cms/rich-text/block-style.d.ts +48 -0
- package/dist/components/cms/rich-text/block-style.js +30 -0
- package/dist/components/cms/rich-text/block-style.js.map +1 -0
- package/dist/components/cms/rich-text/embed-picker.js +4 -3
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
- package/dist/components/cms/rich-text/extensions.d.ts +2 -0
- package/dist/components/cms/rich-text/extensions.js +4 -0
- package/dist/components/cms/rich-text/extensions.js.map +1 -1
- package/dist/components/cms/rich-text/font-size-control.d.ts +31 -0
- package/dist/components/cms/rich-text/font-size-control.js +40 -0
- package/dist/components/cms/rich-text/font-size-control.js.map +1 -0
- package/dist/components/cms/rich-text/font-size.d.ts +16 -0
- package/dist/components/cms/rich-text/font-size.js +40 -0
- package/dist/components/cms/rich-text/font-size.js.map +1 -0
- package/dist/components/cms/rich-text/toolbar-state.d.ts +3 -0
- package/dist/components/cms/rich-text/toolbar-state.js +3 -0
- package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.d.ts +13 -0
- package/dist/components/cms/rich-text/toolbar.js +17 -5
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +24 -1
- package/dist/components/cms/use-live-url.js +26 -4
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/cms/use-publish-controller.js +8 -6
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +15 -7
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/components/component-detail.js +3 -3
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.js +1 -1
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/registry-browser.js +2 -2
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.js +3 -2
- package/dist/components/components/taxonomy.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +1 -1
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/lib/assets/backfill-thumbs.d.ts +67 -0
- package/dist/lib/assets/backfill-thumbs.js +120 -0
- package/dist/lib/assets/backfill-thumbs.js.map +1 -0
- package/dist/lib/assets/delete-asset.d.ts +2 -1
- package/dist/lib/assets/delete-asset.js +12 -0
- package/dist/lib/assets/delete-asset.js.map +1 -1
- package/dist/lib/assets/index.d.ts +2 -0
- package/dist/lib/assets/index.js +2 -0
- package/dist/lib/assets/index.js.map +1 -1
- package/dist/lib/assets/thumbnail.d.ts +145 -0
- package/dist/lib/assets/thumbnail.js +220 -0
- package/dist/lib/assets/thumbnail.js.map +1 -0
- package/dist/lib/assets/upload.d.ts +9 -0
- package/dist/lib/assets/upload.js +31 -0
- package/dist/lib/assets/upload.js.map +1 -1
- package/dist/lib/auth/auth-adapter.d.ts +22 -0
- package/dist/lib/auth/auth-adapter.js.map +1 -1
- package/dist/lib/auth/convex-auth-adapter.d.ts +42 -1
- package/dist/lib/auth/convex-auth-adapter.js +69 -0
- package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
- package/dist/lib/auth/dev-auto-login-adapter.d.ts +2 -1
- package/dist/lib/auth/dev-auto-login-adapter.js +6 -0
- package/dist/lib/auth/dev-auto-login-adapter.js.map +1 -1
- package/dist/lib/auth/index.d.ts +1 -1
- package/dist/lib/auth/index.js.map +1 -1
- package/dist/lib/auth/local-auth-adapter.d.ts +2 -1
- package/dist/lib/auth/local-auth-adapter.js +7 -0
- package/dist/lib/auth/local-auth-adapter.js.map +1 -1
- package/dist/lib/auth-errors.d.ts +45 -3
- package/dist/lib/auth-errors.js +46 -5
- package/dist/lib/auth-errors.js.map +1 -1
- package/dist/lib/clipboard/paste-target.js +5 -2
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +13 -0
- package/dist/lib/dnd/drop-rules.js +15 -0
- package/dist/lib/dnd/drop-rules.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +19 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +16 -0
- package/dist/lib/i18n/messages.en.js +24 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +4 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/index.js +6 -3
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +9 -5
- package/dist/lib/persona/labels.js +13 -8
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/references/references-store.js +7 -1
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/session-expiry.d.ts +19 -6
- package/dist/lib/session-expiry.js +13 -1
- package/dist/lib/session-expiry.js.map +1 -1
- package/dist/lib/session-revalidation.d.ts +81 -0
- package/dist/lib/session-revalidation.js +94 -0
- package/dist/lib/session-revalidation.js.map +1 -0
- package/package.json +2 -2
- package/src/styles/studio.css +13 -0
|
@@ -87,12 +87,19 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
|
|
|
87
87
|
const kind = controlKind(spec);
|
|
88
88
|
// Select options live on the `select` field-def.
|
|
89
89
|
const options = spec.type === 'select' ? spec.options : [];
|
|
90
|
+
// EC-395: `form.readOnly` parity with the document form. `cms-store.tsx`'s
|
|
91
|
+
// `isFieldReadOnly` reads the identical `field.form?.readOnly === true` check —
|
|
92
|
+
// every control kind below (including the delegated `FieldControl`/
|
|
93
|
+
// `SegmentedOptions` mounts, which previously hardcoded `readOnly={false}`)
|
|
94
|
+
// degrades to the SAME disabled/inert interaction the document form's
|
|
95
|
+
// `FieldControl` renders for a read-only field, not a parallel treatment.
|
|
96
|
+
const readOnly = spec.form?.readOnly === true;
|
|
90
97
|
let control;
|
|
91
98
|
if (kind === 'boolean') {
|
|
92
|
-
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') })] }));
|
|
99
|
+
control = (_jsxs("label", { className: "flex h-8 items-center gap-2 text-sm", children: [_jsx("input", { id: id, type: "checkbox", checked: current === true, disabled: readOnly, onChange: (e) => onSet(e.target.checked), className: "size-4 accent-primary disabled:opacity-50" }), _jsx(Text, { size: "sm", tone: "muted", children: current === true ? translate('common.true') : translate('common.false') })] }));
|
|
93
100
|
}
|
|
94
101
|
else if (kind === 'select') {
|
|
95
|
-
control = (_jsx(Select, { id: id, value: String(current ?? ''), onChange: (e) => {
|
|
102
|
+
control = (_jsx(Select, { id: id, disabled: readOnly, value: String(current ?? ''), onChange: (e) => {
|
|
96
103
|
// EC-190: select option values are strings (field-def `options`).
|
|
97
104
|
const opt = options.find((o) => o.value === e.target.value);
|
|
98
105
|
onSet(opt ? opt.value : e.target.value);
|
|
@@ -103,16 +110,16 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
|
|
|
103
110
|
// `field-control.tsx`) — delegation over a parallel dropdown. It reads the
|
|
104
111
|
// field-def's own `options` (label + per-option `description` tooltip); single-
|
|
105
112
|
// select only (picks one, no unset — an unset select keeps the native dropdown).
|
|
106
|
-
control = (_jsx(SegmentedOptions, { id: id, field: { ...spec, name }, value: current, onChange: (v) => onSet(v), readOnly:
|
|
113
|
+
control = (_jsx(SegmentedOptions, { id: id, field: { ...spec, name }, value: current, onChange: (v) => onSet(v), readOnly: readOnly }));
|
|
107
114
|
}
|
|
108
115
|
else if (kind === 'number') {
|
|
109
|
-
control = _jsx(NumberControl, { id: id, current: current, onSet: onSet, onClear: onClear });
|
|
116
|
+
control = (_jsx(NumberControl, { id: id, current: current, onSet: onSet, onClear: onClear, readOnly: readOnly }));
|
|
110
117
|
}
|
|
111
118
|
else if (kind === 'json') {
|
|
112
|
-
control = _jsx(JsonControl, { id: id, current: current, onSet: onSet });
|
|
119
|
+
control = _jsx(JsonControl, { id: id, current: current, onSet: onSet, readOnly: readOnly });
|
|
113
120
|
}
|
|
114
121
|
else if (kind === 'color') {
|
|
115
|
-
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) })] }));
|
|
122
|
+
control = (_jsxs(Row, { gap: "2", children: [_jsx("input", { id: id, type: "color", disabled: readOnly, 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 disabled:opacity-50" }), _jsx(Input, { disabled: readOnly, value: typeof current === 'string' ? current : '', onChange: (e) => onSet(e.target.value) })] }));
|
|
116
123
|
}
|
|
117
124
|
else if (kind === 'richtext') {
|
|
118
125
|
// EC-387: a richText PROP now uses the SAME real editor as the collection-field
|
|
@@ -131,10 +138,10 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
|
|
|
131
138
|
// preview surface (no provider, nothing to edit against) degrades to the same
|
|
132
139
|
// read-only value display the sibling controls use, instead of crashing with
|
|
133
140
|
// "useCollectionView must be used within a <CollectionViewProvider>".
|
|
134
|
-
control = hasWorkspaceContext ? (_jsx(RichTextEditor, { id: id, value: current, onChange: (next) => applyRichTextPropChange(next, { onSet, onClear }), allow: spec.type === 'richText' ? spec.allow : undefined })) : (_jsx(WorkspaceValueFallback, { value: current }));
|
|
141
|
+
control = hasWorkspaceContext ? (_jsx(RichTextEditor, { id: id, value: current, onChange: (next) => applyRichTextPropChange(next, { onSet, onClear }), allow: spec.type === 'richText' ? spec.allow : undefined, readOnly: readOnly })) : (_jsx(WorkspaceValueFallback, { value: current }));
|
|
135
142
|
}
|
|
136
143
|
else if (kind === 'textarea') {
|
|
137
|
-
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" }));
|
|
144
|
+
control = (_jsx("textarea", { id: id, rows: 2, disabled: readOnly, 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 disabled:cursor-not-allowed disabled:opacity-50" }));
|
|
138
145
|
}
|
|
139
146
|
else if (kind === 'asset' || kind === 'object' || kind === 'relation') {
|
|
140
147
|
// EC-195/EC-253/EC-262: reuse the CMS field control. `asset` = library picker +
|
|
@@ -143,12 +150,12 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
|
|
|
143
150
|
// All edit a structured static prop value through the same shared control documents use.
|
|
144
151
|
// Without a workspace context (the Components preview), degrade to a read-only
|
|
145
152
|
// value display so the pane renders instead of throwing.
|
|
146
|
-
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:
|
|
153
|
+
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: readOnly }) })) : (_jsx(WorkspaceValueFallback, { value: current }));
|
|
147
154
|
}
|
|
148
155
|
else {
|
|
149
|
-
control = (_jsx(Input, { id: id, value: typeof current === 'string' ? current : String(current ?? ''), onChange: (e) => onSet(e.target.value) }));
|
|
156
|
+
control = (_jsx(Input, { id: id, disabled: readOnly, value: typeof current === 'string' ? current : String(current ?? ''), onChange: (e) => onSet(e.target.value) }));
|
|
150
157
|
}
|
|
151
|
-
return (_jsxs(Stack, { gap: "1", children: [control, !valid ? (_jsx(Text, { size: "xs", className: "text-destructive", children: translate('builder.inspector.invalidValue') })) : null] }));
|
|
158
|
+
return (_jsxs(Stack, { gap: "1", children: [control, !valid && !readOnly ? (_jsx(Text, { size: "xs", className: "text-destructive", children: translate('builder.inspector.invalidValue') })) : null] }));
|
|
152
159
|
}
|
|
153
160
|
/**
|
|
154
161
|
* Read-only stand-in for an `asset`/`relation`/`object` prop when there is no
|
|
@@ -165,12 +172,12 @@ function WorkspaceValueFallback({ value }) {
|
|
|
165
172
|
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') })] }));
|
|
166
173
|
}
|
|
167
174
|
/** Number input with a local buffer: commits only finite values, never NaN; empty clears the prop. */
|
|
168
|
-
function NumberControl({ id, current, onSet, onClear, }) {
|
|
175
|
+
function NumberControl({ id, current, onSet, onClear, readOnly, }) {
|
|
169
176
|
const [raw, setRaw] = useState(typeof current === 'number' ? String(current) : '');
|
|
170
177
|
useEffect(() => {
|
|
171
178
|
setRaw(typeof current === 'number' ? String(current) : '');
|
|
172
179
|
}, [current]);
|
|
173
|
-
return (_jsx(Input, { id: id, type: "number", value: raw, onChange: (e) => {
|
|
180
|
+
return (_jsx(Input, { id: id, type: "number", disabled: readOnly, value: raw, onChange: (e) => {
|
|
174
181
|
const v = e.target.value;
|
|
175
182
|
setRaw(v);
|
|
176
183
|
if (v.trim() === '') {
|
|
@@ -183,14 +190,14 @@ function NumberControl({ id, current, onSet, onClear, }) {
|
|
|
183
190
|
} }));
|
|
184
191
|
}
|
|
185
192
|
/** JSON textarea with a local buffer: commits only when the text parses; flags parse errors. */
|
|
186
|
-
function JsonControl({ id, current, onSet, }) {
|
|
193
|
+
function JsonControl({ id, current, onSet, readOnly, }) {
|
|
187
194
|
const [raw, setRaw] = useState(() => JSON.stringify(current ?? null));
|
|
188
195
|
const [error, setError] = useState(null);
|
|
189
196
|
useEffect(() => {
|
|
190
197
|
setRaw(JSON.stringify(current ?? null));
|
|
191
198
|
setError(null);
|
|
192
199
|
}, [current]);
|
|
193
|
-
return (_jsxs(Stack, { gap: "1", children: [_jsx("textarea", { id: id, rows: 3, value: raw, onChange: (e) => {
|
|
200
|
+
return (_jsxs(Stack, { gap: "1", children: [_jsx("textarea", { id: id, rows: 3, disabled: readOnly, value: raw, onChange: (e) => {
|
|
194
201
|
const v = e.target.value;
|
|
195
202
|
setRaw(v);
|
|
196
203
|
try {
|
|
@@ -200,6 +207,6 @@ function JsonControl({ id, current, onSet, }) {
|
|
|
200
207
|
catch {
|
|
201
208
|
setError(translate('builder.inspector.invalidJson'));
|
|
202
209
|
}
|
|
203
|
-
}, 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] }));
|
|
210
|
+
}, className: "flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:cursor-not-allowed disabled:opacity-50" }), error ? (_jsx(Text, { size: "xs", className: "text-destructive", children: error })) : null] }));
|
|
204
211
|
}
|
|
205
212
|
//# sourceMappingURL=prop-fields.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prop-fields.js","sourceRoot":"","sources":["../../../src/components/cms/prop-fields.tsx"],"names":[],"mappings":"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","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, SelectFieldDef, 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 { SegmentedOptions } from './field-control-lib'\nimport { FieldDensityProvider } from './field-density'\nimport { RichTextEditor } from './rich-text'\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 | 'options'\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 // EC-393: a single-select prop opting into `form.control: 'options'` renders the\n // SAME segmented button group the document form uses (EC-295), not the native\n // dropdown — parity with `field-control.tsx`. Segmented is single-select only,\n // so a `multiple` select stays on the native control (mirroring field-control).\n if (spec.form?.control === 'options' && !spec.multiple) return 'options'\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\n/**\n * EC-387: route a rich-text editor emission to the prop value callbacks.\n *\n * The shared {@link RichTextEditor} emits either an emptied-editor `undefined`, the\n * exact stored envelope (an untouched/reverted edit), or a fresh `{ version, doc }`\n * value — never a plain string. An `undefined` clears the prop (reverting to the\n * manifest default, matching the asset/relation/object controls); anything else is a\n * structured `JsonValue` envelope set verbatim. Extracted pure so the wiring — the\n * exact bug the retired textarea stub had (it wrote a bare string over the envelope) —\n * is regression-pinned without driving TipTap.\n */\nexport function applyRichTextPropChange(\n next: unknown,\n handlers: { onSet: (value: JsonValue) => void; onClear: () => void },\n): void {\n if (next === undefined) handlers.onClear()\n else handlers.onSet(next as JsonValue)\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 === 'options') {\n // EC-393: reuse the EC-295 segmented control the document form renders (via\n // `field-control.tsx`) — delegation over a parallel dropdown. It reads the\n // field-def's own `options` (label + per-option `description` tooltip); single-\n // select only (picks one, no unset — an unset select keeps the native dropdown).\n control = (\n <SegmentedOptions\n id={id}\n field={{ ...spec, name } as SelectFieldDef}\n value={current}\n onChange={(v) => onSet(v as JsonValue)}\n readOnly={false}\n />\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 === 'richtext') {\n // EC-387: a richText PROP now uses the SAME real editor as the collection-field\n // path (`field-control.tsx` `case 'richText'`) — one editing surface, one\n // `{ version, doc }` value contract, never a second implementation. The retired\n // textarea stub read `typeof current === 'string'`, so a stored envelope showed\n // EMPTY and any edit clobbered the structured document with a bare string\n // (silent data destruction). The editor reads the envelope (or a legacy string,\n // or empty), always emits an envelope, and routes an emptied editor's `undefined`\n // to a prop clear via {@link applyRichTextPropChange}.\n //\n // Like the asset/relation/object controls below, the editor needs the ambient\n // workspace — its asset-image picker reads the REQUIRED collection-view context.\n // The block property inspector (the EC-387 target) is inside the detail editor's\n // `CollectionViewProvider`, so it gets the full editor; the component-registry\n // preview surface (no provider, nothing to edit against) degrades to the same\n // read-only value display the sibling controls use, instead of crashing with\n // \"useCollectionView must be used within a <CollectionViewProvider>\".\n control = hasWorkspaceContext ? (\n <RichTextEditor\n id={id}\n value={current}\n onChange={(next) => applyRichTextPropChange(next, { onSet, onClear })}\n allow={spec.type === 'richText' ? spec.allow : undefined}\n />\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else if (kind === 'textarea') {\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"]}
|
|
1
|
+
{"version":3,"file":"prop-fields.js","sourceRoot":"","sources":["../../../src/components/cms/prop-fields.tsx"],"names":[],"mappings":"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","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, SelectFieldDef, 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 { SegmentedOptions } from './field-control-lib'\nimport { FieldDensityProvider } from './field-density'\nimport { RichTextEditor } from './rich-text'\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 | 'options'\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 // EC-393: a single-select prop opting into `form.control: 'options'` renders the\n // SAME segmented button group the document form uses (EC-295), not the native\n // dropdown — parity with `field-control.tsx`. Segmented is single-select only,\n // so a `multiple` select stays on the native control (mirroring field-control).\n if (spec.form?.control === 'options' && !spec.multiple) return 'options'\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\n/**\n * EC-387: route a rich-text editor emission to the prop value callbacks.\n *\n * The shared {@link RichTextEditor} emits either an emptied-editor `undefined`, the\n * exact stored envelope (an untouched/reverted edit), or a fresh `{ version, doc }`\n * value — never a plain string. An `undefined` clears the prop (reverting to the\n * manifest default, matching the asset/relation/object controls); anything else is a\n * structured `JsonValue` envelope set verbatim. Extracted pure so the wiring — the\n * exact bug the retired textarea stub had (it wrote a bare string over the envelope) —\n * is regression-pinned without driving TipTap.\n */\nexport function applyRichTextPropChange(\n next: unknown,\n handlers: { onSet: (value: JsonValue) => void; onClear: () => void },\n): void {\n if (next === undefined) handlers.onClear()\n else handlers.onSet(next as JsonValue)\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 // EC-395: `form.readOnly` parity with the document form. `cms-store.tsx`'s\n // `isFieldReadOnly` reads the identical `field.form?.readOnly === true` check —\n // every control kind below (including the delegated `FieldControl`/\n // `SegmentedOptions` mounts, which previously hardcoded `readOnly={false}`)\n // degrades to the SAME disabled/inert interaction the document form's\n // `FieldControl` renders for a read-only field, not a parallel treatment.\n const readOnly = spec.form?.readOnly === true\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 disabled={readOnly}\n onChange={(e) => onSet(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\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 disabled={readOnly}\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 === 'options') {\n // EC-393: reuse the EC-295 segmented control the document form renders (via\n // `field-control.tsx`) — delegation over a parallel dropdown. It reads the\n // field-def's own `options` (label + per-option `description` tooltip); single-\n // select only (picks one, no unset — an unset select keeps the native dropdown).\n control = (\n <SegmentedOptions\n id={id}\n field={{ ...spec, name } as SelectFieldDef}\n value={current}\n onChange={(v) => onSet(v as JsonValue)}\n readOnly={readOnly}\n />\n )\n } else if (kind === 'number') {\n control = (\n <NumberControl id={id} current={current} onSet={onSet} onClear={onClear} readOnly={readOnly} />\n )\n } else if (kind === 'json') {\n control = <JsonControl id={id} current={current} onSet={onSet} readOnly={readOnly} />\n } else if (kind === 'color') {\n control = (\n <Row gap=\"2\">\n <input\n id={id}\n type=\"color\"\n disabled={readOnly}\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 disabled:opacity-50\"\n />\n <Input\n disabled={readOnly}\n value={typeof current === 'string' ? current : ''}\n onChange={(e) => onSet(e.target.value)}\n />\n </Row>\n )\n } else if (kind === 'richtext') {\n // EC-387: a richText PROP now uses the SAME real editor as the collection-field\n // path (`field-control.tsx` `case 'richText'`) — one editing surface, one\n // `{ version, doc }` value contract, never a second implementation. The retired\n // textarea stub read `typeof current === 'string'`, so a stored envelope showed\n // EMPTY and any edit clobbered the structured document with a bare string\n // (silent data destruction). The editor reads the envelope (or a legacy string,\n // or empty), always emits an envelope, and routes an emptied editor's `undefined`\n // to a prop clear via {@link applyRichTextPropChange}.\n //\n // Like the asset/relation/object controls below, the editor needs the ambient\n // workspace — its asset-image picker reads the REQUIRED collection-view context.\n // The block property inspector (the EC-387 target) is inside the detail editor's\n // `CollectionViewProvider`, so it gets the full editor; the component-registry\n // preview surface (no provider, nothing to edit against) degrades to the same\n // read-only value display the sibling controls use, instead of crashing with\n // \"useCollectionView must be used within a <CollectionViewProvider>\".\n control = hasWorkspaceContext ? (\n <RichTextEditor\n id={id}\n value={current}\n onChange={(next) => applyRichTextPropChange(next, { onSet, onClear })}\n allow={spec.type === 'richText' ? spec.allow : undefined}\n readOnly={readOnly}\n />\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else if (kind === 'textarea') {\n control = (\n <textarea\n id={id}\n rows={2}\n disabled={readOnly}\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 disabled:cursor-not-allowed disabled:opacity-50\"\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={readOnly}\n />\n </FieldDensityProvider>\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else {\n control = (\n <Input\n id={id}\n disabled={readOnly}\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 {/* EC-395: a read-only prop's value can't be fixed here — suppress the\n invalid-value flag exactly like the document form suppresses its field\n error on a read-only row (`detail-editor.tsx`'s\n `error={fieldReadOnly ? undefined : ...}`). */}\n {!valid && !readOnly ? (\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 readOnly,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: number) => void\n onClear: () => void\n readOnly: boolean\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 disabled={readOnly}\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 readOnly,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: JsonValue) => void\n readOnly: boolean\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 disabled={readOnly}\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 disabled:cursor-not-allowed disabled:opacity-50\"\n />\n {error ? (\n <Text size=\"xs\" className=\"text-destructive\">\n {error}\n </Text>\n ) : null}\n </Stack>\n )\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CmsDocument, CollectionDef } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { ManifestResolver } from '@elytracms/core/component-registry';
|
|
3
|
+
/**
|
|
4
|
+
* EC-425 — THE publish-time hidden-strip, extracted so single-document publish
|
|
5
|
+
* (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)
|
|
6
|
+
* share ONE clean step instead of the bulk bar bypassing it:
|
|
7
|
+
*
|
|
8
|
+
* - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values
|
|
9
|
+
* (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`.
|
|
10
|
+
* - EC-394 `clearHiddenProps`: the same strip for component props across every
|
|
11
|
+
* `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves
|
|
12
|
+
* the EC-277 reference index (derived from the stored draft at the EC-286
|
|
13
|
+
* write chokepoint) and stops blocking its target's delete/unpublish.
|
|
14
|
+
*
|
|
15
|
+
* The manifests resolve through the SESSION registry by default (client-side-
|
|
16
|
+
* with-user-session, the ratified trust boundary — same binding `doPublish`
|
|
17
|
+
* used inline since EC-394); tests inject a resolver to pin behaviour against
|
|
18
|
+
* fixture manifests. Identity-preserving: returns the input document when
|
|
19
|
+
* nothing clears, so callers can save-if-changed.
|
|
20
|
+
*/
|
|
21
|
+
export declare function cleanHiddenForPublish(collection: CollectionDef, doc: CmsDocument, resolveManifest?: ManifestResolver): CmsDocument;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { clearHiddenValues } from '@elytracms/core/cms-core';
|
|
2
|
+
import { clearHiddenProps } from '@elytracms/core/component-registry';
|
|
3
|
+
import { getStudioRegistry } from '@elytracms/studio/lib/registry';
|
|
4
|
+
/**
|
|
5
|
+
* EC-425 — THE publish-time hidden-strip, extracted so single-document publish
|
|
6
|
+
* (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)
|
|
7
|
+
* share ONE clean step instead of the bulk bar bypassing it:
|
|
8
|
+
*
|
|
9
|
+
* - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values
|
|
10
|
+
* (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`.
|
|
11
|
+
* - EC-394 `clearHiddenProps`: the same strip for component props across every
|
|
12
|
+
* `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves
|
|
13
|
+
* the EC-277 reference index (derived from the stored draft at the EC-286
|
|
14
|
+
* write chokepoint) and stops blocking its target's delete/unpublish.
|
|
15
|
+
*
|
|
16
|
+
* The manifests resolve through the SESSION registry by default (client-side-
|
|
17
|
+
* with-user-session, the ratified trust boundary — same binding `doPublish`
|
|
18
|
+
* used inline since EC-394); tests inject a resolver to pin behaviour against
|
|
19
|
+
* fixture manifests. Identity-preserving: returns the input document when
|
|
20
|
+
* nothing clears, so callers can save-if-changed.
|
|
21
|
+
*/
|
|
22
|
+
export function cleanHiddenForPublish(collection, doc, resolveManifest = sessionManifestResolver()) {
|
|
23
|
+
return clearHiddenProps(collection, clearHiddenValues(collection, doc), resolveManifest);
|
|
24
|
+
}
|
|
25
|
+
/** The session component registry as a {@link ManifestResolver}. */
|
|
26
|
+
function sessionManifestResolver() {
|
|
27
|
+
const { registry } = getStudioRegistry();
|
|
28
|
+
return (id) => registry.getManifest(id);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=publish-clean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-clean.js","sourceRoot":"","sources":["../../../src/components/cms/publish-clean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAElE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAyB,EACzB,GAAgB,EAChB,kBAAoC,uBAAuB,EAAE;IAE7D,OAAO,gBAAgB,CAAC,UAAU,EAAE,iBAAiB,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,eAAe,CAAC,CAAA;AAC1F,CAAC;AAED,oEAAoE;AACpE,SAAS,uBAAuB;IAC9B,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;AACzC,CAAC","sourcesContent":["import { clearHiddenValues } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef } from '@elytracms/core/cms-core'\nimport { clearHiddenProps } from '@elytracms/core/component-registry'\nimport type { ManifestResolver } from '@elytracms/core/component-registry'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\n\n/**\n * EC-425 — THE publish-time hidden-strip, extracted so single-document publish\n * (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)\n * share ONE clean step instead of the bulk bar bypassing it:\n *\n * - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values\n * (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`.\n * - EC-394 `clearHiddenProps`: the same strip for component props across every\n * `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves\n * the EC-277 reference index (derived from the stored draft at the EC-286\n * write chokepoint) and stops blocking its target's delete/unpublish.\n *\n * The manifests resolve through the SESSION registry by default (client-side-\n * with-user-session, the ratified trust boundary — same binding `doPublish`\n * used inline since EC-394); tests inject a resolver to pin behaviour against\n * fixture manifests. Identity-preserving: returns the input document when\n * nothing clears, so callers can save-if-changed.\n */\nexport function cleanHiddenForPublish(\n collection: CollectionDef,\n doc: CmsDocument,\n resolveManifest: ManifestResolver = sessionManifestResolver(),\n): CmsDocument {\n return clearHiddenProps(collection, clearHiddenValues(collection, doc), resolveManifest)\n}\n\n/** The session component registry as a {@link ManifestResolver}. */\nfunction sessionManifestResolver(): ManifestResolver {\n const { registry } = getStudioRegistry()\n return (id) => registry.getManifest(id)\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/react';
|
|
2
|
+
/**
|
|
3
|
+
* Shared block-style vocabulary for the rich-text toolbar (EC-418).
|
|
4
|
+
*
|
|
5
|
+
* The block-style dropdown's TRIGGER and its MENU ITEMS must show the SAME
|
|
6
|
+
* icon-per-style so they can never drift: paragraph → the lucide `Pilcrow`, a
|
|
7
|
+
* heading → its compact `H{level}` badge. Declaring the mapping ONCE here —
|
|
8
|
+
* consumed by both — makes that drift structurally impossible, the same pattern
|
|
9
|
+
* `block-commands.ts` (EC-328) uses for the list/quote/code commands.
|
|
10
|
+
*
|
|
11
|
+
* This also underpins the EC-418 width-stability fix: the trigger renders the
|
|
12
|
+
* ACTIVE style's fixed-width glyph instead of its variable-width localized label
|
|
13
|
+
* ("Absatz" ≪ "Überschrift 2"), so caret movement across styles never reflows the
|
|
14
|
+
* strip. The full localized labels appear only in the opened menu, and the trigger
|
|
15
|
+
* keeps its accessible name via {@link blockStyleLabel} (an `aria-label`).
|
|
16
|
+
*/
|
|
17
|
+
/** A block-style option: paragraph (`headingLevel: null`) or a heading level. */
|
|
18
|
+
export interface BlockStyleDescriptor {
|
|
19
|
+
/** Heading level, or `null` for paragraph (the base body style). */
|
|
20
|
+
headingLevel: number | null;
|
|
21
|
+
/** Apply this style on an already-`.focus()`ed chain. */
|
|
22
|
+
chain: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>;
|
|
23
|
+
}
|
|
24
|
+
/** The block styles the toolbar offers, in menu order (paragraph, then H1–H3). */
|
|
25
|
+
export declare const BLOCK_STYLES: readonly BlockStyleDescriptor[];
|
|
26
|
+
/**
|
|
27
|
+
* The pure icon-per-style decision (EC-418) — the single source both the trigger
|
|
28
|
+
* and the menu items derive their glyph from (via {@link BlockStyleGlyph}). Kept
|
|
29
|
+
* data-only (no JSX) so it is unit-testable in a node env without a DOM.
|
|
30
|
+
*/
|
|
31
|
+
export type BlockStyleIcon = {
|
|
32
|
+
kind: 'paragraph';
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'heading';
|
|
35
|
+
level: number;
|
|
36
|
+
};
|
|
37
|
+
export declare function blockStyleIcon(headingLevel: number | null): BlockStyleIcon;
|
|
38
|
+
/** The localized label for a block style — the menu-item text and the trigger's a11y name. */
|
|
39
|
+
export declare function blockStyleLabel(headingLevel: number | null): string;
|
|
40
|
+
/**
|
|
41
|
+
* The one glyph rendered for a block style, shared by the trigger and the menu
|
|
42
|
+
* items (EC-418). Fixed-width by construction: the `Pilcrow` icon is `size-3.5`
|
|
43
|
+
* (14px) and the heading badge is `w-3.5` (14px), so the active-style glyph never
|
|
44
|
+
* changes the trigger's width as the selection moves across styles.
|
|
45
|
+
*/
|
|
46
|
+
export declare function BlockStyleGlyph({ headingLevel }: {
|
|
47
|
+
headingLevel: number | null;
|
|
48
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Pilcrow } from 'lucide-react';
|
|
3
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
4
|
+
/** The block styles the toolbar offers, in menu order (paragraph, then H1–H3). */
|
|
5
|
+
export const BLOCK_STYLES = [
|
|
6
|
+
{ headingLevel: null, chain: (c) => c.setParagraph() },
|
|
7
|
+
{ headingLevel: 1, chain: (c) => c.toggleHeading({ level: 1 }) },
|
|
8
|
+
{ headingLevel: 2, chain: (c) => c.toggleHeading({ level: 2 }) },
|
|
9
|
+
{ headingLevel: 3, chain: (c) => c.toggleHeading({ level: 3 }) },
|
|
10
|
+
];
|
|
11
|
+
export function blockStyleIcon(headingLevel) {
|
|
12
|
+
return headingLevel == null ? { kind: 'paragraph' } : { kind: 'heading', level: headingLevel };
|
|
13
|
+
}
|
|
14
|
+
/** The localized label for a block style — the menu-item text and the trigger's a11y name. */
|
|
15
|
+
export function blockStyleLabel(headingLevel) {
|
|
16
|
+
return headingLevel == null
|
|
17
|
+
? translate('richText.paragraph')
|
|
18
|
+
: translate('richText.heading', { level: headingLevel });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The one glyph rendered for a block style, shared by the trigger and the menu
|
|
22
|
+
* items (EC-418). Fixed-width by construction: the `Pilcrow` icon is `size-3.5`
|
|
23
|
+
* (14px) and the heading badge is `w-3.5` (14px), so the active-style glyph never
|
|
24
|
+
* changes the trigger's width as the selection moves across styles.
|
|
25
|
+
*/
|
|
26
|
+
export function BlockStyleGlyph({ headingLevel }) {
|
|
27
|
+
const icon = blockStyleIcon(headingLevel);
|
|
28
|
+
return icon.kind === 'paragraph' ? (_jsx(Pilcrow, { className: "size-3.5" })) : (_jsxs("span", { className: "w-3.5 text-center text-xs font-semibold", children: ["H", icon.level] }));
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=block-style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-style.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/block-style.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AA0BtD,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAAoC;IAC3D,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,EAAE;IACtD,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE;IAChE,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE;IAChE,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE;CACxD,CAAA;AASV,MAAM,UAAU,cAAc,CAAC,YAA2B;IACxD,OAAO,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;AAChG,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,eAAe,CAAC,YAA2B;IACzD,OAAO,YAAY,IAAI,IAAI;QACzB,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC;QACjC,CAAC,CAAC,SAAS,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,YAAY,EAAmC;IAC/E,MAAM,IAAI,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;IACzC,OAAO,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,CACjC,KAAC,OAAO,IAAC,SAAS,EAAC,UAAU,GAAG,CACjC,CAAC,CAAC,CAAC,CACF,gBAAM,SAAS,EAAC,yCAAyC,kBAAG,IAAI,CAAC,KAAK,IAAQ,CAC/E,CAAA;AACH,CAAC","sourcesContent":["import { Pilcrow } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Shared block-style vocabulary for the rich-text toolbar (EC-418).\n *\n * The block-style dropdown's TRIGGER and its MENU ITEMS must show the SAME\n * icon-per-style so they can never drift: paragraph → the lucide `Pilcrow`, a\n * heading → its compact `H{level}` badge. Declaring the mapping ONCE here —\n * consumed by both — makes that drift structurally impossible, the same pattern\n * `block-commands.ts` (EC-328) uses for the list/quote/code commands.\n *\n * This also underpins the EC-418 width-stability fix: the trigger renders the\n * ACTIVE style's fixed-width glyph instead of its variable-width localized label\n * (\"Absatz\" ≪ \"Überschrift 2\"), so caret movement across styles never reflows the\n * strip. The full localized labels appear only in the opened menu, and the trigger\n * keeps its accessible name via {@link blockStyleLabel} (an `aria-label`).\n */\n\n/** A block-style option: paragraph (`headingLevel: null`) or a heading level. */\nexport interface BlockStyleDescriptor {\n /** Heading level, or `null` for paragraph (the base body style). */\n headingLevel: number | null\n /** Apply this style on an already-`.focus()`ed chain. */\n chain: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>\n}\n\n/** The block styles the toolbar offers, in menu order (paragraph, then H1–H3). */\nexport const BLOCK_STYLES: readonly BlockStyleDescriptor[] = [\n { headingLevel: null, chain: (c) => c.setParagraph() },\n { headingLevel: 1, chain: (c) => c.toggleHeading({ level: 1 }) },\n { headingLevel: 2, chain: (c) => c.toggleHeading({ level: 2 }) },\n { headingLevel: 3, chain: (c) => c.toggleHeading({ level: 3 }) },\n] as const\n\n/**\n * The pure icon-per-style decision (EC-418) — the single source both the trigger\n * and the menu items derive their glyph from (via {@link BlockStyleGlyph}). Kept\n * data-only (no JSX) so it is unit-testable in a node env without a DOM.\n */\nexport type BlockStyleIcon = { kind: 'paragraph' } | { kind: 'heading'; level: number }\n\nexport function blockStyleIcon(headingLevel: number | null): BlockStyleIcon {\n return headingLevel == null ? { kind: 'paragraph' } : { kind: 'heading', level: headingLevel }\n}\n\n/** The localized label for a block style — the menu-item text and the trigger's a11y name. */\nexport function blockStyleLabel(headingLevel: number | null): string {\n return headingLevel == null\n ? translate('richText.paragraph')\n : translate('richText.heading', { level: headingLevel })\n}\n\n/**\n * The one glyph rendered for a block style, shared by the trigger and the menu\n * items (EC-418). Fixed-width by construction: the `Pilcrow` icon is `size-3.5`\n * (14px) and the heading badge is `w-3.5` (14px), so the active-style glyph never\n * changes the trigger's width as the selection moves across styles.\n */\nexport function BlockStyleGlyph({ headingLevel }: { headingLevel: number | null }) {\n const icon = blockStyleIcon(headingLevel)\n return icon.kind === 'paragraph' ? (\n <Pilcrow className=\"size-3.5\" />\n ) : (\n <span className=\"w-3.5 text-center text-xs font-semibold\">H{icon.level}</span>\n )\n}\n"]}
|
|
@@ -3,7 +3,8 @@ import { useMemo, useState } from 'react';
|
|
|
3
3
|
import { Blocks } from 'lucide-react';
|
|
4
4
|
import { getStudioRegistry } from '@elytracms/studio/lib/registry';
|
|
5
5
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, Input, Stack, Text, } from '@elytracms/studio/ui';
|
|
6
|
-
import {
|
|
6
|
+
import { localizedStringVariants } from '@elytracms/core/cms-core';
|
|
7
|
+
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
7
8
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
8
9
|
import { listEmbeddable } from './insert.js';
|
|
9
10
|
/**
|
|
@@ -18,13 +19,13 @@ export function EmbedPicker({ open, onOpenChange, allow, onSelect }) {
|
|
|
18
19
|
const q = query.trim().toLowerCase();
|
|
19
20
|
if (!q)
|
|
20
21
|
return embeddable;
|
|
21
|
-
return embeddable.filter((m) =>
|
|
22
|
+
return embeddable.filter((m) => [m.id, ...localizedStringVariants(m.title)].some((s) => s.toLowerCase().includes(q)));
|
|
22
23
|
}, [embeddable, query]);
|
|
23
24
|
function choose(manifest) {
|
|
24
25
|
onSelect(manifest);
|
|
25
26
|
onOpenChange(false);
|
|
26
27
|
setQuery('');
|
|
27
28
|
}
|
|
28
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: translate('richText.embed.add') }) }), _jsxs(Stack, { gap: "2", children: [_jsx(Input, { autoFocus: true, value: query, placeholder: translate('richText.link.documentSearch'), onChange: (e) => setQuery(e.target.value) }), _jsx(Stack, { gap: "none", className: "max-h-72 overflow-y-auto", children: results.length === 0 ? (_jsx(Text, { as: "span", size: "xs", tone: "muted", className: "px-1 py-2", children: translate('richText.embed.empty') })) : (results.map((manifest) => (_jsxs("button", { type: "button", onClick: () => choose(manifest), className: cn('flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left', 'hover:bg-muted focus-visible:bg-muted outline-none'), 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 text-sm font-medium", children: manifest.title
|
|
29
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: translate('richText.embed.add') }) }), _jsxs(Stack, { gap: "2", children: [_jsx(Input, { autoFocus: true, value: query, placeholder: translate('richText.link.documentSearch'), onChange: (e) => setQuery(e.target.value) }), _jsx(Stack, { gap: "none", className: "max-h-72 overflow-y-auto", children: results.length === 0 ? (_jsx(Text, { as: "span", size: "xs", tone: "muted", className: "px-1 py-2", children: translate('richText.embed.empty') })) : (results.map((manifest) => (_jsxs("button", { type: "button", onClick: () => choose(manifest), className: cn('flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left', 'hover:bg-muted focus-visible:bg-muted outline-none'), 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 text-sm font-medium", children: localizedLabel(manifest.title, manifest.id) }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: manifest.id })] })] }, manifest.id)))) })] })] }) }));
|
|
29
30
|
}
|
|
30
31
|
//# sourceMappingURL=embed-picker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-picker.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-picker.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EACL,MAAM,EACN,aAAa,EACb,YAAY,EACZ,WAAW,EACX,KAAK,EACL,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"embed-picker.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { Blocks } from 'lucide-react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n Input,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { listEmbeddable } from './insert'\n\ninterface EmbedPickerProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** The field's embed `allow` list (component ids). Empty/absent ⇒ any non-primitive. */\n allow?: string[]\n onSelect: (manifest: ComponentManifest) => void\n}\n\n/**\n * The component-embed picker dialog (EC-314/EC-189): a searchable list of the\n * components permitted for inline embedding. Rendered once at the editor level and\n * opened by the toolbar button or the `/embed` slash command.\n */\nexport function EmbedPicker({ open, onOpenChange, allow, onSelect }: EmbedPickerProps) {\n const [query, setQuery] = useState('')\n\n const embeddable = useMemo(\n () => listEmbeddable(allow, getStudioRegistry().registry.list()),\n [allow],\n )\n\n const results = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return embeddable\n return embeddable.filter((m) =>\n [m.id, ...localizedStringVariants(m.title)].some((s) => s.toLowerCase().includes(q)),\n )\n }, [embeddable, query])\n\n function choose(manifest: ComponentManifest) {\n onSelect(manifest)\n onOpenChange(false)\n setQuery('')\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>{translate('richText.embed.add')}</DialogTitle>\n </DialogHeader>\n <Stack gap=\"2\">\n <Input\n autoFocus\n value={query}\n placeholder={translate('richText.link.documentSearch')}\n onChange={(e) => setQuery(e.target.value)}\n />\n <Stack gap=\"none\" className=\"max-h-72 overflow-y-auto\">\n {results.length === 0 ? (\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"px-1 py-2\">\n {translate('richText.embed.empty')}\n </Text>\n ) : (\n results.map((manifest) => (\n <button\n key={manifest.id}\n type=\"button\"\n onClick={() => choose(manifest)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left',\n 'hover:bg-muted focus-visible:bg-muted outline-none',\n )}\n >\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 text-sm font-medium\">{localizedLabel(manifest.title, manifest.id)}</span>\n <span className=\"truncate text-xs text-muted-foreground\">{manifest.id}</span>\n </span>\n </button>\n ))\n )}\n </Stack>\n </Stack>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
@@ -17,6 +17,8 @@ import type { SlashCommandOptions } from './slash-command.js';
|
|
|
17
17
|
* - {@link AssetImage} / {@link ComponentEmbed} declare the two reference-carrying
|
|
18
18
|
* atoms (asset image, embedded registry component) so existing content of those
|
|
19
19
|
* kinds survives a round-trip verbatim rather than being dropped by the schema.
|
|
20
|
+
* - {@link FontSize} declares the semantic font-size mark (EC-411) so a sized run
|
|
21
|
+
* round-trips (an undeclared mark would be silently dropped on load).
|
|
20
22
|
*
|
|
21
23
|
* No `allow` gating lives here — that governs *insertion* (the embed picker) and
|
|
22
24
|
* *validation* (`validateRichTextEmbeds`), not which nodes the schema tolerates.
|
|
@@ -2,6 +2,7 @@ import StarterKit from '@tiptap/starter-kit';
|
|
|
2
2
|
import { Placeholder } from '@tiptap/extensions';
|
|
3
3
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
4
4
|
import { RefLink } from './ref-link.js';
|
|
5
|
+
import { FontSize } from './font-size.js';
|
|
5
6
|
import { AssetImage } from './asset-image.js';
|
|
6
7
|
import { ComponentEmbed } from './component-embed.js';
|
|
7
8
|
import { createSlashCommand } from './slash-command.js';
|
|
@@ -22,6 +23,8 @@ import { createSlashCommand } from './slash-command.js';
|
|
|
22
23
|
* - {@link AssetImage} / {@link ComponentEmbed} declare the two reference-carrying
|
|
23
24
|
* atoms (asset image, embedded registry component) so existing content of those
|
|
24
25
|
* kinds survives a round-trip verbatim rather than being dropped by the schema.
|
|
26
|
+
* - {@link FontSize} declares the semantic font-size mark (EC-411) so a sized run
|
|
27
|
+
* round-trips (an undeclared mark would be silently dropped on load).
|
|
25
28
|
*
|
|
26
29
|
* No `allow` gating lives here — that governs *insertion* (the embed picker) and
|
|
27
30
|
* *validation* (`validateRichTextEmbeds`), not which nodes the schema tolerates.
|
|
@@ -36,6 +39,7 @@ export function buildRichTextExtensions(options = {}) {
|
|
|
36
39
|
heading: { levels: [1, 2, 3, 4, 5, 6] },
|
|
37
40
|
}),
|
|
38
41
|
RefLink.configure({ openOnClick: false, autolink: true, linkOnPaste: true }),
|
|
42
|
+
FontSize,
|
|
39
43
|
AssetImage,
|
|
40
44
|
ComponentEmbed,
|
|
41
45
|
Placeholder.configure({ placeholder: translate('richText.placeholder') }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA2C,EAAE;IACnF,OAAO;QACL,UAAU,CAAC,SAAS,CAAC;YACnB,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;SACxC,CAAC;QACF,OAAO,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC5E,QAAQ;QACR,UAAU;QACV,cAAc;QACd,WAAW,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACzE,kBAAkB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;KACxC,CAAA;AACH,CAAC","sourcesContent":["import StarterKit from '@tiptap/starter-kit'\nimport { Placeholder } from '@tiptap/extensions'\nimport type { Extensions } from '@tiptap/react'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { RefLink } from './ref-link'\nimport { FontSize } from './font-size'\nimport { AssetImage } from './asset-image'\nimport { ComponentEmbed } from './component-embed'\nimport { createSlashCommand } from './slash-command'\nimport type { SlashCommandOptions } from './slash-command'\n\n/**\n * The TipTap extension set for the CMS rich-text editor (EC-314).\n *\n * Deliberately matches the storage vocabulary (`@elytracms/core/rich-text` schema)\n * one-for-one so every editor document round-trips through `parseRichTextValue` /\n * the delivery renderer with no format change:\n *\n * - **StarterKit** supplies `doc/paragraph/text/heading/bulletList/orderedList/\n * listItem/blockquote/codeBlock/hardBreak/horizontalRule` and the marks\n * `bold/italic/code/strike/underline` — all ProseMirror-standard names identical\n * to storage. Its bundled `link` is disabled and replaced by {@link RefLink} so\n * internal-document links can carry a structured `ref` (EC-290). Headings allow\n * all six levels so existing `h4`–`h6` content is preserved even though the\n * toolbar surfaces only 1–3.\n * - {@link AssetImage} / {@link ComponentEmbed} declare the two reference-carrying\n * atoms (asset image, embedded registry component) so existing content of those\n * kinds survives a round-trip verbatim rather than being dropped by the schema.\n * - {@link FontSize} declares the semantic font-size mark (EC-411) so a sized run\n * round-trips (an undeclared mark would be silently dropped on load).\n *\n * No `allow` gating lives here — that governs *insertion* (the embed picker) and\n * *validation* (`validateRichTextEmbeds`), not which nodes the schema tolerates.\n *\n * `slash` wires the Notion-style `/` menu to the editor's shared image/embed pickers\n * (omit an action to hide that slash item).\n */\nexport function buildRichTextExtensions(options: { slash?: SlashCommandOptions } = {}): Extensions {\n return [\n StarterKit.configure({\n link: false,\n heading: { levels: [1, 2, 3, 4, 5, 6] },\n }),\n RefLink.configure({ openOnClick: false, autolink: true, linkOnPaste: true }),\n FontSize,\n AssetImage,\n ComponentEmbed,\n Placeholder.configure({ placeholder: translate('richText.placeholder') }),\n createSlashCommand(options.slash ?? {}),\n ]\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/react';
|
|
2
|
+
import type { RichTextFontSize } from '@elytracms/core/rich-text';
|
|
3
|
+
import type { MessageKey } from '@elytracms/studio/lib/i18n';
|
|
4
|
+
/**
|
|
5
|
+
* A font-size preset in the toolbar's dropdown (EC-411). `size: null` is the
|
|
6
|
+
* markless *normal* step — selecting it REMOVES the mark rather than storing a value.
|
|
7
|
+
*/
|
|
8
|
+
export interface FontSizePreset {
|
|
9
|
+
size: RichTextFontSize | null;
|
|
10
|
+
labelKey: MessageKey;
|
|
11
|
+
}
|
|
12
|
+
/** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */
|
|
13
|
+
export declare const FONT_SIZE_PRESETS: readonly FontSizePreset[];
|
|
14
|
+
/** Whether a preset is the currently-active step (normal ⇔ no mark ⇔ activeSize null). */
|
|
15
|
+
export declare function isPresetActive(preset: FontSizePreset, activeSize: RichTextFontSize | null): boolean;
|
|
16
|
+
/** Apply a preset on a focused chain: set the mark for a step, or unset it for normal. */
|
|
17
|
+
export declare function applyFontSizePreset(editor: Editor, size: RichTextFontSize | null): void;
|
|
18
|
+
interface FontSizeControlProps {
|
|
19
|
+
editor: Editor;
|
|
20
|
+
/** The active semantic step (from the toolbar state), or null for normal. */
|
|
21
|
+
activeSize: RichTextFontSize | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The rich-text font-size control (EC-411). Width-stable under the EC-418 contract
|
|
25
|
+
* (AD-4): the trigger shows a fixed-width icon (+ ChevronDown) in the SAME fixed box
|
|
26
|
+
* as the block-style trigger ({@link RICH_TEXT_MENU_TRIGGER_CLASS}) — the size labels
|
|
27
|
+
* live only in the opened menu, never on the strip, so the control never resizes with
|
|
28
|
+
* selection. The trigger keeps an accessible name via an `aria-label`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function FontSizeControl({ editor, activeSize }: FontSizeControlProps): import("react").JSX.Element;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ALargeSmall, Check, ChevronDown } from 'lucide-react';
|
|
3
|
+
import { FONT_SIZE_MARK_TYPE } from '@elytracms/core/rich-text';
|
|
4
|
+
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@elytracms/studio/ui';
|
|
5
|
+
import { cn } from '@elytracms/studio/lib/utils';
|
|
6
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
7
|
+
import { RICH_TEXT_MENU_TRIGGER_CLASS } from './toolbar.js';
|
|
8
|
+
/** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */
|
|
9
|
+
export const FONT_SIZE_PRESETS = [
|
|
10
|
+
{ size: 'sm', labelKey: 'richText.fontSize.small' },
|
|
11
|
+
{ size: null, labelKey: 'richText.fontSize.normal' },
|
|
12
|
+
{ size: 'lg', labelKey: 'richText.fontSize.large' },
|
|
13
|
+
{ size: 'xl', labelKey: 'richText.fontSize.xlarge' },
|
|
14
|
+
];
|
|
15
|
+
/** Whether a preset is the currently-active step (normal ⇔ no mark ⇔ activeSize null). */
|
|
16
|
+
export function isPresetActive(preset, activeSize) {
|
|
17
|
+
return preset.size === activeSize;
|
|
18
|
+
}
|
|
19
|
+
/** Apply a preset on a focused chain: set the mark for a step, or unset it for normal. */
|
|
20
|
+
export function applyFontSizePreset(editor, size) {
|
|
21
|
+
const chain = editor.chain().focus();
|
|
22
|
+
if (size)
|
|
23
|
+
chain.setMark(FONT_SIZE_MARK_TYPE, { size }).run();
|
|
24
|
+
else
|
|
25
|
+
chain.unsetMark(FONT_SIZE_MARK_TYPE).run();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The rich-text font-size control (EC-411). Width-stable under the EC-418 contract
|
|
29
|
+
* (AD-4): the trigger shows a fixed-width icon (+ ChevronDown) in the SAME fixed box
|
|
30
|
+
* as the block-style trigger ({@link RICH_TEXT_MENU_TRIGGER_CLASS}) — the size labels
|
|
31
|
+
* live only in the opened menu, never on the strip, so the control never resizes with
|
|
32
|
+
* selection. The trigger keeps an accessible name via an `aria-label`.
|
|
33
|
+
*/
|
|
34
|
+
export function FontSizeControl({ editor, activeSize }) {
|
|
35
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: RICH_TEXT_MENU_TRIGGER_CLASS, "aria-label": translate('richText.fontSize.label'), title: translate('richText.fontSize.label'), onMouseDown: (e) => e.preventDefault(), children: [_jsx(ALargeSmall, { className: "size-3.5" }), _jsx(ChevronDown, { className: "size-3 opacity-60" })] }) }), _jsx(DropdownMenuContent, { align: "start", className: "min-w-40", children: FONT_SIZE_PRESETS.map((preset) => {
|
|
36
|
+
const active = isPresetActive(preset, activeSize);
|
|
37
|
+
return (_jsxs(DropdownMenuItem, { onSelect: () => applyFontSizePreset(editor, preset.size), children: [_jsx(Check, { className: cn('size-3.5', !active && 'invisible') }), ' ', translate(preset.labelKey)] }, preset.size ?? 'normal'));
|
|
38
|
+
}) })] }));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=font-size-control.js.map
|