efficient-easel 0.0.0 → 0.97.0

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 (119) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +818 -4
  3. package/dist/autosave/index.d.ts +101 -0
  4. package/dist/autosave/index.js +1 -0
  5. package/dist/blocks/index.d.ts +306 -0
  6. package/dist/blocks/index.js +0 -0
  7. package/dist/buckets-1vRlQceY.d.ts +13 -0
  8. package/dist/canvas/index.d.ts +194 -0
  9. package/dist/canvas/index.js +1 -0
  10. package/dist/chrome/chrome.css +570 -0
  11. package/dist/chrome/index.d.ts +219 -0
  12. package/dist/chrome/index.js +21 -0
  13. package/dist/chunk-2LUSK32B.js +1 -0
  14. package/dist/chunk-2OAZYSWJ.js +1 -0
  15. package/dist/chunk-67VZRQLG.js +13 -0
  16. package/dist/chunk-6OPSJGAI.js +1 -0
  17. package/dist/chunk-6PS6STGA.js +1 -0
  18. package/dist/chunk-6WY6ZXJG.js +1 -0
  19. package/dist/chunk-7A7O2JWQ.js +1 -0
  20. package/dist/chunk-BZ5ASXOL.js +1 -0
  21. package/dist/chunk-CERTFXCV.js +13 -0
  22. package/dist/chunk-D2BCHTQR.js +1 -0
  23. package/dist/chunk-FI23ALVJ.js +1 -0
  24. package/dist/chunk-GGTPNGFJ.js +1 -0
  25. package/dist/chunk-H4B72XCJ.js +1 -0
  26. package/dist/chunk-H6DDIBQ4.js +1 -0
  27. package/dist/chunk-KDIVA6WD.js +1 -0
  28. package/dist/chunk-KRXQM65X.js +1 -0
  29. package/dist/chunk-MCLBHENY.js +1 -0
  30. package/dist/chunk-MFIRYGHW.js +1 -0
  31. package/dist/chunk-NKB3KVAB.js +1 -0
  32. package/dist/chunk-O2NLQBFX.js +1 -0
  33. package/dist/chunk-OGZFDJ6G.js +1 -0
  34. package/dist/chunk-OWBO4WKA.js +0 -0
  35. package/dist/chunk-QB3BZIRA.js +5 -0
  36. package/dist/chunk-RE4LP46F.js +1 -0
  37. package/dist/chunk-STFMZJKU.js +14 -0
  38. package/dist/chunk-SUO5627P.js +1 -0
  39. package/dist/chunk-TDA4HH54.js +1 -0
  40. package/dist/chunk-WK3AFPXY.js +1 -0
  41. package/dist/chunk-YIMRTIBN.js +3 -0
  42. package/dist/chunk-YNZZYHA4.js +450 -0
  43. package/dist/chunk-YRLPFZW3.js +1 -0
  44. package/dist/chunk-ZAOM6PFH.js +1 -0
  45. package/dist/chunk-ZNCEOSXO.js +1 -0
  46. package/dist/copy/copy.css +282 -0
  47. package/dist/copy/index.d.ts +336 -0
  48. package/dist/copy/index.js +10 -0
  49. package/dist/data/index.d.ts +375 -0
  50. package/dist/data/index.js +1 -0
  51. package/dist/drag/drag.css +360 -0
  52. package/dist/drag/index.d.ts +379 -0
  53. package/dist/drag/index.js +18 -0
  54. package/dist/edit-lock/index.d.ts +354 -0
  55. package/dist/edit-lock/index.js +1 -0
  56. package/dist/editor/editor.css +58 -0
  57. package/dist/editor/index.d.ts +361 -0
  58. package/dist/editor/index.js +9 -0
  59. package/dist/fields/fields.css +207 -0
  60. package/dist/fields/index.d.ts +273 -0
  61. package/dist/fields/index.js +46 -0
  62. package/dist/gold/gold.css +196 -0
  63. package/dist/gold/index.d.ts +280 -0
  64. package/dist/gold/index.js +1 -0
  65. package/dist/icons/index.d.ts +474 -0
  66. package/dist/icons/index.js +1 -0
  67. package/dist/index.d.ts +6 -0
  68. package/dist/index.js +1 -0
  69. package/dist/markup/index.d.ts +452 -0
  70. package/dist/markup/index.js +1 -0
  71. package/dist/menu/index.d.ts +49 -0
  72. package/dist/menu/index.js +15 -0
  73. package/dist/menu/menu.css +254 -0
  74. package/dist/node-ops/index.d.ts +91 -0
  75. package/dist/node-ops/index.js +1 -0
  76. package/dist/panel/index.d.ts +1088 -0
  77. package/dist/panel/index.js +16 -0
  78. package/dist/panel/panel.css +1460 -0
  79. package/dist/pick/index.d.ts +153 -0
  80. package/dist/pick/index.js +14 -0
  81. package/dist/pick/pick.css +235 -0
  82. package/dist/plan-DChbUXdS.d.ts +88 -0
  83. package/dist/rail/index.d.ts +145 -0
  84. package/dist/rail/index.js +1 -0
  85. package/dist/rail/rail.css +175 -0
  86. package/dist/render/index.d.ts +117 -0
  87. package/dist/render/index.js +5 -0
  88. package/dist/resolve-D68pb3Bx.d.ts +57 -0
  89. package/dist/shell/index.d.ts +159 -0
  90. package/dist/shell/index.js +1 -0
  91. package/dist/shell/shell.css +122 -0
  92. package/dist/strings/catalogue.d.ts +141 -0
  93. package/dist/strings/catalogue.js +1 -0
  94. package/dist/strings/index.d.ts +56 -0
  95. package/dist/strings/index.js +2 -0
  96. package/dist/style-clipboard/index.d.ts +184 -0
  97. package/dist/style-clipboard/index.js +1 -0
  98. package/dist/tree/index.d.ts +173 -0
  99. package/dist/tree/index.js +1 -0
  100. package/dist/tree/tree.css +618 -0
  101. package/dist/types-41704JE4.d.ts +112 -0
  102. package/dist/types-CEqUFH2j.d.ts +129 -0
  103. package/dist/types-CuNiSnX6.d.ts +204 -0
  104. package/dist/types-DNrn_w4V.d.ts +88 -0
  105. package/dist/types-Dy6oR6Z2.d.ts +153 -0
  106. package/dist/types-pGBXevBU.d.ts +131 -0
  107. package/dist/useNodeOps-BF0fQsif.d.ts +68 -0
  108. package/dist/watch-DmasQA61.d.ts +60 -0
  109. package/dist/wordFit-9B9nqyf3.d.ts +35 -0
  110. package/dist/words-B2YLaKEr.d.ts +30 -0
  111. package/dist/words-B87GIFWH.d.ts +86 -0
  112. package/dist/words-BK-npta-.d.ts +27 -0
  113. package/dist/words-CD3WADyb.d.ts +51 -0
  114. package/dist/words-CsRWXkY4.d.ts +129 -0
  115. package/dist/words-D9ChvbNC.d.ts +52 -0
  116. package/dist/words-DOhF-4D4.d.ts +35 -0
  117. package/dist/words-DagJdwIA.d.ts +78 -0
  118. package/dist/words-DfxEp_RH.d.ts +142 -0
  119. package/package.json +202 -3
@@ -0,0 +1,175 @@
1
+ /*
2
+ * efficient-easel — rail styles.
3
+ *
4
+ * The values here mirror `railTokens` in tokens.ts one-for-one (both trace back
5
+ * to the storefront editor's `.sf-rail` rules — the visual reference). They are
6
+ * declared as custom properties ON `.es-rail`, not on `:root`, so importing this
7
+ * stylesheet themes ONLY the rail and never leaks variables into the host page.
8
+ * Re-theme by overriding any `--es-rail-*` var on or above the rail element.
9
+ *
10
+ * Import once from a client entry / global stylesheet:
11
+ * import 'efficient-easel/rail.css';
12
+ */
13
+
14
+ .es-rail {
15
+ /* — tokens (see tokens.ts / railCssVars) — */
16
+ --es-rail-w: 46px;
17
+ --es-rail-pad-y: 8px;
18
+ --es-rail-gap: 4px;
19
+ --es-rail-radius: 12px;
20
+ --es-rail-btn-size: 34px;
21
+ --es-rail-btn-radius: 9px;
22
+ --es-rail-icon-size: 18px;
23
+ --es-rail-word-pad-x: 8px;
24
+ --es-rail-word-gap: 6px;
25
+ --es-rail-word-size: 12px;
26
+ --es-rail-word-weight: 500;
27
+ --es-rail-bg: #ffffff;
28
+ --es-rail-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06);
29
+ --es-rail-fg: #6b7280;
30
+ --es-rail-hover-bg: #f3f4f6;
31
+ --es-rail-hover-fg: #111827;
32
+ --es-rail-accent: #2563eb;
33
+ --es-rail-accent-tint: #eff6ff;
34
+
35
+ flex: none;
36
+ width: var(--es-rail-w);
37
+ display: flex;
38
+ flex-direction: column;
39
+ align-items: center;
40
+ gap: var(--es-rail-gap);
41
+ padding: var(--es-rail-pad-y) 0;
42
+ background: var(--es-rail-bg);
43
+ border-radius: var(--es-rail-radius);
44
+ box-shadow: var(--es-rail-shadow);
45
+ }
46
+
47
+ /*
48
+ * The ROW variant (card 66160) — the same rail laid into a top bar.
49
+ *
50
+ * The column costs a 1280px laptop 56px of website preview (a 46px rail plus
51
+ * the 10px panel gap) that the top bar has going spare, so the icons move up
52
+ * there. `.es-rail` above is UNTOUCHED: this rule only overrides, so a consumer
53
+ * who never passes `orientation` gets exactly the stylesheet they had.
54
+ *
55
+ * THE CARD LOOK GOES, THROUGH THE TOKENS THAT ALREADY EXIST. The bar has its
56
+ * own surface and shadow; a second white card floating inside it is the bug.
57
+ * These re-point the THREE custom properties the base rule already reads rather
58
+ * than re-stating `background` / `box-shadow` / `border-radius` here — so the
59
+ * card look stays declared in exactly one place, and a host re-theming the rail
60
+ * still only has the `--es-rail-*` names to learn.
61
+ *
62
+ * WHAT DELIBERATELY DOES NOT CHANGE: the button size, the button radius, the
63
+ * icon size, the gap and every colour. The icons must not resize when they move
64
+ * into the bar, so no `--es-rail-btn-*`, `--es-rail-icon-size` or colour token
65
+ * appears below, and there is no `.es-rail-row .es-rail-btn` rule anywhere.
66
+ * `align-items: center` is inherited from the base rule and does the right
67
+ * thing here too — in a row it centres the buttons vertically.
68
+ */
69
+ .es-rail.es-rail-row {
70
+ --es-rail-bg: transparent;
71
+ --es-rail-shadow: none;
72
+ --es-rail-radius: 0;
73
+
74
+ flex-direction: row;
75
+ /* No fixed width: the row is as wide as its buttons. `flex: none` from the
76
+ base rule keeps flex-basis on `auto`, so this sizes to content. */
77
+ width: auto;
78
+ /* The column pads `--es-rail-pad-y 0`: the one step goes on the MAIN axis —
79
+ the run of buttons — and the cross axis is handled by the fixed width.
80
+ Rotate the axes and the same step goes on the ENDS of the row. The cross
81
+ axis is 0 on purpose, so the row is exactly one button tall (34px) and the
82
+ 52px bar centres it, instead of the rail imposing a height on the bar. */
83
+ padding: 0 var(--es-rail-pad-y);
84
+ }
85
+
86
+ .es-rail-btn {
87
+ width: var(--es-rail-btn-size);
88
+ height: var(--es-rail-btn-size);
89
+ border: none;
90
+ background: transparent;
91
+ border-radius: var(--es-rail-btn-radius);
92
+ color: var(--es-rail-fg);
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ cursor: pointer;
97
+ transition: background 0.12s, color 0.12s;
98
+ }
99
+
100
+ .es-rail-btn svg {
101
+ width: var(--es-rail-icon-size);
102
+ height: var(--es-rail-icon-size);
103
+ display: block;
104
+ }
105
+
106
+ .es-rail-btn:hover:not(:disabled) {
107
+ background: var(--es-rail-hover-bg);
108
+ color: var(--es-rail-hover-fg);
109
+ }
110
+
111
+ .es-rail-btn.is-active {
112
+ background: var(--es-rail-accent-tint);
113
+ color: var(--es-rail-accent);
114
+ }
115
+
116
+ .es-rail-btn:disabled {
117
+ opacity: 0.35;
118
+ cursor: default;
119
+ }
120
+
121
+ /* ── Words beside the icons (a row rail only) ────────────────────────────────
122
+ THE SETTINGS TABS' RULE (owner, 2026-09-18: "the rule must be the same as the
123
+ top tabs of the right settings panel"). Three states, picked by the width of
124
+ the rail's column and nothing else:
125
+
126
+ every word when every button fits with its word;
127
+ chosen word otherwise the glyphs, plus the word of the button whose
128
+ panel is open;
129
+ glyphs only when even that does not fit.
130
+
131
+ BOTH parts are always in the markup once the words are measured; the widths
132
+ decide which are drawn, in CSS, against the column's own width (`100cqi`) —
133
+ the tab strip's way, not a script's. The thresholds come from ONE rule, the
134
+ "word fit" block in `panel.css`, which the segmented control reads too. This
135
+ sheet only names the rail's chrome for it — the four inputs below — so a
136
+ re-padded rail button moves its thresholds with it.
137
+
138
+ `is-measured` is set by `EditorRail` when it was handed the words' widths
139
+ (`TopBarRail` measures them). Without it, and without `panel.css`, the word
140
+ stays clipped to nothing: an icon-only rail always fits. */
141
+ .es-rail.es-rail-row.is-measured {
142
+ --es-fit-chip: var(--es-rail-btn-size);
143
+ --es-fit-word-gap: var(--es-rail-word-gap);
144
+ --es-fit-gap: var(--es-rail-gap);
145
+ --es-fit-ends: var(--es-rail-pad-y);
146
+ }
147
+ /* The button keeps its 34px height, its radius and its idle / hover / active
148
+ colours; only its width follows the word. Wordless it is 34px wide exactly
149
+ as before — the padding plus the 18px glyph is 34. The icon-to-word gap is
150
+ the WORD's margin (see `panel.css`), so the button's own `gap` stays 0 and
151
+ goes with the word.
152
+
153
+ `min-width: 0` KEEPS THE ICON-ONLY RAIL'S GIVE. Six glyph buttons need 240px
154
+ and the 220px panel floor gives the column 219: the icon-only rail has
155
+ always shrunk its buttons a few px there rather than run past the column,
156
+ and padding would otherwise make the button's own content its floor.
157
+ The glyph keeps its square instead (the rule after), which is also what
158
+ holds a narrower glyph — the shop's text "+" — to a 34px button. */
159
+ .es-rail.es-rail-row.is-measured .es-rail-btn {
160
+ width: auto;
161
+ min-width: 0;
162
+ padding: 0 var(--es-rail-word-pad-x);
163
+ }
164
+ .es-rail.es-rail-row.is-measured .es-rail-btn > :first-child {
165
+ min-width: var(--es-rail-icon-size);
166
+ text-align: center;
167
+ }
168
+ .es-rail-word {
169
+ font-size: var(--es-rail-word-size);
170
+ font-weight: var(--es-rail-word-weight);
171
+ line-height: 1;
172
+ white-space: nowrap;
173
+ overflow: hidden;
174
+ max-width: 0;
175
+ }
@@ -0,0 +1,117 @@
1
+ import { CSSProperties, ReactNode, ReactElement } from 'react';
2
+ import { B as BlockShape, D as Data } from '../types-41704JE4.js';
3
+
4
+ /**
5
+ * efficient-easel/render — draw a saved document, the published way.
6
+ *
7
+ * Step 1.2 of the engine that replaces Puck (blueprint "Puck From The Ground
8
+ * Up For Efficient Editor", 2026-09-29). The published storefront draws every
9
+ * page through Puck's `<Render>` today (efficient-shop `lib/puck/render-client.tsx`
10
+ * and `components/PublishedFooter.tsx`); this is the drop-in for it: the same
11
+ * config, the same document, THE SAME HTML — `tests/render-puck-parity.test.tsx`
12
+ * renders both and compares the markup byte for byte.
13
+ *
14
+ * WHAT IS DIFFERENT IS WHAT IT IS MADE OF. Puck's `<Render>` reaches the
15
+ * document through a React context and builds each block's props in hooks, so
16
+ * it can only run as a client component. This is a plain function: no hook, no
17
+ * context, no state, no browser global. It runs in a server component, in a
18
+ * client component, in a Node script, or called directly — `Render(props)`
19
+ * returns the element tree. Whether the BLOCKS can run on the server is up to
20
+ * the blocks (the storefront's are client components today; blueprint default
21
+ * #4 keeps that wrapper until they are moved one by one).
22
+ *
23
+ * THE BLOCK AUTHORING SHAPE IS PUCK'S (blueprint default #3), so an app hands
24
+ * in the config it has:
25
+ *
26
+ * - a block's `render` receives its props, plus `puck`, plus each SLOT as a
27
+ * component: `render: ({ content: Content }) => <Content className="…" />`.
28
+ * A slot draws `<div className style>` around its blocks, as Puck's does;
29
+ * - the root's `render` receives the root props, `puck`, `title` (`''` when
30
+ * unset), `editMode: false`, `id: 'puck-root'`, and the page as `children`;
31
+ * - a block type the config does not know draws nothing.
32
+ *
33
+ * Three details are copied exactly because a block can observe them, not
34
+ * because they are good design:
35
+ *
36
+ * 1. The top-level list is wrapped in one plain `<div>`, root render or not.
37
+ * (Puck's own SERVER `<Render>` uses a fragment there instead, so Puck's
38
+ * two renderers disagree; the client one is what is published.) A host
39
+ * that needs to reach that wrapper from its stylesheet passes `className`
40
+ * (0.95.0); left out, the `<div>` is as bare as Puck's.
41
+ * 2. `puck` differs by level. The root gets
42
+ * `{ renderDropZone, isEditing: false, dragRef: null, metadata }`; a
43
+ * top-level block gets `{ renderDropZone, metadata: { ...metadata,
44
+ * ...block.metadata } }`; a block inside a slot gets `{ metadata }` only.
45
+ * 3. A missing top-level slot draws as an empty slot; a slot is found by its
46
+ * field, including inside `object` and `array` fields.
47
+ *
48
+ * `renderDropZone` draws a legacy `zones` entry. Neither editor writes zones,
49
+ * so it draws an empty `<div>` in practice; it exists so a block that still
50
+ * calls it gets what Puck gave it.
51
+ *
52
+ * LEAF DISCIPLINE: imports React's JSX runtime and this package's own data
53
+ * layer, nothing else (`tests/render-leaf-safety.test.ts`).
54
+ */
55
+
56
+ type Metadata = Record<string, unknown>;
57
+ /** What a slot component accepts. Only `className` and `style` draw anything. */
58
+ interface SlotProps {
59
+ className?: string;
60
+ style?: CSSProperties;
61
+ allow?: string[];
62
+ disallow?: string[];
63
+ minEmptyHeight?: number;
64
+ collisionAxis?: unknown;
65
+ }
66
+ /** A slot, as a block's `render` receives it. */
67
+ type SlotComponent = (props?: SlotProps) => ReactElement;
68
+ interface DropZoneProps extends SlotProps {
69
+ zone: string;
70
+ }
71
+ /** The `puck` prop. Which keys are present depends on the level (see above). */
72
+ interface RenderContext {
73
+ renderDropZone?: (props: DropZoneProps) => ReactElement;
74
+ isEditing?: boolean;
75
+ dragRef?: null;
76
+ metadata: Metadata;
77
+ }
78
+ type BlockRender = (props: any) => ReactNode;
79
+ interface RenderBlock extends BlockShape {
80
+ render: BlockRender;
81
+ metadata?: Metadata;
82
+ }
83
+ interface RenderConfig {
84
+ root?: BlockShape & {
85
+ render?: BlockRender;
86
+ };
87
+ components: Record<string, RenderBlock | undefined>;
88
+ }
89
+ /**
90
+ * What `Render` draws: a saved document, as loosely as the renderer reads it.
91
+ * `root.props` is OPTIONAL here, as it is in Puck's own `Data` type — a root
92
+ * that holds its props directly (the older format) is drawn the way Puck draws
93
+ * it, and a document with no root or no content draws an empty page. So a
94
+ * Puck-typed document is passed as it is, with no cast.
95
+ */
96
+ interface RenderData {
97
+ root?: {
98
+ props?: Record<string, unknown>;
99
+ [key: string]: unknown;
100
+ };
101
+ content?: unknown[];
102
+ zones?: Record<string, unknown[]>;
103
+ }
104
+ interface RenderProps {
105
+ config: RenderConfig;
106
+ data: RenderData | Partial<Data>;
107
+ metadata?: Metadata;
108
+ /**
109
+ * A class for the `<div>` around the top-level list — that wrapper, and
110
+ * nothing else. OPT-IN: left out (or empty) the wrapper carries no attribute
111
+ * at all, and the markup is Puck's, byte for byte.
112
+ */
113
+ className?: string;
114
+ }
115
+ declare function Render({ config, data, metadata, className }: RenderProps): ReactElement;
116
+
117
+ export { type DropZoneProps, type Metadata, Render, type RenderBlock, type RenderConfig, type RenderContext, type RenderData, type RenderProps, type SlotComponent, type SlotProps };
@@ -0,0 +1,5 @@
1
+ import{isRecord as t,fieldsFrom as n,slotLocations as e,setIn as r,isNode as o}from"../chunk-NKB3KVAB.js";import{jsx as a}from"react/jsx-runtime";function c({config:e,data:r,metadata:o={},className:c}){const s=Array.isArray(r?.content)?r.content:[],l=t(r?.root)?r.root:{},p="props"in l?l.props:l,m={config:e,content:s,zones:t(r?.zones)?r.zones:{},metadata:o},f={...p,puck:{renderDropZone:d(m,"root"),isEditing:!1,dragRef:null,metadata:o},title:p?.title||"",editMode:!1,id:"puck-root"},y=i(m,n(e.root),f),g=s.map(t=>u(m,t)),k=/* @__PURE__ */a("div",c?{className:c,children:g}:{children:g}),N=e.root?.render;return N?/* @__PURE__ */a(N,{...y,children:k}):k}function i(t,n,o){let a=o;for(const c of e(o,n))a=r(a,c.segments,s(t,c.content));return a}function s(t,e){return function(r={}){/* @__PURE__ */
2
+ return a("div",{className:r.className,style:r.style,children:e.map(e=>function(t,e){if(!o(e))return null;const r=t.config.components[e.type];if(!r)return null;const c=i(t,n(r),e.props),s=r.render,u={...c.puck,metadata:t.metadata||{}};/* @__PURE__ */
3
+ return a(s,{...c,puck:u},c.id)}(t,e))})}}function u(t,e){if(!o(e))return null;const r=t.config.components[e.type];if(!r)return null;const c=i(t,n(r),e.props),s=r.render,u={...c.puck,renderDropZone:d(t,c.id),metadata:{...t.metadata,...r.metadata}};/* @__PURE__ */
4
+ return a(s,{...c,puck:u},c.id)}function d(t,n){return function({zone:e,className:r,style:o}){const c=`${n}:${e}`,i="root:default-zone"===c?t.content:t.zones[c],s=Array.isArray(i)?i:[];/* @__PURE__ */
5
+ return a("div",{className:r,style:o,children:s.map(n=>u(t,n))})}}export{c as Render};
@@ -0,0 +1,57 @@
1
+ import { a as DataConfig } from './types-41704JE4.js';
2
+
3
+ /**
4
+ * A BLOCK'S `resolveData`, RUN AS PUCK RUNS IT — `resolveComponentData` in Puck
5
+ * 0.20 (`lib/resolve-component-data.ts`), because the documents both editors
6
+ * hold were resolved by it and must go on resolving the same way.
7
+ *
8
+ * - It runs only when the item differs from the last one resolved for that id
9
+ * (deep-equal), unless the trigger is "force"; the last answer is reused.
10
+ * - `changed` names every prop of the new item with whether it differs from
11
+ * the last one resolved (`changedProps`, Puck's `getChanged`).
12
+ * - The block's own `metadata` is spread over the host's.
13
+ * - The answer's `props` are spread OVER the item's, and its `readOnly` kept
14
+ * when it has any key.
15
+ * - Then every slot's children are resolved the same way, in order.
16
+ *
17
+ * What differs from Puck is only where the memory lives: Puck kept the last
18
+ * change per id in a module-level object shared by every editor on the page;
19
+ * here the host hands in its own (`createResolveCache`), one per editor.
20
+ *
21
+ * Pure apart from the resolvers it calls: no React, no browser global.
22
+ */
23
+ type ResolveTrigger = 'insert' | 'replace' | 'load' | 'force' | 'move';
24
+ /** An item to resolve: a node, or the root (`{ props }`, no type). */
25
+ interface ResolvableItem {
26
+ type?: string;
27
+ props: Record<string, unknown>;
28
+ readOnly?: Record<string, boolean>;
29
+ }
30
+ interface ResolveCache {
31
+ lastChange: Record<string, {
32
+ item: ResolvableItem;
33
+ resolved: ResolvableItem;
34
+ }>;
35
+ }
36
+ declare const createResolveCache: () => ResolveCache;
37
+ interface ResolveOptions {
38
+ metadata?: Record<string, unknown>;
39
+ trigger?: ResolveTrigger;
40
+ cache?: ResolveCache;
41
+ onResolveStart?: (item: ResolvableItem) => void;
42
+ onResolveEnd?: (item: ResolvableItem) => void;
43
+ }
44
+ /** Deep equality of JSON-shaped values, as `fast-deep-equal` answers it for them. */
45
+ declare function deepEqual(a: unknown, b: unknown): boolean;
46
+ /** Every prop of `next`, and whether it differs from `last` (Puck's `getChanged`). */
47
+ declare function changedProps(next: ResolvableItem | null | undefined, last: ResolvableItem | null | undefined): Record<string, boolean>;
48
+ /**
49
+ * Resolve one item and everything in its slots. `didChange` says whether the
50
+ * answer differs from the item handed in.
51
+ */
52
+ declare function resolveNodeData<T extends ResolvableItem>(item: T, config: DataConfig, options?: ResolveOptions): Promise<{
53
+ node: T;
54
+ didChange: boolean;
55
+ }>;
56
+
57
+ export { type ResolvableItem as R, type ResolveCache as a, type ResolveOptions as b, type ResolveTrigger as c, changedProps as d, createResolveCache as e, deepEqual as f, resolveNodeData as r };
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Editor SHELL design tokens — the ONE source both editors' chrome converge on.
3
+ *
4
+ * Where `rail/tokens.ts` owns the slim icon-rail, this owns the rest of the
5
+ * editor chrome theme: accent, surfaces, fields, text, lines, chips, named
6
+ * chrome literals, danger/warning/success, radii, gap, shadows and font. Every
7
+ * value bar one is taken verbatim from the storefront editor's `--sf-*` block
8
+ * (`efficient-shop/components/storefront-editor.css`), the visual reference the
9
+ * campaign designer (`--cd-*`) and the storefront's own TS mirror (`tk`) had
10
+ * each re-copied. After card 66024 they all resolve to these values instead.
11
+ * The one exception is `success`, which that block has no counterpart for; see
12
+ * its own note below.
13
+ *
14
+ * Exposed BOTH ways, per the same value (mirrors the rail):
15
+ * - `shellTokens` — this JS object, for code that needs the numbers/colours
16
+ * (e.g. the storefront's `floating-editor/tokens.ts` inline-style mirror);
17
+ * - `shell.css` — the stylesheet, which declares the identical values as CSS
18
+ * custom properties on `.es-editor` (names listed in `shellCssVars`).
19
+ *
20
+ * Numeric fields are pixel magnitudes (unitless) so they compose in code; the
21
+ * CSS applies the `px`. Both editing surfaces are LIGHT only (no dark mode), so
22
+ * there is a single palette here — no dark counterpart.
23
+ */
24
+ declare const shellTokens: {
25
+ /** `--es-accent` — blue-600. */
26
+ readonly accent: "#2563eb";
27
+ /** `--es-accent-tint` — blue-50. */
28
+ readonly accentTint: "#eff6ff";
29
+ /** `--es-accent-tint-strong` — blue-100. */
30
+ readonly accentTintStrong: "#dbeafe";
31
+ /** `--es-accent-line` — blue-200: a quiet accent hairline (an empty slot's outline). */
32
+ readonly accentLine: "#bfdbfe";
33
+ /** `--es-accent-text` — blue-700. */
34
+ readonly accentText: "#1d4ed8";
35
+ /** `--es-accent-ring` — focus ring wash. */
36
+ readonly accentRing: "rgba(37, 99, 235, 0.25)";
37
+ /** `--es-accent-shadow` — accent button shadow. */
38
+ readonly accentShadow: "rgba(37, 99, 235, 0.35)";
39
+ /** `--es-on-accent` — text/icon over the solid accent. */
40
+ readonly onAccent: "#ffffff";
41
+ /** `--es-app-bg` — gray-50, sunken canvas. */
42
+ readonly appBg: "#f9fafb";
43
+ /** `--es-preview-bg` — gray-100, preview well. */
44
+ readonly previewBg: "#f3f4f6";
45
+ /** `--es-panel` — raised cards / panels. */
46
+ readonly panel: "#ffffff";
47
+ /** `--es-raised` — a lifted surface. NOT the active chip since card 66302. */
48
+ readonly raised: "#ffffff";
49
+ /** `--es-topbar-bg`. */
50
+ readonly topbarBg: "#ffffff";
51
+ /** `--es-frame-bg` — device "paper" behind the preview iframe. */
52
+ readonly frameBg: "#ffffff";
53
+ /** `--es-hover` — gray-100 row / icon hover. */
54
+ readonly hover: "#f3f4f6";
55
+ /** `--es-hatch` — 45° upload-slot texture. */
56
+ readonly hatch: "rgba(0, 0, 0, 0.018)";
57
+ /** `--es-addcard-veil` — add-section card hover wash. */
58
+ readonly addcardVeil: "rgba(248, 249, 250, 0.66)";
59
+ /** `--es-field-bg`. */
60
+ readonly fieldBg: "#ffffff";
61
+ /** `--es-track-bg` — gray-100 segmented / search track. */
62
+ readonly trackBg: "#f3f4f6";
63
+ /** `--es-input-border` — gray-300. */
64
+ readonly inputBorder: "#d1d5db";
65
+ /** `--es-field-text` — gray-800. */
66
+ readonly fieldText: "#1f2937";
67
+ /** `--es-field-placeholder` — gray-400. */
68
+ readonly fieldPlaceholder: "#9ca3af";
69
+ /** `--es-knob` — toggle knob. */
70
+ readonly knob: "#ffffff";
71
+ /** `--es-text` — gray-900. */
72
+ readonly text: "#111827";
73
+ /** `--es-text-strong` — gray-950 active labels. */
74
+ readonly textStrong: "#030712";
75
+ /** `--es-text-secondary` — gray-700. */
76
+ readonly textSecondary: "#374151";
77
+ /** `--es-muted` — gray-500. */
78
+ readonly muted: "#6b7280";
79
+ /** `--es-icon` — gray-500 icon buttons. */
80
+ readonly icon: "#6b7280";
81
+ /** `--es-disabled` — gray-400 disabled / faint glyphs. */
82
+ readonly disabled: "#9ca3af";
83
+ /** `--es-divider` — gray-200. */
84
+ readonly divider: "#e5e7eb";
85
+ /** `--es-line` — gray-200 hairlines. */
86
+ readonly line: "#e5e7eb";
87
+ /** `--es-btn-border` — gray-300. */
88
+ readonly btnBorder: "#d1d5db";
89
+ /** `--es-btn-text` — gray-700. */
90
+ readonly btnText: "#374151";
91
+ /** `--es-chip-bg` — gray-100. */
92
+ readonly chipBg: "#f3f4f6";
93
+ /** `--es-chip-fg` — gray-500. */
94
+ readonly chipFg: "#6b7280";
95
+ /** `--es-brand-square` — gray-900 wordmark square. */
96
+ readonly brandSquare: "#111827";
97
+ /** `--es-device-border` — gray-200 tablet/mobile frame. */
98
+ readonly deviceBorder: "#e5e7eb";
99
+ /** `--es-toast-bg` — gray-900 dark chip / seam pill. */
100
+ readonly toastBg: "#111827";
101
+ /** `--es-toggle-off` — gray-300 toggle track. */
102
+ readonly toggleOff: "#d1d5db";
103
+ /** `--es-scrollbar` — gray-300. */
104
+ readonly scrollbar: "#d1d5db";
105
+ /** `--es-scrollbar-hover` — gray-400. */
106
+ readonly scrollbarHover: "#9ca3af";
107
+ /** `--es-danger` — red-600. */
108
+ readonly danger: "#dc2626";
109
+ /** `--es-danger-strong` — red-700. */
110
+ readonly dangerStrong: "#b91c1c";
111
+ /** `--es-danger-tint` — red-50. */
112
+ readonly dangerTint: "#fef2f2";
113
+ /** `--es-warning` — amber-700. */
114
+ readonly warning: "#b45309";
115
+ /** `--es-preview-tint` — amber-100 "Preview" pill. */
116
+ readonly previewTint: "#fef3c7";
117
+ /**
118
+ * `--es-success` — green-700. THE ONE VALUE HERE WITH NO `--sf-*` BEHIND IT:
119
+ * the storefront editor's block has a red and an amber and no green, and card
120
+ * 8802.g needs one for a green tick. So it is picked to sit with the block
121
+ * rather than invented next to it — the same Tailwind ramp every other colour
122
+ * in this file comes from, at the 700 step `--es-warning` already sits on.
123
+ * 700 and not 600 for the reason amber went there: it is the darkest step
124
+ * still plainly the hue, and it clears WCAG AA (5.0:1) on the shell's white
125
+ * panels, so a tick may carry a label without needing a second token.
126
+ */
127
+ readonly success: "#15803d";
128
+ /** `--es-panel-radius`. */
129
+ readonly panelRadius: 12;
130
+ /** `--es-row-radius`. */
131
+ readonly rowRadius: 8;
132
+ /** `--es-field-radius`. */
133
+ readonly fieldRadius: 6;
134
+ /** `--es-panel-gap` — inter-pane gutter + outer frame padding, ONE token. */
135
+ readonly panelGap: 10;
136
+ /** `--es-panel-shadow`. */
137
+ readonly panelShadow: "0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)";
138
+ /** `--es-topbar-shadow`. */
139
+ readonly topbarShadow: "0 1px 3px rgba(0, 0, 0, 0.06)";
140
+ /**
141
+ * `--es-raised-shadow` — what a small surface sitting ON another one casts:
142
+ * the chip in the settings tab strip's track (card 66138). Tighter and darker
143
+ * than `panelShadow`, which is a whole panel floating over the app; this one
144
+ * has to read across 3px of track without looking like a second panel.
145
+ */
146
+ readonly raisedShadow: "0 1px 2px rgba(16, 24, 40, 0.1)";
147
+ /** `--es-font` — system sans stack. */
148
+ readonly font: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif";
149
+ };
150
+ type ShellTokens = typeof shellTokens;
151
+ /**
152
+ * The CSS custom-property name behind each `shellTokens` key. `shell.css` sets
153
+ * these on `.es-editor`; override any of them on (or above) the editor root to
154
+ * re-theme without shipping new CSS. The `shell-tokens.test.ts` parity gate
155
+ * proves this map, `shellTokens` and `shell.css` never drift apart.
156
+ */
157
+ declare const shellCssVars: Record<keyof ShellTokens, string>;
158
+
159
+ export { type ShellTokens, shellCssVars, shellTokens };
@@ -0,0 +1 @@
1
+ export{shellCssVars,shellTokens}from"../chunk-FI23ALVJ.js";
@@ -0,0 +1,122 @@
1
+ /*
2
+ * efficient-easel — shell chrome tokens.
3
+ *
4
+ * The values here mirror `shellTokens` in tokens.ts one-for-one (both trace back
5
+ * to the storefront editor's `--sf-*` block — the visual reference). They are
6
+ * declared as custom properties ON `.es-editor`, not on `:root`, so importing
7
+ * this stylesheet themes ONLY an editor root and never leaks variables into the
8
+ * host page (the campaign designer renders INSIDE the admin DOM, so this must
9
+ * not touch the surrounding app).
10
+ *
11
+ * Both editors add the `es-editor` class to their chrome root and alias their
12
+ * own `--sf-*` / `--cd-*` names onto these — e.g. `--sf-accent: var(--es-accent)`
13
+ * — so the light values live ONCE, here. Re-theme by overriding any `--es-*` var
14
+ * on or above the editor root.
15
+ *
16
+ * LIGHT only: both editing surfaces always render light (no dark mode), so there
17
+ * is no dark counterpart block — do not add one.
18
+ *
19
+ *
20
+ * ⛔ CUSTOM PROPERTIES ONLY IN THIS BLOCK. Never a paint property.
21
+ *
22
+ * `.es-editor` is not this package's own element — it is the class each editor
23
+ * puts on ITS OWN chrome root, and the campaign designer puts it on a root that
24
+ * lives INSIDE the admin portal's DOM (card 66024 made that the mechanism:
25
+ * `CampaignDesigner.tsx` adds the class and `campaignBlocks.css` aliases its
26
+ * `--cd-*` onto these values). A custom property here is inert until something
27
+ * reads it. A `background`, a `padding`, a `font` — anything that PAINTS — is
28
+ * not: it applies to that root the moment this sheet is imported, in every
29
+ * editor that imports it, and in the admin portal's own document.
30
+ *
31
+ * ⚠️ This sheet is the one that matters most for that, because it is the one the
32
+ * admin portal actually imports (`CampaignDesigner.tsx`, since card 66024).
33
+ *
34
+ * Audited 2026-09-02 across every sheet here that opens an `.es-editor` block,
35
+ * and the honest number is zero:
36
+ *
37
+ * shell.css 67 declarations, 67 custom, 0 paint
38
+ * panel.css 11 declarations, 11 custom, 0 paint
39
+ * menu.css 7 declarations, 7 custom, 0 paint
40
+ *
41
+ * ⚠️ Nothing enforces this — a rule with no test behind it. If you are adding a
42
+ * declaration here, you are the check.
43
+ * Import once from a client entry / global stylesheet:
44
+ * import 'efficient-easel/shell.css';
45
+ */
46
+
47
+ .es-editor {
48
+ /* — accent (efficient-shell blue) — */
49
+ --es-accent: #2563eb;
50
+ --es-accent-tint: #eff6ff;
51
+ --es-accent-tint-strong: #dbeafe;
52
+ --es-accent-line: #bfdbfe;
53
+ --es-accent-text: #1d4ed8;
54
+ --es-accent-ring: rgba(37, 99, 235, 0.25);
55
+ --es-accent-shadow: rgba(37, 99, 235, 0.35);
56
+ --es-on-accent: #ffffff;
57
+
58
+ /* — surfaces (cool gray ramp) — */
59
+ --es-app-bg: #f9fafb;
60
+ --es-preview-bg: #f3f4f6;
61
+ --es-panel: #ffffff;
62
+ --es-raised: #ffffff;
63
+ --es-topbar-bg: #ffffff;
64
+ --es-frame-bg: #ffffff;
65
+ --es-hover: #f3f4f6;
66
+ --es-hatch: rgba(0, 0, 0, 0.018);
67
+ --es-addcard-veil: rgba(248, 249, 250, 0.66);
68
+
69
+ /* — fields — */
70
+ --es-field-bg: #ffffff;
71
+ --es-track-bg: #f3f4f6;
72
+ --es-input-border: #d1d5db;
73
+ --es-field-text: #1f2937;
74
+ --es-field-placeholder: #9ca3af;
75
+ --es-knob: #ffffff;
76
+
77
+ /* — text — */
78
+ --es-text: #111827;
79
+ --es-text-strong: #030712;
80
+ --es-text-secondary: #374151;
81
+ --es-muted: #6b7280;
82
+ --es-icon: #6b7280;
83
+ --es-disabled: #9ca3af;
84
+
85
+ /* — lines / chips / buttons — */
86
+ --es-divider: #e5e7eb;
87
+ --es-line: #e5e7eb;
88
+ --es-btn-border: #d1d5db;
89
+ --es-btn-text: #374151;
90
+ --es-chip-bg: #f3f4f6;
91
+ --es-chip-fg: #6b7280;
92
+
93
+ /* — named chrome literals — */
94
+ --es-brand-square: #111827;
95
+ --es-device-border: #e5e7eb;
96
+ --es-toast-bg: #111827;
97
+ --es-toggle-off: #d1d5db;
98
+ --es-scrollbar: #d1d5db;
99
+ --es-scrollbar-hover: #9ca3af;
100
+
101
+ /* — danger / warning / success (shell red / amber / green) — */
102
+ --es-danger: #dc2626;
103
+ --es-danger-strong: #b91c1c;
104
+ --es-danger-tint: #fef2f2;
105
+ --es-warning: #b45309;
106
+ --es-preview-tint: #fef3c7;
107
+ --es-success: #15803d;
108
+
109
+ /* — radii / shape / gap — */
110
+ --es-panel-radius: 12px;
111
+ --es-row-radius: 8px;
112
+ --es-field-radius: 6px;
113
+ --es-panel-gap: 10px;
114
+
115
+ /* — shadows — */
116
+ --es-panel-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06);
117
+ --es-topbar-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
118
+ --es-raised-shadow: 0 1px 2px rgba(16, 24, 40, 0.1);
119
+
120
+ /* — type — */
121
+ --es-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
122
+ }