@elytracms/studio 0.0.57 → 0.0.59

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 (109) hide show
  1. package/dist/components/ai/ai-chat-panel.js +44 -6
  2. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  3. package/dist/components/ai/chat-ui.d.ts +1 -1
  4. package/dist/components/ai/chat-ui.js +11 -2
  5. package/dist/components/ai/chat-ui.js.map +1 -1
  6. package/dist/components/ai/proposal-card.d.ts +30 -7
  7. package/dist/components/ai/proposal-card.js +81 -21
  8. package/dist/components/ai/proposal-card.js.map +1 -1
  9. package/dist/components/builder/blocks-model.js +12 -7
  10. package/dist/components/builder/blocks-model.js.map +1 -1
  11. package/dist/components/builder/canvas-dnd.d.ts +59 -0
  12. package/dist/components/builder/canvas-dnd.js +207 -0
  13. package/dist/components/builder/canvas-dnd.js.map +1 -0
  14. package/dist/components/builder/canvas-dom.d.ts +11 -0
  15. package/dist/components/builder/canvas-dom.js +17 -0
  16. package/dist/components/builder/canvas-dom.js.map +1 -0
  17. package/dist/components/builder/canvas-inline-editing.d.ts +91 -0
  18. package/dist/components/builder/canvas-inline-editing.js +242 -0
  19. package/dist/components/builder/canvas-inline-editing.js.map +1 -0
  20. package/dist/components/builder/canvas-render-context.d.ts +83 -0
  21. package/dist/components/builder/canvas-render-context.js +196 -0
  22. package/dist/components/builder/canvas-render-context.js.map +1 -0
  23. package/dist/components/builder/canvas.d.ts +4 -1
  24. package/dist/components/builder/canvas.js +32 -567
  25. package/dist/components/builder/canvas.js.map +1 -1
  26. package/dist/components/builder/composition-field-editor.js +14 -4
  27. package/dist/components/builder/composition-field-editor.js.map +1 -1
  28. package/dist/components/builder/insert-context.d.ts +5 -2
  29. package/dist/components/builder/insert-context.js +1 -3
  30. package/dist/components/builder/insert-context.js.map +1 -1
  31. package/dist/components/builder/node-factory.d.ts +12 -0
  32. package/dist/components/builder/node-factory.js +18 -1
  33. package/dist/components/builder/node-factory.js.map +1 -1
  34. package/dist/components/cms/asset-field.d.ts +22 -0
  35. package/dist/components/cms/asset-field.js +300 -0
  36. package/dist/components/cms/asset-field.js.map +1 -0
  37. package/dist/components/cms/detail-editor.js +12 -0
  38. package/dist/components/cms/detail-editor.js.map +1 -1
  39. package/dist/components/cms/document-diff-view.d.ts +12 -1
  40. package/dist/components/cms/document-diff-view.js +119 -42
  41. package/dist/components/cms/document-diff-view.js.map +1 -1
  42. package/dist/components/cms/field-control-lib.d.ts +43 -0
  43. package/dist/components/cms/field-control-lib.js +98 -0
  44. package/dist/components/cms/field-control-lib.js.map +1 -0
  45. package/dist/components/cms/field-control.d.ts +7 -35
  46. package/dist/components/cms/field-control.js +13 -556
  47. package/dist/components/cms/field-control.js.map +1 -1
  48. package/dist/components/cms/object-array-field.d.ts +29 -0
  49. package/dist/components/cms/object-array-field.js +205 -0
  50. package/dist/components/cms/object-array-field.js.map +1 -0
  51. package/dist/components/cms/rich-text-preview.d.ts +15 -0
  52. package/dist/components/cms/rich-text-preview.js +81 -0
  53. package/dist/components/cms/rich-text-preview.js.map +1 -0
  54. package/dist/components/cms/version-compare-text.d.ts +26 -0
  55. package/dist/components/cms/version-compare-text.js +60 -0
  56. package/dist/components/cms/version-compare-text.js.map +1 -1
  57. package/dist/components/project-session.d.ts +19 -0
  58. package/dist/components/project-session.js +29 -2
  59. package/dist/components/project-session.js.map +1 -1
  60. package/dist/components/use-hydration-phase.d.ts +6 -4
  61. package/dist/components/use-hydration-phase.js.map +1 -1
  62. package/dist/lib/ai/apply-content.js +52 -10
  63. package/dist/lib/ai/apply-content.js.map +1 -1
  64. package/dist/lib/ai/chat-context.d.ts +19 -3
  65. package/dist/lib/ai/chat-context.js +33 -4
  66. package/dist/lib/ai/chat-context.js.map +1 -1
  67. package/dist/lib/ai/chat-state.d.ts +6 -3
  68. package/dist/lib/ai/chat-state.js.map +1 -1
  69. package/dist/lib/ai/fake-provider.d.ts +60 -5
  70. package/dist/lib/ai/fake-provider.js +74 -6
  71. package/dist/lib/ai/fake-provider.js.map +1 -1
  72. package/dist/lib/ai/index.d.ts +2 -0
  73. package/dist/lib/ai/index.js +2 -0
  74. package/dist/lib/ai/index.js.map +1 -1
  75. package/dist/lib/ai/rich-text-input.d.ts +27 -0
  76. package/dist/lib/ai/rich-text-input.js +25 -0
  77. package/dist/lib/ai/rich-text-input.js.map +1 -0
  78. package/dist/lib/ai/shape.d.ts +12 -1
  79. package/dist/lib/ai/shape.js +46 -20
  80. package/dist/lib/ai/shape.js.map +1 -1
  81. package/dist/lib/ai/tools.d.ts +88 -13
  82. package/dist/lib/ai/tools.js +253 -14
  83. package/dist/lib/ai/tools.js.map +1 -1
  84. package/dist/lib/ai/types.d.ts +20 -0
  85. package/dist/lib/ai/types.js.map +1 -1
  86. package/dist/lib/ai/update-fields.d.ts +68 -0
  87. package/dist/lib/ai/update-fields.js +296 -0
  88. package/dist/lib/ai/update-fields.js.map +1 -0
  89. package/dist/lib/ai-current-document.d.ts +53 -0
  90. package/dist/lib/ai-current-document.js +54 -0
  91. package/dist/lib/ai-current-document.js.map +1 -0
  92. package/dist/lib/cms-views/view-model.d.ts +6 -6
  93. package/dist/lib/data/collection-content.d.ts +12 -7
  94. package/dist/lib/data/collection-content.js +6 -4
  95. package/dist/lib/data/collection-content.js.map +1 -1
  96. package/dist/lib/i18n/messages.en.js +10 -1
  97. package/dist/lib/i18n/messages.en.js.map +1 -1
  98. package/dist/lib/operations/session-hydration.d.ts +4 -1
  99. package/dist/lib/operations/session-hydration.js +4 -1
  100. package/dist/lib/operations/session-hydration.js.map +1 -1
  101. package/dist/lib/persistence/index.js +13 -1
  102. package/dist/lib/persistence/index.js.map +1 -1
  103. package/dist/lib/workspace/index.d.ts +1 -1
  104. package/dist/lib/workspace/index.js +1 -1
  105. package/dist/lib/workspace/index.js.map +1 -1
  106. package/dist/lib/workspace/workspace-source.d.ts +17 -22
  107. package/dist/lib/workspace/workspace-source.js +1 -49
  108. package/dist/lib/workspace/workspace-source.js.map +1 -1
  109. package/package.json +2 -2
@@ -0,0 +1,43 @@
1
+ import type { ObjectFieldDef, SelectFieldDef } from '@elytracms/core/cms-core';
2
+ /**
3
+ * Pure companions of the document-editor field controls (EC-330, split from
4
+ * `field-control.tsx`): the repeater reorder/summary/shape decisions and the
5
+ * depth-encoding palette the object-array editors render from, plus the
6
+ * `options` segmented control. Small and dependency-light so the behavioural
7
+ * pins in `field-control.test.ts` reach them without pulling the whole
8
+ * field-control dispatcher.
9
+ */
10
+ /**
11
+ * Move `from` → `to`, returning a NEW array (all elements preserved). The repeater
12
+ * applies the same `(from, to)` to its value array AND its parallel stable-key array,
13
+ * so the two stay aligned through a reorder — a row's child/DOM state (an open
14
+ * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).
15
+ */
16
+ export declare function reorder<T>(list: readonly T[], from: number, to: number): T[];
17
+ /**
18
+ * A scannable one-line summary for a collapsed repeater item (EC-294): the first
19
+ * meaningful scalar subfield value — preferring title-ish field names — else `#n`.
20
+ * Pure (no store): summarises from the item's own scalar values, so it works for
21
+ * any object shape without resolving relations/assets.
22
+ */
23
+ export declare function objectItemSummary(fields: readonly {
24
+ name: string;
25
+ }[], item: Record<string, unknown>, index: number): string;
26
+ export declare const depthRail: (d: number) => string;
27
+ export declare const depthTint: (d: number) => string;
28
+ export declare function isTabularObjectType(field: ObjectFieldDef): boolean;
29
+ /**
30
+ * EC-295: the `options` control — a `select` shown as inline segmented buttons
31
+ * (`[[Page]] [URL] [File]`) instead of a dropdown. Engine built-in (no consumer
32
+ * registration); single-select only. Each option shows `label ?? value` and a
33
+ * `title` tooltip from the option's `description`; the field `default` + `readOnly`
34
+ * flow through the shared form. Picks one — to allow an empty/unset value use the
35
+ * default dropdown control.
36
+ */
37
+ export declare function SegmentedOptions({ field, value, onChange, readOnly, id, }: {
38
+ field: SelectFieldDef;
39
+ value: unknown;
40
+ onChange: (value: unknown) => void;
41
+ readOnly: boolean;
42
+ id: string;
43
+ }): import("react").JSX.Element;
@@ -0,0 +1,98 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button, Row } from '@elytracms/studio/ui';
3
+ import { localizedLabel } from '@elytracms/studio/lib/i18n';
4
+ import { fieldLabel } from './cms-labels.js';
5
+ /**
6
+ * Pure companions of the document-editor field controls (EC-330, split from
7
+ * `field-control.tsx`): the repeater reorder/summary/shape decisions and the
8
+ * depth-encoding palette the object-array editors render from, plus the
9
+ * `options` segmented control. Small and dependency-light so the behavioural
10
+ * pins in `field-control.test.ts` reach them without pulling the whole
11
+ * field-control dispatcher.
12
+ */
13
+ /**
14
+ * Move `from` → `to`, returning a NEW array (all elements preserved). The repeater
15
+ * applies the same `(from, to)` to its value array AND its parallel stable-key array,
16
+ * so the two stay aligned through a reorder — a row's child/DOM state (an open
17
+ * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).
18
+ */
19
+ export function reorder(list, from, to) {
20
+ const next = list.slice();
21
+ const [moved] = next.splice(from, 1);
22
+ if (moved !== undefined)
23
+ next.splice(to, 0, moved);
24
+ return next;
25
+ }
26
+ const SUMMARY_PREFERRED_FIELDS = ['title', 'label', 'name', 'heading', 'text', 'linkType'];
27
+ /**
28
+ * A scannable one-line summary for a collapsed repeater item (EC-294): the first
29
+ * meaningful scalar subfield value — preferring title-ish field names — else `#n`.
30
+ * Pure (no store): summarises from the item's own scalar values, so it works for
31
+ * any object shape without resolving relations/assets.
32
+ */
33
+ export function objectItemSummary(fields, item, index) {
34
+ const scalar = (name) => {
35
+ const v = item[name];
36
+ if (typeof v === 'string' && v.trim())
37
+ return v.trim();
38
+ if (typeof v === 'number' || typeof v === 'boolean')
39
+ return String(v);
40
+ return null;
41
+ };
42
+ const names = fields.map((f) => f.name);
43
+ for (const name of SUMMARY_PREFERRED_FIELDS) {
44
+ if (names.includes(name)) {
45
+ const s = scalar(name);
46
+ if (s)
47
+ return truncateSummary(s);
48
+ }
49
+ }
50
+ for (const name of names) {
51
+ const s = scalar(name);
52
+ if (s)
53
+ return truncateSummary(s);
54
+ }
55
+ return `#${index + 1}`;
56
+ }
57
+ function truncateSummary(s, max = 60) {
58
+ return s.length > max ? `${s.slice(0, max - 1)}…` : s;
59
+ }
60
+ // (B) Depth-keyed visuals: a coloured left rail + a matching faint tint, DISTINCT
61
+ // per level so nesting reads at a glance. The outermost array (depth 0) starts at
62
+ // the brand primary — no more neutral, un-encoded first level — then a fixed
63
+ // high-contrast rotation whose adjacent hues never collide (primary→amber→green→red;
64
+ // note `info` is a near-primary blue, so it is deliberately NOT adjacent to primary).
65
+ // It cycles (mod) so pathological depth still renders. Literal classes so Tailwind emits them.
66
+ const DEPTH_RAIL = ['border-l-primary', 'border-l-warning', 'border-l-success', 'border-l-destructive'];
67
+ const DEPTH_TINT = ['bg-primary/5', 'bg-warning/5', 'bg-success/5', 'bg-destructive/5'];
68
+ // `d` is always ≥ 0, so `d % length` is in-bounds — the assertion is sound and gives
69
+ // callers a definite `string` (cn tolerates undefined, but tests read the value).
70
+ export const depthRail = (d) => DEPTH_RAIL[d % DEPTH_RAIL.length];
71
+ export const depthTint = (d) => DEPTH_TINT[d % DEPTH_TINT.length];
72
+ // (C) An object array renders as a compact TABLE when every subfield is a scalar (a
73
+ // flat row) and there are few enough columns to read; richer/nested object types stay
74
+ // as cards with depth encoding (B). Conditional (function-hidden) subfields force the
75
+ // card path — a table has no place to show/hide a column per row.
76
+ const TABULAR_FIELD_TYPES = new Set(['text', 'number', 'boolean', 'select', 'date']);
77
+ const MAX_TABLE_COLUMNS = 4;
78
+ export function isTabularObjectType(field) {
79
+ return (field.fields.length > 0 &&
80
+ field.fields.length <= MAX_TABLE_COLUMNS &&
81
+ field.fields.every((f) => TABULAR_FIELD_TYPES.has(f.type) && typeof f.form?.hidden !== 'function'));
82
+ }
83
+ /**
84
+ * EC-295: the `options` control — a `select` shown as inline segmented buttons
85
+ * (`[[Page]] [URL] [File]`) instead of a dropdown. Engine built-in (no consumer
86
+ * registration); single-select only. Each option shows `label ?? value` and a
87
+ * `title` tooltip from the option's `description`; the field `default` + `readOnly`
88
+ * flow through the shared form. Picks one — to allow an empty/unset value use the
89
+ * default dropdown control.
90
+ */
91
+ export function SegmentedOptions({ field, value, onChange, readOnly, id, }) {
92
+ const current = typeof value === 'string' ? value : '';
93
+ return (_jsx(Row, { gap: "1", wrap: true, role: "radiogroup", id: id, "aria-label": fieldLabel(field), children: field.options.map((opt) => {
94
+ const active = current === opt.value;
95
+ return (_jsx(Button, { type: "button", variant: active ? 'default' : 'outline', size: "sm", role: "radio", "aria-checked": active, title: localizedLabel(opt.description) || undefined, disabled: readOnly, onClick: () => onChange(opt.value), children: localizedLabel(opt.label, opt.value) }, opt.value));
96
+ }) }));
97
+ }
98
+ //# sourceMappingURL=field-control-lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-control-lib.js","sourceRoot":"","sources":["../../../src/components/cms/field-control-lib.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ObjectFieldDef, SelectFieldDef } from '@elytracms/core/cms-core'\nimport { Button, Row } from '@elytracms/studio/ui'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { fieldLabel } from './cms-labels'\n\n/**\n * Pure companions of the document-editor field controls (EC-330, split from\n * `field-control.tsx`): the repeater reorder/summary/shape decisions and the\n * depth-encoding palette the object-array editors render from, plus the\n * `options` segmented control. Small and dependency-light so the behavioural\n * pins in `field-control.test.ts` reach them without pulling the whole\n * field-control dispatcher.\n */\n\n/**\n * Move `from` → `to`, returning a NEW array (all elements preserved). The repeater\n * applies the same `(from, to)` to its value array AND its parallel stable-key array,\n * so the two stay aligned through a reorder — a row's child/DOM state (an open\n * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).\n */\nexport function reorder<T>(list: readonly T[], from: number, to: number): T[] {\n const next = list.slice()\n const [moved] = next.splice(from, 1)\n if (moved !== undefined) next.splice(to, 0, moved)\n return next\n}\n\nconst SUMMARY_PREFERRED_FIELDS = ['title', 'label', 'name', 'heading', 'text', 'linkType']\n\n/**\n * A scannable one-line summary for a collapsed repeater item (EC-294): the first\n * meaningful scalar subfield value — preferring title-ish field names — else `#n`.\n * Pure (no store): summarises from the item's own scalar values, so it works for\n * any object shape without resolving relations/assets.\n */\nexport function objectItemSummary(\n fields: readonly { name: string }[],\n item: Record<string, unknown>,\n index: number,\n): string {\n const scalar = (name: string): string | null => {\n const v = item[name]\n if (typeof v === 'string' && v.trim()) return v.trim()\n if (typeof v === 'number' || typeof v === 'boolean') return String(v)\n return null\n }\n const names = fields.map((f) => f.name)\n for (const name of SUMMARY_PREFERRED_FIELDS) {\n if (names.includes(name)) {\n const s = scalar(name)\n if (s) return truncateSummary(s)\n }\n }\n for (const name of names) {\n const s = scalar(name)\n if (s) return truncateSummary(s)\n }\n return `#${index + 1}`\n}\n\nfunction truncateSummary(s: string, max = 60): string {\n return s.length > max ? `${s.slice(0, max - 1)}…` : s\n}\n\n// (B) Depth-keyed visuals: a coloured left rail + a matching faint tint, DISTINCT\n// per level so nesting reads at a glance. The outermost array (depth 0) starts at\n// the brand primary — no more neutral, un-encoded first level — then a fixed\n// high-contrast rotation whose adjacent hues never collide (primary→amber→green→red;\n// note `info` is a near-primary blue, so it is deliberately NOT adjacent to primary).\n// It cycles (mod) so pathological depth still renders. Literal classes so Tailwind emits them.\nconst DEPTH_RAIL = ['border-l-primary', 'border-l-warning', 'border-l-success', 'border-l-destructive']\nconst DEPTH_TINT = ['bg-primary/5', 'bg-warning/5', 'bg-success/5', 'bg-destructive/5']\n// `d` is always ≥ 0, so `d % length` is in-bounds — the assertion is sound and gives\n// callers a definite `string` (cn tolerates undefined, but tests read the value).\nexport const depthRail = (d: number) => DEPTH_RAIL[d % DEPTH_RAIL.length]!\nexport const depthTint = (d: number) => DEPTH_TINT[d % DEPTH_TINT.length]!\n\n// (C) An object array renders as a compact TABLE when every subfield is a scalar (a\n// flat row) and there are few enough columns to read; richer/nested object types stay\n// as cards with depth encoding (B). Conditional (function-hidden) subfields force the\n// card path — a table has no place to show/hide a column per row.\nconst TABULAR_FIELD_TYPES = new Set(['text', 'number', 'boolean', 'select', 'date'])\nconst MAX_TABLE_COLUMNS = 4\nexport function isTabularObjectType(field: ObjectFieldDef): boolean {\n return (\n field.fields.length > 0 &&\n field.fields.length <= MAX_TABLE_COLUMNS &&\n field.fields.every(\n (f) => TABULAR_FIELD_TYPES.has(f.type) && typeof f.form?.hidden !== 'function',\n )\n )\n}\n\n/**\n * EC-295: the `options` control — a `select` shown as inline segmented buttons\n * (`[[Page]] [URL] [File]`) instead of a dropdown. Engine built-in (no consumer\n * registration); single-select only. Each option shows `label ?? value` and a\n * `title` tooltip from the option's `description`; the field `default` + `readOnly`\n * flow through the shared form. Picks one — to allow an empty/unset value use the\n * default dropdown control.\n */\nexport function SegmentedOptions({\n field,\n value,\n onChange,\n readOnly,\n id,\n}: {\n field: SelectFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}) {\n const current = typeof value === 'string' ? value : ''\n return (\n <Row gap=\"1\" wrap role=\"radiogroup\" id={id} aria-label={fieldLabel(field)}>\n {field.options.map((opt) => {\n const active = current === opt.value\n return (\n <Button\n key={opt.value}\n type=\"button\"\n variant={active ? 'default' : 'outline'}\n size=\"sm\"\n role=\"radio\"\n aria-checked={active}\n title={localizedLabel(opt.description) || undefined}\n disabled={readOnly}\n onClick={() => onChange(opt.value)}\n >\n {localizedLabel(opt.label, opt.value)}\n </Button>\n )\n })}\n </Row>\n )\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { FieldDef, ObjectFieldDef, SelectFieldDef } from '@elytracms/core/cms-core';
1
+ import type { FieldDef } from '@elytracms/core/cms-core';
2
2
  interface FieldControlProps {
3
3
  field: FieldDef;
4
4
  value: unknown;
@@ -15,40 +15,12 @@ interface FieldControlProps {
15
15
  * naming a control with NO renderer and NO built-in name (a typo) shows the
16
16
  * built-in with a loud issue — never a silent fall-through; otherwise the built-in
17
17
  * renders alone.
18
+ *
19
+ * EC-330 split: the asset-field family lives in `asset-field.tsx`, the object /
20
+ * repeater editors in `object-array-field.tsx`, and their pure companions
21
+ * (reorder/summary/depth/table decisions, the `options` segmented control) in
22
+ * `field-control-lib.tsx` — this file keeps the dispatcher plus the slug and
23
+ * relation controls.
18
24
  */
19
25
  export declare function FieldControl(props: FieldControlProps): import("react").JSX.Element;
20
- /**
21
- * EC-295: the `options` control — a `select` shown as inline segmented buttons
22
- * (`[[Page]] [URL] [File]`) instead of a dropdown. Engine built-in (no consumer
23
- * registration); single-select only. Each option shows `label ?? value` and a
24
- * `title` tooltip from the option's `description`; the field `default` + `readOnly`
25
- * flow through the shared form. Picks one — to allow an empty/unset value use the
26
- * default dropdown control.
27
- */
28
- export declare function SegmentedOptions({ field, value, onChange, readOnly, id, }: {
29
- field: SelectFieldDef;
30
- value: unknown;
31
- onChange: (value: unknown) => void;
32
- readOnly: boolean;
33
- id: string;
34
- }): import("react").JSX.Element;
35
- /**
36
- * Move `from` → `to`, returning a NEW array (all elements preserved). The repeater
37
- * applies the same `(from, to)` to its value array AND its parallel stable-key array,
38
- * so the two stay aligned through a reorder — a row's child/DOM state (an open
39
- * relation/asset picker, focus) never mis-associates (EC-253 C2, EC-294).
40
- */
41
- export declare function reorder<T>(list: readonly T[], from: number, to: number): T[];
42
- /**
43
- * A scannable one-line summary for a collapsed repeater item (EC-294): the first
44
- * meaningful scalar subfield value — preferring title-ish field names — else `#n`.
45
- * Pure (no store): summarises from the item's own scalar values, so it works for
46
- * any object shape without resolving relations/assets.
47
- */
48
- export declare function objectItemSummary(fields: readonly {
49
- name: string;
50
- }[], item: Record<string, unknown>, index: number): string;
51
- export declare const depthRail: (d: number) => string;
52
- export declare const depthTint: (d: number) => string;
53
- export declare function isTabularObjectType(field: ObjectFieldDef): boolean;
54
26
  export {};