@elytracms/studio 0.0.66 → 0.0.68
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/dist/components/builder/property-inspector.d.ts +0 -21
- package/dist/components/builder/property-inspector.js +5 -22
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/cms/asset-picker-core.d.ts +52 -0
- package/dist/components/cms/asset-picker-core.js +69 -0
- package/dist/components/cms/asset-picker-core.js.map +1 -0
- package/dist/components/cms/asset-picker.d.ts +16 -4
- package/dist/components/cms/asset-picker.js +34 -50
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/detail-editor.js +45 -54
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/prop-visibility.d.ts +3 -0
- package/dist/components/cms/prop-visibility.js +31 -0
- package/dist/components/cms/prop-visibility.js.map +1 -0
- package/dist/components/cms/rich-text/component-embed.d.ts +8 -6
- package/dist/components/cms/rich-text/component-embed.js +21 -62
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/editor.d.ts +13 -3
- package/dist/components/cms/rich-text/editor.js +36 -21
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.d.ts +60 -27
- package/dist/components/cms/rich-text/embed-props.js +102 -45
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +1 -2
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +0 -1
- package/dist/lib/i18n/messages.en.js +1 -2
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/ui/expandable-surface.d.ts +35 -25
- package/dist/ui/expandable-surface.js +41 -21
- package/dist/ui/expandable-surface.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/surface-geometry.d.ts +68 -0
- package/dist/ui/surface-geometry.js +61 -0
- package/dist/ui/surface-geometry.js.map +1 -0
- package/package.json +2 -2
- package/src/styles/studio.css +30 -0
- package/dist/components/cms/rich-text/embed-inspector-context.d.ts +0 -50
- package/dist/components/cms/rich-text/embed-inspector-context.js +0 -12
- package/dist/components/cms/rich-text/embed-inspector-context.js.map +0 -1
- package/dist/components/cms/rich-text/embed-inspector.d.ts +0 -25
- package/dist/components/cms/rich-text/embed-inspector.js +0 -48
- package/dist/components/cms/rich-text/embed-inspector.js.map +0 -1
- package/dist/components/cms/use-embed-inspector-host.d.ts +0 -12
- package/dist/components/cms/use-embed-inspector-host.js +0 -17
- package/dist/components/cms/use-embed-inspector-host.js.map +0 -1
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import type { SurfaceGeometryProps } from './surface-geometry.js';
|
|
3
|
+
export type ExpandableSurfaceProps = {
|
|
4
|
+
/** Heading text; doubles as the surface's `aria-label`. */
|
|
4
5
|
title: string;
|
|
5
6
|
/** Accessible label + tooltip for the header close button (caller-translated). */
|
|
6
7
|
closeLabel: string;
|
|
7
|
-
/**
|
|
8
|
-
* CSS width of the sheet panel — callers keep their shipped widths
|
|
9
|
-
* (e.g. `min(820px, 92vw)` rich text, `min(720px, 92vw)` proposal review).
|
|
10
|
-
*/
|
|
11
|
-
width: string;
|
|
12
8
|
/**
|
|
13
9
|
* Dismiss request: Escape (only while this surface is the topmost layer),
|
|
14
10
|
* backdrop click, or the header close button. The caller owns the open state.
|
|
@@ -18,31 +14,45 @@ export interface ExpandableSurfaceProps {
|
|
|
18
14
|
headerLeading?: ReactNode;
|
|
19
15
|
/** Everything below the header. Callers own their scroll areas and footers. */
|
|
20
16
|
children: ReactNode;
|
|
21
|
-
}
|
|
17
|
+
} & SurfaceGeometryProps;
|
|
22
18
|
/**
|
|
23
|
-
* ExpandableSurface (EC-392) — the tier-2 answer to
|
|
24
|
-
* its slot": the SAME surface grown into a roomy
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
19
|
+
* ExpandableSurface (EC-392, geometry variants EC-431) — the tier-2 answer to
|
|
20
|
+
* "an inline surface outgrows its slot": the SAME surface grown into a roomy
|
|
21
|
+
* takeover. It is never a second form — no save/cancel semantics of its own
|
|
22
|
+
* (forms-never-modal) — and stays lightly dismissible: Escape, backdrop click,
|
|
23
|
+
* header close.
|
|
24
|
+
*
|
|
25
|
+
* Two geometries, one mechanic set (ratified AD-A, 2026-07-16):
|
|
26
|
+
*
|
|
27
|
+
* - `variant="stage"` — CENTERED, near-full height, width from a per-surface
|
|
28
|
+
* preset (`prose` rich-text expand EC-431 / `grid` media browser EC-432 /
|
|
29
|
+
* `form` embed inspector EC-430). Primary-activity surfaces live here; the
|
|
30
|
+
* entrance is a subtle motion-safe scale/opacity grow from center, never an
|
|
31
|
+
* edge slide.
|
|
32
|
+
* - `variant="side"` (default) — the right-anchored sheet, reserved for
|
|
33
|
+
* genuinely peripheral review next to a primary activity; today exactly one
|
|
34
|
+
* caller: the AI proposal review card. Shipped shape unchanged, no motion.
|
|
29
35
|
*
|
|
30
36
|
* Rendered through a `createPortal` to `document.body` so it escapes narrow
|
|
31
37
|
* inspectors/sidebars and any transformed ancestor; without a DOM (the
|
|
32
38
|
* `renderToStaticMarkup` smoke path) it renders `null`, so callers keep their
|
|
33
|
-
*
|
|
39
|
+
* surfaces closed by default on SSR paths.
|
|
34
40
|
*
|
|
35
41
|
* Stacking (ratified semantics): all instances register on the shared layer
|
|
36
42
|
* stack; only the TOPMOST layer acts on Escape (backdrop ownership falls out of
|
|
37
|
-
* paint order — the topmost
|
|
38
|
-
* above (the immediate case: the asset
|
|
39
|
-
* expanded rich-text toolbar) are bridged
|
|
40
|
-
* Escape on the document capture phase and
|
|
41
|
-
* this surface treats as "handled above me" —
|
|
42
|
-
* and the
|
|
43
|
+
* paint order — the topmost full-viewport container covers everything below it,
|
|
44
|
+
* for both geometries). Foreign layers above (the immediate case: the asset
|
|
45
|
+
* picker's Radix dialog opened from the expanded rich-text toolbar) are bridged
|
|
46
|
+
* via the event itself: Radix consumes Escape on the document capture phase and
|
|
47
|
+
* marks it `defaultPrevented`, which this surface treats as "handled above me" —
|
|
48
|
+
* so Escape closes the picker first and the surface only on the next press. A
|
|
49
|
+
* second ExpandableSurface above a stage rides the same stack (later mount →
|
|
50
|
+
* later in DOM → paints above; `isTop` hands it Escape) — the EC-432
|
|
51
|
+
* stage-above-stage case needs no new mechanics.
|
|
43
52
|
*
|
|
44
|
-
* Motion: the
|
|
45
|
-
*
|
|
46
|
-
*
|
|
53
|
+
* Motion: only the stage variant has an entrance transition, and it is entirely
|
|
54
|
+
* `motion-safe:`-gated (see `surface-geometry.ts`) — under
|
|
55
|
+
* `prefers-reduced-motion` the stage appears instantly. The side sheet keeps
|
|
56
|
+
* its shipped no-transition behavior.
|
|
47
57
|
*/
|
|
48
|
-
export declare function ExpandableSurface(
|
|
58
|
+
export declare function ExpandableSurface(props: ExpandableSurfaceProps): import("react").ReactPortal | null;
|
|
@@ -6,41 +6,61 @@ import { Button } from './button.js';
|
|
|
6
6
|
import { Heading } from './typography.js';
|
|
7
7
|
import { Row, Spacer } from './layout.js';
|
|
8
8
|
import { createLayerStack } from './layer-stack.js';
|
|
9
|
+
import { surfaceGeometry } from './surface-geometry.js';
|
|
9
10
|
/**
|
|
10
11
|
* The ONE shared open-layer stack all {@link ExpandableSurface} instances
|
|
11
12
|
* coordinate through. The surfaces themselves are instance-per-caller (ratified:
|
|
12
|
-
* no singleton/global
|
|
13
|
-
* legal); only the *ordering* is shared, so that the topmost open
|
|
14
|
-
* Escape while the ones below stay open.
|
|
13
|
+
* no singleton/global surface slot — concurrent surfaces from different features
|
|
14
|
+
* are legal); only the *ordering* is shared, so that the topmost open surface
|
|
15
|
+
* owns Escape while the ones below stay open.
|
|
15
16
|
*/
|
|
16
17
|
const surfaceLayers = createLayerStack();
|
|
17
18
|
/**
|
|
18
|
-
* ExpandableSurface (EC-392) — the tier-2 answer to
|
|
19
|
-
* its slot": the SAME surface grown into a roomy
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* ExpandableSurface (EC-392, geometry variants EC-431) — the tier-2 answer to
|
|
20
|
+
* "an inline surface outgrows its slot": the SAME surface grown into a roomy
|
|
21
|
+
* takeover. It is never a second form — no save/cancel semantics of its own
|
|
22
|
+
* (forms-never-modal) — and stays lightly dismissible: Escape, backdrop click,
|
|
23
|
+
* header close.
|
|
24
|
+
*
|
|
25
|
+
* Two geometries, one mechanic set (ratified AD-A, 2026-07-16):
|
|
26
|
+
*
|
|
27
|
+
* - `variant="stage"` — CENTERED, near-full height, width from a per-surface
|
|
28
|
+
* preset (`prose` rich-text expand EC-431 / `grid` media browser EC-432 /
|
|
29
|
+
* `form` embed inspector EC-430). Primary-activity surfaces live here; the
|
|
30
|
+
* entrance is a subtle motion-safe scale/opacity grow from center, never an
|
|
31
|
+
* edge slide.
|
|
32
|
+
* - `variant="side"` (default) — the right-anchored sheet, reserved for
|
|
33
|
+
* genuinely peripheral review next to a primary activity; today exactly one
|
|
34
|
+
* caller: the AI proposal review card. Shipped shape unchanged, no motion.
|
|
24
35
|
*
|
|
25
36
|
* Rendered through a `createPortal` to `document.body` so it escapes narrow
|
|
26
37
|
* inspectors/sidebars and any transformed ancestor; without a DOM (the
|
|
27
38
|
* `renderToStaticMarkup` smoke path) it renders `null`, so callers keep their
|
|
28
|
-
*
|
|
39
|
+
* surfaces closed by default on SSR paths.
|
|
29
40
|
*
|
|
30
41
|
* Stacking (ratified semantics): all instances register on the shared layer
|
|
31
42
|
* stack; only the TOPMOST layer acts on Escape (backdrop ownership falls out of
|
|
32
|
-
* paint order — the topmost
|
|
33
|
-
* above (the immediate case: the asset
|
|
34
|
-
* expanded rich-text toolbar) are bridged
|
|
35
|
-
* Escape on the document capture phase and
|
|
36
|
-
* this surface treats as "handled above me" —
|
|
37
|
-
* and the
|
|
43
|
+
* paint order — the topmost full-viewport container covers everything below it,
|
|
44
|
+
* for both geometries). Foreign layers above (the immediate case: the asset
|
|
45
|
+
* picker's Radix dialog opened from the expanded rich-text toolbar) are bridged
|
|
46
|
+
* via the event itself: Radix consumes Escape on the document capture phase and
|
|
47
|
+
* marks it `defaultPrevented`, which this surface treats as "handled above me" —
|
|
48
|
+
* so Escape closes the picker first and the surface only on the next press. A
|
|
49
|
+
* second ExpandableSurface above a stage rides the same stack (later mount →
|
|
50
|
+
* later in DOM → paints above; `isTop` hands it Escape) — the EC-432
|
|
51
|
+
* stage-above-stage case needs no new mechanics.
|
|
38
52
|
*
|
|
39
|
-
* Motion: the
|
|
40
|
-
*
|
|
41
|
-
*
|
|
53
|
+
* Motion: only the stage variant has an entrance transition, and it is entirely
|
|
54
|
+
* `motion-safe:`-gated (see `surface-geometry.ts`) — under
|
|
55
|
+
* `prefers-reduced-motion` the stage appears instantly. The side sheet keeps
|
|
56
|
+
* its shipped no-transition behavior.
|
|
42
57
|
*/
|
|
43
|
-
export function ExpandableSurface(
|
|
58
|
+
export function ExpandableSurface(props) {
|
|
59
|
+
const { title, closeLabel, onDismiss, headerLeading, children } = props;
|
|
60
|
+
const geometry = surfaceGeometry(props);
|
|
61
|
+
// The stage IS the primary activity while open — a <section>; the side sheet
|
|
62
|
+
// is complementary content next to it — an <aside>.
|
|
63
|
+
const Panel = props.variant === 'stage' ? 'section' : 'aside';
|
|
44
64
|
// Latest dismiss handler, read inside the once-per-mount listener.
|
|
45
65
|
const onDismissRef = useRef(onDismiss);
|
|
46
66
|
onDismissRef.current = onDismiss;
|
|
@@ -67,6 +87,6 @@ export function ExpandableSurface({ title, closeLabel, width, onDismiss, headerL
|
|
|
67
87
|
}, []);
|
|
68
88
|
if (typeof document === 'undefined')
|
|
69
89
|
return null;
|
|
70
|
-
return createPortal(_jsxs("div", { className:
|
|
90
|
+
return createPortal(_jsxs("div", { className: geometry.container, children: [_jsx("div", { className: geometry.backdrop, "aria-hidden": true, onClick: onDismiss }), _jsxs(Panel, { role: "dialog", "aria-label": title, style: geometry.panelStyle, className: geometry.panel, children: [_jsxs(Row, { gap: "2", align: "center", className: "h-12 shrink-0 border-b border-border px-4", children: [headerLeading, _jsx(Heading, { level: 2, size: "h4", className: "min-w-0 truncate", children: title }), _jsx(Spacer, {}), _jsx(Button, { size: "icon-sm", variant: "ghost", onClick: onDismiss, title: closeLabel, "aria-label": closeLabel, children: _jsx(X, {}) })] }), children] })] }), document.body);
|
|
71
91
|
}
|
|
72
92
|
//# sourceMappingURL=expandable-surface.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expandable-surface.js","sourceRoot":"","sources":["../../src/ui/expandable-surface.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"expandable-surface.js","sourceRoot":"","sources":["../../src/ui/expandable-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { X } from 'lucide-react'\nimport { Button } from './button'\nimport { Heading } from './typography'\nimport { Row, Spacer } from './layout'\nimport { createLayerStack } from './layer-stack'\nimport { surfaceGeometry } from './surface-geometry'\nimport type { SurfaceGeometryProps } from './surface-geometry'\n\n/**\n * The ONE shared open-layer stack all {@link ExpandableSurface} instances\n * coordinate through. The surfaces themselves are instance-per-caller (ratified:\n * no singleton/global surface slot — concurrent surfaces from different features\n * are legal); only the *ordering* is shared, so that the topmost open surface\n * owns Escape while the ones below stay open.\n */\nconst surfaceLayers = createLayerStack()\n\nexport type ExpandableSurfaceProps = {\n /** Heading text; doubles as the surface's `aria-label`. */\n title: string\n /** Accessible label + tooltip for the header close button (caller-translated). */\n closeLabel: string\n /**\n * Dismiss request: Escape (only while this surface is the topmost layer),\n * backdrop click, or the header close button. The caller owns the open state.\n */\n onDismiss: () => void\n /** Optional leading header adornment (an icon) rendered before the title. */\n headerLeading?: ReactNode\n /** Everything below the header. Callers own their scroll areas and footers. */\n children: ReactNode\n} & SurfaceGeometryProps\n\n/**\n * ExpandableSurface (EC-392, geometry variants EC-431) — the tier-2 answer to\n * \"an inline surface outgrows its slot\": the SAME surface grown into a roomy\n * takeover. It is never a second form — no save/cancel semantics of its own\n * (forms-never-modal) — and stays lightly dismissible: Escape, backdrop click,\n * header close.\n *\n * Two geometries, one mechanic set (ratified AD-A, 2026-07-16):\n *\n * - `variant=\"stage\"` — CENTERED, near-full height, width from a per-surface\n * preset (`prose` rich-text expand EC-431 / `grid` media browser EC-432 /\n * `form` embed inspector EC-430). Primary-activity surfaces live here; the\n * entrance is a subtle motion-safe scale/opacity grow from center, never an\n * edge slide.\n * - `variant=\"side\"` (default) — the right-anchored sheet, reserved for\n * genuinely peripheral review next to a primary activity; today exactly one\n * caller: the AI proposal review card. Shipped shape unchanged, no motion.\n *\n * Rendered through a `createPortal` to `document.body` so it escapes narrow\n * inspectors/sidebars and any transformed ancestor; without a DOM (the\n * `renderToStaticMarkup` smoke path) it renders `null`, so callers keep their\n * surfaces closed by default on SSR paths.\n *\n * Stacking (ratified semantics): all instances register on the shared layer\n * stack; only the TOPMOST layer acts on Escape (backdrop ownership falls out of\n * paint order — the topmost full-viewport container covers everything below it,\n * for both geometries). Foreign layers above (the immediate case: the asset\n * picker's Radix dialog opened from the expanded rich-text toolbar) are bridged\n * via the event itself: Radix consumes Escape on the document capture phase and\n * marks it `defaultPrevented`, which this surface treats as \"handled above me\" —\n * so Escape closes the picker first and the surface only on the next press. A\n * second ExpandableSurface above a stage rides the same stack (later mount →\n * later in DOM → paints above; `isTop` hands it Escape) — the EC-432\n * stage-above-stage case needs no new mechanics.\n *\n * Motion: only the stage variant has an entrance transition, and it is entirely\n * `motion-safe:`-gated (see `surface-geometry.ts`) — under\n * `prefers-reduced-motion` the stage appears instantly. The side sheet keeps\n * its shipped no-transition behavior.\n */\nexport function ExpandableSurface(props: ExpandableSurfaceProps) {\n const { title, closeLabel, onDismiss, headerLeading, children } = props\n const geometry = surfaceGeometry(props)\n // The stage IS the primary activity while open — a <section>; the side sheet\n // is complementary content next to it — an <aside>.\n const Panel = props.variant === 'stage' ? 'section' : 'aside'\n\n // Latest dismiss handler, read inside the once-per-mount listener.\n const onDismissRef = useRef(onDismiss)\n onDismissRef.current = onDismiss\n\n useEffect(() => {\n const layer = surfaceLayers.push()\n const onKey = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n // A layer above already consumed this Escape — a Radix dialog (asset\n // picker) preventDefaults on dismiss, and a sibling surface that handled\n // it marks it the same way. Layers below stay open.\n if (event.defaultPrevented) return\n if (!surfaceLayers.isTop(layer)) return\n event.preventDefault()\n onDismissRef.current()\n }\n window.addEventListener('keydown', onKey)\n return () => {\n window.removeEventListener('keydown', onKey)\n surfaceLayers.remove(layer)\n }\n }, [])\n\n if (typeof document === 'undefined') return null\n\n return createPortal(\n <div className={geometry.container}>\n <div className={geometry.backdrop} aria-hidden onClick={onDismiss} />\n <Panel\n role=\"dialog\"\n aria-label={title}\n style={geometry.panelStyle}\n className={geometry.panel}\n >\n <Row gap=\"2\" align=\"center\" className=\"h-12 shrink-0 border-b border-border px-4\">\n {headerLeading}\n <Heading level={2} size=\"h4\" className=\"min-w-0 truncate\">\n {title}\n </Heading>\n <Spacer />\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={onDismiss}\n title={closeLabel}\n aria-label={closeLabel}\n >\n <X />\n </Button>\n </Row>\n {children}\n </Panel>\n </div>,\n document.body,\n )\n}\n"]}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './separator.js';
|
|
|
12
12
|
export * from './toolbar.js';
|
|
13
13
|
export * from './inspector.js';
|
|
14
14
|
export * from './expandable-surface.js';
|
|
15
|
+
export * from './surface-geometry.js';
|
|
15
16
|
export * from './layer-stack.js';
|
|
16
17
|
export * from './dialog.js';
|
|
17
18
|
export * from './dropdown-menu.js';
|
package/dist/ui/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './separator.js';
|
|
|
12
12
|
export * from './toolbar.js';
|
|
13
13
|
export * from './inspector.js';
|
|
14
14
|
export * from './expandable-surface.js';
|
|
15
|
+
export * from './surface-geometry.js';
|
|
15
16
|
export * from './layer-stack.js';
|
|
16
17
|
export * from './dialog.js';
|
|
17
18
|
export * from './dropdown-menu.js';
|
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAC1B,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,qBAAqB,CAAA","sourcesContent":["export * from './typography'\nexport * from './layout'\nexport * from './avatar'\nexport * from './button'\nexport * from './input'\nexport * from './badge'\nexport * from './card'\nexport * from './panel'\nexport * from './row-item'\nexport * from './collapsible-section'\nexport * from './separator'\nexport * from './toolbar'\nexport * from './inspector'\nexport * from './expandable-surface'\nexport * from './layer-stack'\nexport * from './dialog'\nexport * from './dropdown-menu'\nexport * from './popover'\nexport * from './field'\nexport * from './toast'\nexport * from './segmented-control'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAC1B,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,qBAAqB,CAAA","sourcesContent":["export * from './typography'\nexport * from './layout'\nexport * from './avatar'\nexport * from './button'\nexport * from './input'\nexport * from './badge'\nexport * from './card'\nexport * from './panel'\nexport * from './row-item'\nexport * from './collapsible-section'\nexport * from './separator'\nexport * from './toolbar'\nexport * from './inspector'\nexport * from './expandable-surface'\nexport * from './surface-geometry'\nexport * from './layer-stack'\nexport * from './dialog'\nexport * from './dropdown-menu'\nexport * from './popover'\nexport * from './field'\nexport * from './toast'\nexport * from './segmented-control'\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* EC-431 (AD-A) — the pure geometry core for {@link ExpandableSurface}: which
|
|
4
|
+
* classes/width a surface mounts with, per variant. Extracted (the EC-391
|
|
5
|
+
* class-contract pattern) so the geometry is pinnable in the node test env,
|
|
6
|
+
* where the portal itself renders `null`.
|
|
7
|
+
*
|
|
8
|
+
* Two geometries, one mechanic set (portal-to-body, backdrop, header close,
|
|
9
|
+
* layer-stacked Escape):
|
|
10
|
+
*
|
|
11
|
+
* - `side` — the right-anchored sheet. Reserved for genuinely PERIPHERAL review
|
|
12
|
+
* surfaces (a side panel semantically means "detail next to the primary
|
|
13
|
+
* activity"); today exactly one: the AI proposal review card. Byte-for-byte
|
|
14
|
+
* the EC-392 shipped shape — no visual change for that caller.
|
|
15
|
+
* - `stage` — the centered takeover for PRIMARY-ACTIVITY surfaces (ratified
|
|
16
|
+
* AD-A, 2026-07-16): centered, near-full height, growing from center with a
|
|
17
|
+
* subtle scale/opacity entrance — never an edge slide. Width comes from a
|
|
18
|
+
* per-surface preset ({@link STAGE_WIDTHS}).
|
|
19
|
+
*
|
|
20
|
+
* Motion: only the stage has an entrance transition, and every animated class
|
|
21
|
+
* is `motion-safe:`-gated — under `prefers-reduced-motion` the stage appears
|
|
22
|
+
* instantly (pinned by `surface-geometry.test.ts`). The side sheet keeps its
|
|
23
|
+
* shipped no-transition behavior.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The stage width presets — one per surface family (ratified AD-A). Consumers
|
|
27
|
+
* pick a preset, never a raw width, so stage surfaces stay mutually consistent:
|
|
28
|
+
*
|
|
29
|
+
* - `prose` — distraction-free writing (the rich-text expand, EC-431/AD-B). The
|
|
30
|
+
* ratified `min(920px, 92vw)`; the CONTENT column inside is constrained
|
|
31
|
+
* further (~70ch) by the caller — the stage being wider than the text is the
|
|
32
|
+
* point, not a defect.
|
|
33
|
+
* - `grid` — large centered browsing grids (the EC-432 media browser).
|
|
34
|
+
* - `form` — a single-column form measure (the EC-430 embed inspector).
|
|
35
|
+
*/
|
|
36
|
+
export declare const STAGE_WIDTHS: {
|
|
37
|
+
readonly prose: "min(920px, 92vw)";
|
|
38
|
+
readonly grid: "min(1240px, 94vw)";
|
|
39
|
+
readonly form: "min(720px, 92vw)";
|
|
40
|
+
};
|
|
41
|
+
export type StageWidthPreset = keyof typeof STAGE_WIDTHS;
|
|
42
|
+
/**
|
|
43
|
+
* The geometry-discriminating props ({@link ExpandableSurface} spreads these
|
|
44
|
+
* into its own): `side` callers keep passing their shipped CSS width; `stage`
|
|
45
|
+
* callers pick a preset.
|
|
46
|
+
*/
|
|
47
|
+
export type SurfaceGeometryProps = {
|
|
48
|
+
/** The right-anchored sheet (default) — peripheral review surfaces only. */
|
|
49
|
+
variant?: 'side';
|
|
50
|
+
/** CSS width of the sheet panel (callers keep their shipped widths). */
|
|
51
|
+
width: string;
|
|
52
|
+
} | {
|
|
53
|
+
/** The centered stage — primary-activity surfaces (ratified AD-A). */
|
|
54
|
+
variant: 'stage';
|
|
55
|
+
/** Per-surface stage width preset. */
|
|
56
|
+
preset: StageWidthPreset;
|
|
57
|
+
};
|
|
58
|
+
export interface SurfaceGeometry {
|
|
59
|
+
/** The fixed full-viewport container. */
|
|
60
|
+
container: string;
|
|
61
|
+
/** The dismiss backdrop (paint order keeps it under the panel). */
|
|
62
|
+
backdrop: string;
|
|
63
|
+
/** The `role="dialog"` panel. */
|
|
64
|
+
panel: string;
|
|
65
|
+
/** Inline style carrying the resolved panel width (deterministic, no class scanning). */
|
|
66
|
+
panelStyle: CSSProperties;
|
|
67
|
+
}
|
|
68
|
+
export declare function surfaceGeometry(props: SurfaceGeometryProps): SurfaceGeometry;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EC-431 (AD-A) — the pure geometry core for {@link ExpandableSurface}: which
|
|
3
|
+
* classes/width a surface mounts with, per variant. Extracted (the EC-391
|
|
4
|
+
* class-contract pattern) so the geometry is pinnable in the node test env,
|
|
5
|
+
* where the portal itself renders `null`.
|
|
6
|
+
*
|
|
7
|
+
* Two geometries, one mechanic set (portal-to-body, backdrop, header close,
|
|
8
|
+
* layer-stacked Escape):
|
|
9
|
+
*
|
|
10
|
+
* - `side` — the right-anchored sheet. Reserved for genuinely PERIPHERAL review
|
|
11
|
+
* surfaces (a side panel semantically means "detail next to the primary
|
|
12
|
+
* activity"); today exactly one: the AI proposal review card. Byte-for-byte
|
|
13
|
+
* the EC-392 shipped shape — no visual change for that caller.
|
|
14
|
+
* - `stage` — the centered takeover for PRIMARY-ACTIVITY surfaces (ratified
|
|
15
|
+
* AD-A, 2026-07-16): centered, near-full height, growing from center with a
|
|
16
|
+
* subtle scale/opacity entrance — never an edge slide. Width comes from a
|
|
17
|
+
* per-surface preset ({@link STAGE_WIDTHS}).
|
|
18
|
+
*
|
|
19
|
+
* Motion: only the stage has an entrance transition, and every animated class
|
|
20
|
+
* is `motion-safe:`-gated — under `prefers-reduced-motion` the stage appears
|
|
21
|
+
* instantly (pinned by `surface-geometry.test.ts`). The side sheet keeps its
|
|
22
|
+
* shipped no-transition behavior.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The stage width presets — one per surface family (ratified AD-A). Consumers
|
|
26
|
+
* pick a preset, never a raw width, so stage surfaces stay mutually consistent:
|
|
27
|
+
*
|
|
28
|
+
* - `prose` — distraction-free writing (the rich-text expand, EC-431/AD-B). The
|
|
29
|
+
* ratified `min(920px, 92vw)`; the CONTENT column inside is constrained
|
|
30
|
+
* further (~70ch) by the caller — the stage being wider than the text is the
|
|
31
|
+
* point, not a defect.
|
|
32
|
+
* - `grid` — large centered browsing grids (the EC-432 media browser).
|
|
33
|
+
* - `form` — a single-column form measure (the EC-430 embed inspector).
|
|
34
|
+
*/
|
|
35
|
+
export const STAGE_WIDTHS = {
|
|
36
|
+
prose: 'min(920px, 92vw)',
|
|
37
|
+
grid: 'min(1240px, 94vw)',
|
|
38
|
+
form: 'min(720px, 92vw)',
|
|
39
|
+
};
|
|
40
|
+
export function surfaceGeometry(props) {
|
|
41
|
+
if (props.variant === 'stage') {
|
|
42
|
+
return {
|
|
43
|
+
// Near-full height falls out of the container padding: the panel is
|
|
44
|
+
// `h-full` inside the padded fixed container, so it always keeps a
|
|
45
|
+
// breathing gutter to every viewport edge.
|
|
46
|
+
container: 'fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6',
|
|
47
|
+
backdrop: 'absolute inset-0 bg-background/70 backdrop-blur-sm motion-safe:animate-ec-stage-fade',
|
|
48
|
+
panel: 'relative flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl motion-safe:animate-ec-stage-in',
|
|
49
|
+
panelStyle: { width: STAGE_WIDTHS[props.preset] },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// The EC-392 shipped sheet shape, byte-for-byte — the proposal card must not
|
|
53
|
+
// change visually (ratified: side survives for peripheral review).
|
|
54
|
+
return {
|
|
55
|
+
container: 'fixed inset-0 z-50 flex',
|
|
56
|
+
backdrop: 'flex-1 bg-background/70 backdrop-blur-sm',
|
|
57
|
+
panel: 'flex h-full flex-col border-l border-border bg-card shadow-2xl',
|
|
58
|
+
panelStyle: { width: props.width },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=surface-geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-geometry.js","sourceRoot":"","sources":["../../src/ui/surface-geometry.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,kBAAkB;IACzB,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,kBAAkB;CAChB,CAAA;AAkCV,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QAC9B,OAAO;YACL,oEAAoE;YACpE,mEAAmE;YACnE,2CAA2C;YAC3C,SAAS,EAAE,gEAAgE;YAC3E,QAAQ,EACN,sFAAsF;YACxF,KAAK,EACH,kIAAkI;YACpI,UAAU,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;SAClD,CAAA;IACH,CAAC;IACD,6EAA6E;IAC7E,mEAAmE;IACnE,OAAO;QACL,SAAS,EAAE,yBAAyB;QACpC,QAAQ,EAAE,0CAA0C;QACpD,KAAK,EAAE,gEAAgE;QACvE,UAAU,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;KACnC,CAAA;AACH,CAAC","sourcesContent":["import type { CSSProperties } from 'react'\n\n/**\n * EC-431 (AD-A) — the pure geometry core for {@link ExpandableSurface}: which\n * classes/width a surface mounts with, per variant. Extracted (the EC-391\n * class-contract pattern) so the geometry is pinnable in the node test env,\n * where the portal itself renders `null`.\n *\n * Two geometries, one mechanic set (portal-to-body, backdrop, header close,\n * layer-stacked Escape):\n *\n * - `side` — the right-anchored sheet. Reserved for genuinely PERIPHERAL review\n * surfaces (a side panel semantically means \"detail next to the primary\n * activity\"); today exactly one: the AI proposal review card. Byte-for-byte\n * the EC-392 shipped shape — no visual change for that caller.\n * - `stage` — the centered takeover for PRIMARY-ACTIVITY surfaces (ratified\n * AD-A, 2026-07-16): centered, near-full height, growing from center with a\n * subtle scale/opacity entrance — never an edge slide. Width comes from a\n * per-surface preset ({@link STAGE_WIDTHS}).\n *\n * Motion: only the stage has an entrance transition, and every animated class\n * is `motion-safe:`-gated — under `prefers-reduced-motion` the stage appears\n * instantly (pinned by `surface-geometry.test.ts`). The side sheet keeps its\n * shipped no-transition behavior.\n */\n\n/**\n * The stage width presets — one per surface family (ratified AD-A). Consumers\n * pick a preset, never a raw width, so stage surfaces stay mutually consistent:\n *\n * - `prose` — distraction-free writing (the rich-text expand, EC-431/AD-B). The\n * ratified `min(920px, 92vw)`; the CONTENT column inside is constrained\n * further (~70ch) by the caller — the stage being wider than the text is the\n * point, not a defect.\n * - `grid` — large centered browsing grids (the EC-432 media browser).\n * - `form` — a single-column form measure (the EC-430 embed inspector).\n */\nexport const STAGE_WIDTHS = {\n prose: 'min(920px, 92vw)',\n grid: 'min(1240px, 94vw)',\n form: 'min(720px, 92vw)',\n} as const\n\nexport type StageWidthPreset = keyof typeof STAGE_WIDTHS\n\n/**\n * The geometry-discriminating props ({@link ExpandableSurface} spreads these\n * into its own): `side` callers keep passing their shipped CSS width; `stage`\n * callers pick a preset.\n */\nexport type SurfaceGeometryProps =\n | {\n /** The right-anchored sheet (default) — peripheral review surfaces only. */\n variant?: 'side'\n /** CSS width of the sheet panel (callers keep their shipped widths). */\n width: string\n }\n | {\n /** The centered stage — primary-activity surfaces (ratified AD-A). */\n variant: 'stage'\n /** Per-surface stage width preset. */\n preset: StageWidthPreset\n }\n\nexport interface SurfaceGeometry {\n /** The fixed full-viewport container. */\n container: string\n /** The dismiss backdrop (paint order keeps it under the panel). */\n backdrop: string\n /** The `role=\"dialog\"` panel. */\n panel: string\n /** Inline style carrying the resolved panel width (deterministic, no class scanning). */\n panelStyle: CSSProperties\n}\n\nexport function surfaceGeometry(props: SurfaceGeometryProps): SurfaceGeometry {\n if (props.variant === 'stage') {\n return {\n // Near-full height falls out of the container padding: the panel is\n // `h-full` inside the padded fixed container, so it always keeps a\n // breathing gutter to every viewport edge.\n container: 'fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6',\n backdrop:\n 'absolute inset-0 bg-background/70 backdrop-blur-sm motion-safe:animate-ec-stage-fade',\n panel:\n 'relative flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl motion-safe:animate-ec-stage-in',\n panelStyle: { width: STAGE_WIDTHS[props.preset] },\n }\n }\n // The EC-392 shipped sheet shape, byte-for-byte — the proposal card must not\n // change visually (ratified: side survives for peripheral review).\n return {\n container: 'fixed inset-0 z-50 flex',\n backdrop: 'flex-1 bg-background/70 backdrop-blur-sm',\n panel: 'flex h-full flex-col border-l border-border bg-card shadow-2xl',\n panelStyle: { width: props.width },\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elytracms/studio",
|
|
3
3
|
"author": "finaldream <hi@finaldream.de>",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.68",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "The Elytra studio engine (UI + lib + routes + data-only Convex schema) as an importable package. Extraction staged in EXTRACTION.md (EC-223); Stages 1-2 = UI primitives + router-free lib leaves.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"lucide-react": "^1.23.0",
|
|
147
147
|
"tailwind-merge": "^3.6.0",
|
|
148
148
|
"zod": "^4.4.3",
|
|
149
|
-
"@elytracms/core": "^0.0.
|
|
149
|
+
"@elytracms/core": "^0.0.68"
|
|
150
150
|
},
|
|
151
151
|
"peerDependencies": {
|
|
152
152
|
"@tanstack/react-router": "^1.170.0",
|
package/src/styles/studio.css
CHANGED
|
@@ -65,6 +65,36 @@
|
|
|
65
65
|
--radius-xl: calc(var(--radius) + 4px);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/*
|
|
69
|
+
* EC-431 — the centered stage surface (ExpandableSurface variant="stage") grows
|
|
70
|
+
* from center with a subtle scale/opacity entrance; the backdrop fades in. Both
|
|
71
|
+
* are consumed ONLY through `motion-safe:` variants, so `prefers-reduced-motion`
|
|
72
|
+
* gets an instant appearance (no transition at all).
|
|
73
|
+
*/
|
|
74
|
+
@theme {
|
|
75
|
+
--animate-ec-stage-in: ec-stage-in 180ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
76
|
+
--animate-ec-stage-fade: ec-stage-fade 180ms ease-out;
|
|
77
|
+
|
|
78
|
+
@keyframes ec-stage-in {
|
|
79
|
+
from {
|
|
80
|
+
opacity: 0;
|
|
81
|
+
transform: scale(0.97);
|
|
82
|
+
}
|
|
83
|
+
to {
|
|
84
|
+
opacity: 1;
|
|
85
|
+
transform: scale(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
@keyframes ec-stage-fade {
|
|
89
|
+
from {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
}
|
|
92
|
+
to {
|
|
93
|
+
opacity: 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
68
98
|
@layer base {
|
|
69
99
|
* {
|
|
70
100
|
border-color: var(--color-border);
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { ComponentNode } from '@elytracms/core/project-graph';
|
|
2
|
-
import type { ComponentManifest } from '@elytracms/core/component-registry';
|
|
3
|
-
/**
|
|
4
|
-
* Selection-driven embed inspector plumbing (0.0.41) — a deliberately **TipTap-free**
|
|
5
|
-
* seam between the rich-text editor and the CMS detail editor's right sidebar.
|
|
6
|
-
*
|
|
7
|
-
* When a `componentEmbed` node is selected inside a rich-text field, the field pushes
|
|
8
|
-
* an {@link EmbedInspectorPayload} through this context; the detail editor swaps its
|
|
9
|
-
* inspector sidebar for the embed's prop form (the same EC-190 controls the old modal
|
|
10
|
-
* used). Deselecting clears it and the normal sidebar returns.
|
|
11
|
-
*
|
|
12
|
-
* It carries only plain data + callbacks (no `@tiptap/*` imports) so the detail
|
|
13
|
-
* editor — which sits on the critical path and must NOT statically pull TipTap
|
|
14
|
-
* (EC-322) — can consume it while the editor subtree stays behind `rich-text/lazy`.
|
|
15
|
-
*/
|
|
16
|
-
export interface EmbedInspectorPayload {
|
|
17
|
-
/**
|
|
18
|
-
* Stable identity of the embedded node (its `ComponentNode.id`). Dedupes selection
|
|
19
|
-
* races: when the selection moves from embed A to embed B, B's "open" and A's
|
|
20
|
-
* "close" can interleave — clearing keys on this id so A never wipes B.
|
|
21
|
-
*/
|
|
22
|
-
nodeId: string;
|
|
23
|
-
/** The embedded component node whose props the inspector edits. */
|
|
24
|
-
node: ComponentNode;
|
|
25
|
-
/** Its manifest — drives which prop fields render. */
|
|
26
|
-
manifest: ComponentManifest;
|
|
27
|
-
/** Render the inspector read-only (mirrors the field's `readOnly`). */
|
|
28
|
-
readOnly: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Commit an edited node. The field writes it back through one ProseMirror
|
|
31
|
-
* `updateAttributes` transaction (undoable); the caller has already applied the
|
|
32
|
-
* no-dirty guard, so this only fires on a real structural change.
|
|
33
|
-
*/
|
|
34
|
-
onChange: (node: ComponentNode) => void;
|
|
35
|
-
/** Move the editor selection off this node — the inspector's back affordance. */
|
|
36
|
-
onDeselect: () => void;
|
|
37
|
-
}
|
|
38
|
-
export interface EmbedInspectorApi {
|
|
39
|
-
/** Show (or refocus) the inspector for the currently-selected embed. */
|
|
40
|
-
setActiveEmbed: (payload: EmbedInspectorPayload) => void;
|
|
41
|
-
/** Hide the inspector, but only when `nodeId` is the one currently shown. */
|
|
42
|
-
clearActiveEmbed: (nodeId: string) => void;
|
|
43
|
-
}
|
|
44
|
-
export declare const EmbedInspectorProvider: import("react").Provider<EmbedInspectorApi | null>;
|
|
45
|
-
/**
|
|
46
|
-
* The ambient embed-inspector sink, or `null` when the rich-text field is mounted
|
|
47
|
-
* outside a host that provides one (the builder property inspector, the asset detail
|
|
48
|
-
* pane). A `null` return is the signal to fall back to the modal prop editor.
|
|
49
|
-
*/
|
|
50
|
-
export declare function useOptionalEmbedInspector(): EmbedInspectorApi | null;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react';
|
|
2
|
-
const EmbedInspectorContext = createContext(null);
|
|
3
|
-
export const EmbedInspectorProvider = EmbedInspectorContext.Provider;
|
|
4
|
-
/**
|
|
5
|
-
* The ambient embed-inspector sink, or `null` when the rich-text field is mounted
|
|
6
|
-
* outside a host that provides one (the builder property inspector, the asset detail
|
|
7
|
-
* pane). A `null` return is the signal to fall back to the modal prop editor.
|
|
8
|
-
*/
|
|
9
|
-
export function useOptionalEmbedInspector() {
|
|
10
|
-
return useContext(EmbedInspectorContext);
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=embed-inspector-context.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"embed-inspector-context.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-inspector-context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AA+CjD,MAAM,qBAAqB,GAAG,aAAa,CAA2B,IAAI,CAAC,CAAA;AAE3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,qBAAqB,CAAC,QAAQ,CAAA;AAEpE;;;;GAIG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO,UAAU,CAAC,qBAAqB,CAAC,CAAA;AAC1C,CAAC","sourcesContent":["import { createContext, useContext } from 'react'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n/**\n * Selection-driven embed inspector plumbing (0.0.41) — a deliberately **TipTap-free**\n * seam between the rich-text editor and the CMS detail editor's right sidebar.\n *\n * When a `componentEmbed` node is selected inside a rich-text field, the field pushes\n * an {@link EmbedInspectorPayload} through this context; the detail editor swaps its\n * inspector sidebar for the embed's prop form (the same EC-190 controls the old modal\n * used). Deselecting clears it and the normal sidebar returns.\n *\n * It carries only plain data + callbacks (no `@tiptap/*` imports) so the detail\n * editor — which sits on the critical path and must NOT statically pull TipTap\n * (EC-322) — can consume it while the editor subtree stays behind `rich-text/lazy`.\n */\nexport interface EmbedInspectorPayload {\n /**\n * Stable identity of the embedded node (its `ComponentNode.id`). Dedupes selection\n * races: when the selection moves from embed A to embed B, B's \"open\" and A's\n * \"close\" can interleave — clearing keys on this id so A never wipes B.\n */\n nodeId: string\n /** The embedded component node whose props the inspector edits. */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /** Render the inspector read-only (mirrors the field's `readOnly`). */\n readOnly: boolean\n /**\n * Commit an edited node. The field writes it back through one ProseMirror\n * `updateAttributes` transaction (undoable); the caller has already applied the\n * no-dirty guard, so this only fires on a real structural change.\n */\n onChange: (node: ComponentNode) => void\n /** Move the editor selection off this node — the inspector's back affordance. */\n onDeselect: () => void\n}\n\nexport interface EmbedInspectorApi {\n /** Show (or refocus) the inspector for the currently-selected embed. */\n setActiveEmbed: (payload: EmbedInspectorPayload) => void\n /** Hide the inspector, but only when `nodeId` is the one currently shown. */\n clearActiveEmbed: (nodeId: string) => void\n}\n\nconst EmbedInspectorContext = createContext<EmbedInspectorApi | null>(null)\n\nexport const EmbedInspectorProvider = EmbedInspectorContext.Provider\n\n/**\n * The ambient embed-inspector sink, or `null` when the rich-text field is mounted\n * outside a host that provides one (the builder property inspector, the asset detail\n * pane). A `null` return is the signal to fall back to the modal prop editor.\n */\nexport function useOptionalEmbedInspector(): EmbedInspectorApi | null {\n return useContext(EmbedInspectorContext)\n}\n"]}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { EmbedInspectorPayload } from './embed-inspector-context.js';
|
|
2
|
-
/**
|
|
3
|
-
* The selection-driven embed prop inspector (0.0.41) — the detail editor's right
|
|
4
|
-
* sidebar swaps to this when a `componentEmbed` node is selected in a rich-text
|
|
5
|
-
* field. It replaces the retired-for-this-surface {@link EmbedPropsDialog} modal so
|
|
6
|
-
* embed props are edited in the SAME place document fields are, not a third form
|
|
7
|
-
* surface (the product-owner feedback that motivated the change).
|
|
8
|
-
*
|
|
9
|
-
* It reuses the exact modal machinery — the EC-190 {@link PropValueControl} stack and
|
|
10
|
-
* the {@link editableEmbedProps}/{@link blocksEmbedProps} manifest partitioning — so
|
|
11
|
-
* `blocks`-typed composition props are excluded (edited on the canvas) with a visible
|
|
12
|
-
* note, and asset/relation/object props open their real pickers.
|
|
13
|
-
*
|
|
14
|
-
* Write-back is **immediate per prop change** (like the builder inspector): each edit
|
|
15
|
-
* commits through the payload's `onChange` (one ProseMirror transaction upstream),
|
|
16
|
-
* guarded by {@link resolveEmbedWrite} so an unchanged write never dirties the field.
|
|
17
|
-
* There is no draft+Apply — the sidebar always reflects the committed node.
|
|
18
|
-
*
|
|
19
|
-
* TipTap-free by construction (see {@link EmbedInspectorPayload}) so the detail editor
|
|
20
|
-
* can import it without pulling the editor runtime onto the critical path (EC-322).
|
|
21
|
-
*/
|
|
22
|
-
export declare function EmbedInspector({ payload, className, }: {
|
|
23
|
-
payload: EmbedInspectorPayload;
|
|
24
|
-
className?: string;
|
|
25
|
-
}): import("react").JSX.Element;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { ChevronLeft } from 'lucide-react';
|
|
3
|
-
import { Button, Field, Row, Separator, Stack, Text } from '@elytracms/studio/ui';
|
|
4
|
-
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
5
|
-
import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
|
|
6
|
-
import { cn } from '@elytracms/studio/lib/utils';
|
|
7
|
-
import { PropValueControl } from '../prop-fields.js';
|
|
8
|
-
import { blocksEmbedProps, clearComponentNodeProp, editableEmbedProps, resolveEmbedWrite, setComponentNodeProp, } from './embed-props.js';
|
|
9
|
-
/**
|
|
10
|
-
* The selection-driven embed prop inspector (0.0.41) — the detail editor's right
|
|
11
|
-
* sidebar swaps to this when a `componentEmbed` node is selected in a rich-text
|
|
12
|
-
* field. It replaces the retired-for-this-surface {@link EmbedPropsDialog} modal so
|
|
13
|
-
* embed props are edited in the SAME place document fields are, not a third form
|
|
14
|
-
* surface (the product-owner feedback that motivated the change).
|
|
15
|
-
*
|
|
16
|
-
* It reuses the exact modal machinery — the EC-190 {@link PropValueControl} stack and
|
|
17
|
-
* the {@link editableEmbedProps}/{@link blocksEmbedProps} manifest partitioning — so
|
|
18
|
-
* `blocks`-typed composition props are excluded (edited on the canvas) with a visible
|
|
19
|
-
* note, and asset/relation/object props open their real pickers.
|
|
20
|
-
*
|
|
21
|
-
* Write-back is **immediate per prop change** (like the builder inspector): each edit
|
|
22
|
-
* commits through the payload's `onChange` (one ProseMirror transaction upstream),
|
|
23
|
-
* guarded by {@link resolveEmbedWrite} so an unchanged write never dirties the field.
|
|
24
|
-
* There is no draft+Apply — the sidebar always reflects the committed node.
|
|
25
|
-
*
|
|
26
|
-
* TipTap-free by construction (see {@link EmbedInspectorPayload}) so the detail editor
|
|
27
|
-
* can import it without pulling the editor runtime onto the critical path (EC-322).
|
|
28
|
-
*/
|
|
29
|
-
export function EmbedInspector({ payload, className, }) {
|
|
30
|
-
const { node, manifest, readOnly, onChange, onDeselect } = payload;
|
|
31
|
-
const editable = editableEmbedProps(manifest);
|
|
32
|
-
const blocks = blocksEmbedProps(manifest);
|
|
33
|
-
function set(name, value) {
|
|
34
|
-
const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value));
|
|
35
|
-
if (next)
|
|
36
|
-
onChange(next);
|
|
37
|
-
}
|
|
38
|
-
function clear(name) {
|
|
39
|
-
const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name));
|
|
40
|
-
if (next)
|
|
41
|
-
onChange(next);
|
|
42
|
-
}
|
|
43
|
-
return (_jsxs("div", { className: cn('flex h-full min-h-0 flex-col bg-card', className), children: [_jsxs(Row, { gap: "1", align: "center", className: "shrink-0 border-b border-border p-1.5", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": translate('richText.embed.inspectorBack'), onClick: onDeselect, children: _jsx(ChevronLeft, { className: "size-4" }) }), _jsx(Text, { as: "span", size: "xs", weight: "semibold", className: "min-w-0 flex-1 truncate", children: componentDisplayName(node.componentId, manifest) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-3", children: _jsxs(Stack, { gap: "4", "aria-disabled": readOnly || undefined, className: cn(readOnly && 'pointer-events-none opacity-70'), children: [editable.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", children: translate('richText.embed.noProps') })) : (editable.map(([name, spec]) => {
|
|
44
|
-
const id = `embed-${node.id}-${name}`;
|
|
45
|
-
return (_jsx(Field, { label: localizedLabel(spec.form?.label, name), htmlFor: id, children: _jsx(PropValueControl, { id: id, name: name, spec: spec, value: node.props?.[name], onSet: (v) => set(name, v), onClear: () => clear(name) }) }, name));
|
|
46
|
-
})), blocks.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { size: "xs", tone: "subtle", children: translate('richText.embed.blocksNote') })] })) : null] }) })] }));
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=embed-inspector.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"embed-inspector.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-inspector.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACjF,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAA;AAC3E,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,eAAe,CAAA;AAGtB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,OAAO,EACP,SAAS,GAIV;IACC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAA;IAClE,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IAC7C,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAEzC,SAAS,GAAG,CAAC,IAAY,EAAE,KAAgB;QACzC,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;QAC7E,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IACD,SAAS,KAAK,CAAC,IAAY;QACzB,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,sBAAsB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;QACxE,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,sCAAsC,EAAE,SAAS,CAAC,aACnE,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,uCAAuC,aAC3E,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,SAAS,gBACF,SAAS,CAAC,8BAA8B,CAAC,EACrD,OAAO,EAAE,UAAU,YAEnB,KAAC,WAAW,IAAC,SAAS,EAAC,QAAQ,GAAG,GAC3B,EACT,KAAC,IAAI,IAAC,EAAE,EAAC,MAAM,EAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,EAAC,SAAS,EAAC,yBAAyB,YAC5E,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GAC5C,IACH,EACN,cAAK,SAAS,EAAC,kCAAkC,YAC/C,MAAC,KAAK,IACJ,GAAG,EAAC,GAAG,mBACQ,QAAQ,IAAI,SAAS,EACpC,SAAS,EAAE,EAAE,CAAC,QAAQ,IAAI,gCAAgC,CAAC,aAE1D,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACvB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,wBAAwB,CAAC,GAC/B,CACR,CAAC,CAAC,CAAC,CACF,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;4BAC5B,MAAM,EAAE,GAAG,SAAS,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,CAAA;4BACrC,OAAO,CACL,KAAC,KAAK,IAAY,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,YAC1E,KAAC,gBAAgB,IACf,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EACzB,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,EAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAC1B,IARQ,IAAI,CASR,CACT,CAAA;wBACH,CAAC,CAAC,CACH,EACA,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACnB,8BACE,KAAC,SAAS,KAAG,EACb,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,2BAA2B,CAAC,GAClC,IACN,CACJ,CAAC,CAAC,CAAC,IAAI,IACF,GACJ,IACF,CACP,CAAA;AACH,CAAC","sourcesContent":["import { ChevronLeft } from 'lucide-react'\nimport type { JsonValue } from '@elytracms/core/project-graph'\nimport { Button, Field, Row, Separator, Stack, Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { PropValueControl } from '../prop-fields'\nimport {\n blocksEmbedProps,\n clearComponentNodeProp,\n editableEmbedProps,\n resolveEmbedWrite,\n setComponentNodeProp,\n} from './embed-props'\nimport type { EmbedInspectorPayload } from './embed-inspector-context'\n\n/**\n * The selection-driven embed prop inspector (0.0.41) — the detail editor's right\n * sidebar swaps to this when a `componentEmbed` node is selected in a rich-text\n * field. It replaces the retired-for-this-surface {@link EmbedPropsDialog} modal so\n * embed props are edited in the SAME place document fields are, not a third form\n * surface (the product-owner feedback that motivated the change).\n *\n * It reuses the exact modal machinery — the EC-190 {@link PropValueControl} stack and\n * the {@link editableEmbedProps}/{@link blocksEmbedProps} manifest partitioning — so\n * `blocks`-typed composition props are excluded (edited on the canvas) with a visible\n * note, and asset/relation/object props open their real pickers.\n *\n * Write-back is **immediate per prop change** (like the builder inspector): each edit\n * commits through the payload's `onChange` (one ProseMirror transaction upstream),\n * guarded by {@link resolveEmbedWrite} so an unchanged write never dirties the field.\n * There is no draft+Apply — the sidebar always reflects the committed node.\n *\n * TipTap-free by construction (see {@link EmbedInspectorPayload}) so the detail editor\n * can import it without pulling the editor runtime onto the critical path (EC-322).\n */\nexport function EmbedInspector({\n payload,\n className,\n}: {\n payload: EmbedInspectorPayload\n className?: string\n}) {\n const { node, manifest, readOnly, onChange, onDeselect } = payload\n const editable = editableEmbedProps(manifest)\n const blocks = blocksEmbedProps(manifest)\n\n function set(name: string, value: JsonValue) {\n const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value))\n if (next) onChange(next)\n }\n function clear(name: string) {\n const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name))\n if (next) onChange(next)\n }\n\n return (\n <div className={cn('flex h-full min-h-0 flex-col bg-card', className)}>\n <Row gap=\"1\" align=\"center\" className=\"shrink-0 border-b border-border p-1.5\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('richText.embed.inspectorBack')}\n onClick={onDeselect}\n >\n <ChevronLeft className=\"size-4\" />\n </Button>\n <Text as=\"span\" size=\"xs\" weight=\"semibold\" className=\"min-w-0 flex-1 truncate\">\n {componentDisplayName(node.componentId, manifest)}\n </Text>\n </Row>\n <div className=\"min-h-0 flex-1 overflow-auto p-3\">\n <Stack\n gap=\"4\"\n aria-disabled={readOnly || undefined}\n className={cn(readOnly && 'pointer-events-none opacity-70')}\n >\n {editable.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n editable.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field key={name} label={localizedLabel(spec.form?.label, name)} htmlFor={id}>\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={node.props?.[name]}\n onSet={(v) => set(name, v)}\n onClear={() => clear(name)}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <>\n <Separator />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n </>\n ) : null}\n </Stack>\n </div>\n </div>\n )\n}\n"]}
|