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,141 @@
1
+ import { S as SaveWords } from '../words-CsRWXkY4.js';
2
+ import { N as NoticeWords, C as ChromeWords } from '../words-DagJdwIA.js';
3
+ import { C as CopyWords } from '../words-DfxEp_RH.js';
4
+ import { D as DragWords } from '../words-D9ChvbNC.js';
5
+ import { LockWords } from '../edit-lock/index.js';
6
+ import { F as FieldsWords } from '../words-CD3WADyb.js';
7
+ import { N as NodeMenuWords } from '../words-DOhF-4D4.js';
8
+ import { S as SettingsBucket } from '../buckets-1vRlQceY.js';
9
+ import { P as PickWords } from '../words-B87GIFWH.js';
10
+ import { S as StyleClipboardWords } from '../words-B2YLaKEr.js';
11
+ import { T as TreeWords } from '../words-BK-npta-.js';
12
+ import '../types-CuNiSnX6.js';
13
+ import '../types-DNrn_w4V.js';
14
+ import '../wordFit-9B9nqyf3.js';
15
+ import 'react';
16
+ import '../markup/index.js';
17
+
18
+ /**
19
+ * The editor canvas's own words (`efficient-easel/strings`, 0.95.0). There is
20
+ * one: what the frame is called when the host does not name it (`title`).
21
+ *
22
+ * A plain module with no import.
23
+ */
24
+ type CanvasWords = {
25
+ /** The canvas frame's accessible name. */
26
+ frame: string;
27
+ };
28
+
29
+ /**
30
+ * The settings panel's own words (`efficient-easel/strings`, 0.95.0): what
31
+ * the tab strip is called, and what each tab SAYS.
32
+ *
33
+ * A bucket's key (`'Content'`, `'Style'`, …) is an identifier — fields name
34
+ * their tab with it and `SETTINGS_BUCKETS` orders them — so it never changes
35
+ * with the language. `buckets` is the word drawn for each key.
36
+ *
37
+ * The panel is hook-free, so nothing here is read from the strings provider:
38
+ * `SettingsTabs` takes its name as `ariaLabel` and each tab's word as the
39
+ * item's `label`, and `Inspector` (which has hooks) passes both.
40
+ */
41
+
42
+ type PanelWords = {
43
+ /** The tab strip's accessible name. */
44
+ settings: string;
45
+ /** The word each tab shows, by its key. */
46
+ buckets: Record<SettingsBucket, string>;
47
+ };
48
+
49
+ /**
50
+ * The rail's own words. There is one: what the toolbar is called when the host
51
+ * does not name it (`ariaLabel`). Every other word on the rail is an item's
52
+ * `label`, and those are the host's.
53
+ *
54
+ * A plain module with no import, so the rail stays the leaf it is
55
+ * (`tests/rail-leaf-safety.test.tsx`). It is NOT exported from `./index`: the
56
+ * rail's runtime exports are pinned (`tests/rail-public-shape.test.tsx`), and a
57
+ * host reads these through `efficient-easel/strings`.
58
+ */
59
+ type RailWords = {
60
+ /** The toolbar's accessible name. */
61
+ label: string;
62
+ };
63
+
64
+ /**
65
+ * THE ENGINE'S OWN WORDS, as one catalogue a host can translate (0.95.0).
66
+ *
67
+ * Every word and sentence this package itself draws or returns — visible text,
68
+ * `aria-label`s, tooltips, placeholders — is an entry here, grouped by the
69
+ * module that says it. What a HOST supplies (a block's name, a field's label, a
70
+ * tile's caption, the document's noun) was never the engine's and is not here.
71
+ *
72
+ * - A plain word is a `string`.
73
+ * - Anything with a value in it, or a count that can be one or many, is a
74
+ * function: the host puts the value where its language wants it and does
75
+ * its own plural.
76
+ *
77
+ * ── Where the English lives ───────────────────────────────────────────────
78
+ * In each module's own `words.ts`, NOT here. A leaf may only import its own
79
+ * lane (`tests/*-leaf-safety`), and several are held to a bundle that never
80
+ * spells a browser global — while one sentence below does ("another tab or
81
+ * window"). So a module owns its group and its defaults, and this file only
82
+ * puts the groups side by side. `DEFAULT_EDITOR_STRINGS.pick` IS the pickers'
83
+ * own default object, not a copy of it.
84
+ *
85
+ * ── Framework-free ────────────────────────────────────────────────────────
86
+ * No React, no browser global, nothing run at import but object literals
87
+ * (`tests/strings-leaf-safety.test.ts`). A server-side test in a host imports
88
+ * this to check its own catalogues against the engine's keys; it is published
89
+ * on its own as `efficient-easel/strings/catalogue`. The provider and the
90
+ * hooks are in ./provider.
91
+ */
92
+
93
+ /** Every word the engine says, by the module that says it. */
94
+ interface EditorStrings {
95
+ /** `efficient-easel/chrome` — `EditNotice`: who holds the document, a refused save. */
96
+ notice: NoticeWords;
97
+ /** `efficient-easel/edit-lock` — "2 minutes ago", "Someone else". */
98
+ lock: LockWords;
99
+ /** `efficient-easel/autosave` — the save pill. */
100
+ save: SaveWords;
101
+ /** `efficient-easel/chrome` — the device switch, "up one level", the panel grip. */
102
+ chrome: ChromeWords;
103
+ /** `efficient-easel/rail` — the toolbar's name. */
104
+ rail: RailWords;
105
+ /** `efficient-easel/tree` — the drag hint and the caret. */
106
+ tree: TreeWords;
107
+ /** `efficient-easel/panel` — the tab strip's name and each tab's word. */
108
+ panel: PanelWords;
109
+ /** `efficient-easel/fields` — the number box, the dropdown, the list field. */
110
+ fields: FieldsWords;
111
+ /** `efficient-easel/pick` — the colour row and the image box. */
112
+ pick: PickWords;
113
+ /** `efficient-easel/drag` — the Add panel and the canvas layer. */
114
+ drag: DragWords;
115
+ /** `efficient-easel/node-ops` — the right-click rows. */
116
+ menu: NodeMenuWords;
117
+ /** `efficient-easel/style-clipboard` — Copy style, Paste style and what a paste says. */
118
+ styleClipboard: StyleClipboardWords;
119
+ /** `efficient-easel/copy` — the formatting bar, alignment, the link window, each mark. */
120
+ copy: CopyWords;
121
+ /** `efficient-easel/editor` — the canvas frame's name. */
122
+ canvas: CanvasWords;
123
+ }
124
+ /** Today's English, word for word. */
125
+ declare const DEFAULT_EDITOR_STRINGS: EditorStrings;
126
+ /**
127
+ * A catalogue with any part left out. A function entry is taken whole or not
128
+ * at all: it has no "part" to leave out.
129
+ */
130
+ type DeepPartial<T> = T extends (...args: never[]) => unknown ? T : T extends object ? {
131
+ [K in keyof T]?: DeepPartial<T[K]>;
132
+ } : T;
133
+ /**
134
+ * `defaults` with every entry `partial` gives laid over it. Whatever `partial`
135
+ * leaves out (or gives as `undefined` / `null`) keeps the default, so a host
136
+ * that has translated half the catalogue shows the other half in English
137
+ * rather than as nothing. Neither argument is changed.
138
+ */
139
+ declare function mergeEditorStrings(defaults: EditorStrings, partial?: DeepPartial<EditorStrings> | null): EditorStrings;
140
+
141
+ export { type CanvasWords, ChromeWords, CopyWords, DEFAULT_EDITOR_STRINGS, type DeepPartial, DragWords, type EditorStrings, FieldsWords, LockWords, NodeMenuWords, NoticeWords, type PanelWords, PickWords, type RailWords, SaveWords, StyleClipboardWords, TreeWords, mergeEditorStrings };
@@ -0,0 +1 @@
1
+ export{DEFAULT_EDITOR_STRINGS,mergeEditorStrings}from"../chunk-OGZFDJ6G.js";import"../chunk-ZAOM6PFH.js";import"../chunk-6WY6ZXJG.js";import"../chunk-GGTPNGFJ.js";import"../chunk-SUO5627P.js";import"../chunk-H4B72XCJ.js";import"../chunk-7A7O2JWQ.js";import"../chunk-WK3AFPXY.js";import"../chunk-H6DDIBQ4.js";import"../chunk-KDIVA6WD.js";import"../chunk-RE4LP46F.js";import"../chunk-2OAZYSWJ.js";import"../chunk-2LUSK32B.js";import"../chunk-6OPSJGAI.js";import"../chunk-O2NLQBFX.js";
@@ -0,0 +1,56 @@
1
+ import { DeepPartial, EditorStrings } from './catalogue.js';
2
+ export { CanvasWords, DEFAULT_EDITOR_STRINGS, PanelWords, RailWords, mergeEditorStrings } from './catalogue.js';
3
+ import * as react from 'react';
4
+ import { ReactNode } from 'react';
5
+ export { C as ChromeWords, N as NoticeWords } from '../words-DagJdwIA.js';
6
+ export { C as CopyWords } from '../words-DfxEp_RH.js';
7
+ export { D as DragWords } from '../words-D9ChvbNC.js';
8
+ export { F as FieldsWords } from '../words-CD3WADyb.js';
9
+ export { LockWords } from '../edit-lock/index.js';
10
+ export { N as NodeMenuWords } from '../words-DOhF-4D4.js';
11
+ export { P as PickWords } from '../words-B87GIFWH.js';
12
+ export { S as SaveWords } from '../words-CsRWXkY4.js';
13
+ export { S as StyleClipboardWords } from '../words-B2YLaKEr.js';
14
+ export { T as TreeWords } from '../words-BK-npta-.js';
15
+ import '../buckets-1vRlQceY.js';
16
+ import '../markup/index.js';
17
+ import '../types-CuNiSnX6.js';
18
+ import '../types-DNrn_w4V.js';
19
+ import '../wordFit-9B9nqyf3.js';
20
+
21
+ interface EditorStringsProviderProps {
22
+ /**
23
+ * The host's words, laid over the ones in reach — the engine's English, or an
24
+ * outer provider's. Anything left out keeps what was there.
25
+ *
26
+ * Pass an object that keeps its identity between renders (a module constant,
27
+ * a `useMemo`): a new object every render is a new catalogue every render,
28
+ * and everything that reads it draws again.
29
+ */
30
+ value?: DeepPartial<EditorStrings>;
31
+ /**
32
+ * The language those words are in, as a BCP 47 tag (`'de'`, `'zh-Hant'`). The
33
+ * engine does not read it; `useEditorLocale()` hands it to a host's blocks and
34
+ * fields, so a number or a date drawn in the editor follows the same language.
35
+ * Left out: an outer provider's, else `'en'`.
36
+ */
37
+ locale?: string;
38
+ children?: ReactNode;
39
+ }
40
+ /**
41
+ * Puts a host's words in reach of every engine component under it that reads
42
+ * them. With no provider the engine speaks English, exactly as before 0.95.0.
43
+ *
44
+ * The hook-free leaves do NOT read it — the rail, the settings-panel
45
+ * primitives, the Layers tree, the pickers, the Add panel, the canvas layer —
46
+ * and neither do the plain functions (`editNotice`, `saveIndicator`,
47
+ * `nodeMenuItems`, …). Each takes its group as a prop or an argument instead;
48
+ * a host hands it `useEditorStrings()`'s.
49
+ */
50
+ declare function EditorStringsProvider({ value, locale, children }: EditorStringsProviderProps): react.JSX.Element;
51
+ /** The whole catalogue: the nearest provider's, or the engine's English. */
52
+ declare function useEditorStrings(): EditorStrings;
53
+ /** The nearest provider's locale, or `'en'`. */
54
+ declare function useEditorLocale(): string;
55
+
56
+ export { DeepPartial, EditorStrings, EditorStringsProvider, type EditorStringsProviderProps, useEditorLocale, useEditorStrings };
@@ -0,0 +1,2 @@
1
+ import{DEFAULT_EDITOR_STRINGS as r,mergeEditorStrings as n}from"../chunk-OGZFDJ6G.js";export{DEFAULT_EDITOR_STRINGS,mergeEditorStrings}from"../chunk-OGZFDJ6G.js";import"../chunk-ZAOM6PFH.js";import"../chunk-6WY6ZXJG.js";import"../chunk-GGTPNGFJ.js";import"../chunk-SUO5627P.js";import"../chunk-H4B72XCJ.js";import"../chunk-7A7O2JWQ.js";import"../chunk-WK3AFPXY.js";import"../chunk-H6DDIBQ4.js";import"../chunk-KDIVA6WD.js";import"../chunk-RE4LP46F.js";import"../chunk-2OAZYSWJ.js";import"../chunk-2LUSK32B.js";import"../chunk-6OPSJGAI.js";import{stringsContext as o}from"../chunk-MFIRYGHW.js";import"../chunk-O2NLQBFX.js";import{useContext as t,useMemo as i}from"react";import{jsx as c}from"react/jsx-runtime";function s({value:s,locale:u,children:m}){const e=o(),p=t(e),h=p?.strings??r,j=u??p?.locale??"en",k=i(()=>({strings:n(h,s),locale:j}),[h,s,j]);/* @__PURE__ */
2
+ return c(e.Provider,{value:k,children:m})}function u(){return t(o())?.strings??r}function m(){return t(o())?.locale??"en"}export{s as EditorStringsProvider,m as useEditorLocale,u as useEditorStrings};
@@ -0,0 +1,184 @@
1
+ import { E as EditorContextMenuItem } from '../types-pGBXevBU.js';
2
+ import { S as StyleClipboardWords } from '../words-B2YLaKEr.js';
3
+ import 'react';
4
+
5
+ /**
6
+ * `efficient-easel/style-clipboard` — COPY STYLE and PASTE STYLE, for both editors.
7
+ *
8
+ * Owner, 2026-09-19 (card [ADD] Copy Style And Paste Style): copy a component's
9
+ * look, paste it onto another component, in the campaign designer AND the
10
+ * website editor, written once. His three answers are the three rules here:
11
+ *
12
+ * 1. ONE copy. Everything that is the same in both editors lives in this file:
13
+ * the held style, the matching rule, the menu rows, the words a merchant
14
+ * reads after a paste, and the shortcut test. A host only says which of its
15
+ * rows are Style or Layout, and saves the new props through the one op it
16
+ * already has — so a paste is one undo step, and the shell dispatches
17
+ * nothing.
18
+ * 2. Paste onto ANY component, but only the settings the target also has. A
19
+ * setting is the same setting when the KEY and the KIND are the same — see
20
+ * {@link planPaste}. When we cannot tell, it is skipped.
21
+ * 3. Style and Layout, never Content. Which rows those are is the host's
22
+ * answer (its tabs are its own); this file never sees a Content row.
23
+ *
24
+ * WHAT IS HELD. One style at a time, tagged with the SCOPE it came from (a site,
25
+ * a campaign). In another scope nothing is held, so a look never crosses from
26
+ * one site to another or from the email editor to the website editor. It is
27
+ * kept in memory and in `sessionStorage`, so it lasts for the editor session
28
+ * and survives a page load inside the same tab. Storage can be missing or throw
29
+ * (a private window, blocked site data); every touch is in a try/catch and the
30
+ * memory copy still works.
31
+ *
32
+ * A LEAF. Nothing here runs at import: no `window`, no `document`, no storage.
33
+ * It imports React only for the two menu glyphs, and nothing outside `src/`
34
+ * (`tests/style-clipboard-leaf-safety.test.ts`).
35
+ */
36
+
37
+ /** Where a value is stored on a node's props: `['size']`, or `['mobile', 'size']`
38
+ * for the campaign editor's phone value of `size`. */
39
+ type StylePath = readonly string[];
40
+ /** One settings row, as the host sees it. */
41
+ interface StyleRow {
42
+ path: StylePath;
43
+ /**
44
+ * WHAT KIND of setting this is — the second half of "the same setting".
45
+ * `null` means the host cannot say, and such a row only matches on a
46
+ * component of the SAME type (where the same key is certainly the same
47
+ * field). Use {@link fieldKind} for a Puck field.
48
+ */
49
+ kind: string | null;
50
+ /**
51
+ * Counted in "Pasted N settings"? Default true. A row that only travels with
52
+ * another one — the campaign editor's phone value of a setting — passes
53
+ * false, so the number is the rows a merchant sees in the panel.
54
+ */
55
+ counted?: boolean;
56
+ }
57
+ /** The property a field factory sets to say what kind of setting it builds. */
58
+ declare const STYLE_KIND: "__styleKind";
59
+ /**
60
+ * Tag a field with its kind. Put it on a FACTORY (a colour picker, an
61
+ * alignment control), so every row the factory builds shares one kind — that
62
+ * is what lets Text `color` paste onto Divider `color`. Returns the same
63
+ * object, so a factory can `return withStyleKind({...}, 'color')`.
64
+ */
65
+ declare function withStyleKind<F extends object>(field: F, kind: string): F;
66
+ /**
67
+ * The kind of a Puck field.
68
+ *
69
+ * - a tagged field → its tag;
70
+ * - `text` / `textarea` / `number` → that word;
71
+ * - `select` / `radio` → the word plus its sorted option values, so two
72
+ * selects with different options are two different settings;
73
+ * - anything else (an untagged `custom`, an `object`, an `array`, a `slot`) →
74
+ * `null`: we cannot say, so it matches only on the same component type.
75
+ */
76
+ /**
77
+ * A CHOICE row's kind: the control's word, then the values it offers, sorted —
78
+ * `choice:center|left|right`. Both hosts build their own choice rows with this,
79
+ * so one spelling reaches {@link planPaste}'s value rule below.
80
+ */
81
+ declare function choiceKind(control: string, values: readonly unknown[]): string;
82
+ declare function fieldKind(field: unknown): string | null;
83
+ type Props = Record<string, unknown>;
84
+ /** A copied look. */
85
+ interface StyleSnapshot {
86
+ scope: string;
87
+ /** The source's component type — for the same-type rule of a `null` kind. */
88
+ sourceType: string;
89
+ /** The source's name, as the menu and the message say it: "Heading". */
90
+ sourceLabel: string;
91
+ rows: {
92
+ path: string[];
93
+ kind: string | null;
94
+ counted: boolean;
95
+ value: unknown;
96
+ }[];
97
+ }
98
+ /**
99
+ * Copy a node's look. `rows` are its Style and Layout rows (the host's answer);
100
+ * `props` are its stored props. Every row is held, set or empty — an empty row
101
+ * is what makes the target inherit too.
102
+ */
103
+ declare function copyStyle(input: {
104
+ scope: string;
105
+ type: string;
106
+ label: string;
107
+ rows: readonly StyleRow[];
108
+ props: Props;
109
+ }): StyleSnapshot;
110
+ /** The held look, or null when nothing is held IN THIS SCOPE. */
111
+ declare function heldStyle(scope: string): StyleSnapshot | null;
112
+ /** Hear about a new copy, so an open menu can redraw. Returns the unsubscribe. */
113
+ declare function subscribeStyle(listener: () => void): () => void;
114
+ /** Forget what is held. For tests, and for a host that must drop it. */
115
+ declare function clearStyle(): void;
116
+ interface PastePlan {
117
+ /** The target's new props. Hand them to the host's one replace op. */
118
+ props: Props;
119
+ /** How many counted rows matched — the number the merchant is told. */
120
+ pasted: number;
121
+ /** Did anything actually change? A host may skip a save that changes nothing. */
122
+ changed: boolean;
123
+ }
124
+ /**
125
+ * THE MATCHING RULE — the only copy of it.
126
+ *
127
+ * A held row is pasted onto a target row only when:
128
+ * 1. the PATH is the same (same key), and
129
+ * 2. the KIND is the same — or both rows are CHOICE rows of the same control
130
+ * and the target offers the value being pasted (see {@link matches}), and
131
+ * 3. that kind is known — or the two components are the same type, where the
132
+ * same key is certainly the same field.
133
+ *
134
+ * The target's rows are the rows its panel DRAWS right now, so a row the target
135
+ * has culled is not pasted either. An empty held value CLEARS the target row, so
136
+ * the target inherits exactly as the source did; nothing inherited is ever
137
+ * frozen into a value.
138
+ */
139
+ declare function planPaste(snapshot: StyleSnapshot, target: {
140
+ type: string;
141
+ rows: readonly StyleRow[];
142
+ props: Props;
143
+ }): PastePlan;
144
+ /** The words a merchant reads after a paste. One copy, so both editors say the
145
+ * same thing — including, plainly, when nothing matched. `words` is the host's
146
+ * language (`useEditorStrings().styleClipboard`, 0.95.0); left out, English. */
147
+ declare function pasteMessage(plan: Pick<PastePlan, 'pasted'>, sourceLabel: string, targetLabel: string, words?: StyleClipboardWords): string;
148
+ /** The two shortcuts, as a menu draws them. */
149
+ declare const COPY_STYLE_SHORTCUT = "\u2325\u2318C";
150
+ declare const PASTE_STYLE_SHORTCUT = "\u2325\u2318V";
151
+ /**
152
+ * The menu rows, in order: Copy style, then Paste style.
153
+ *
154
+ * A row that would do nothing is not drawn — both menus' written rule. So
155
+ * `Copy style` is left out when the node has no Style or Layout row
156
+ * (`canCopy: false`), and `Paste style` until something is held in this scope.
157
+ * The paste row names what it will paste: "Paste style from Heading".
158
+ */
159
+ declare function styleMenuItems(opts: {
160
+ held: StyleSnapshot | null;
161
+ canCopy: boolean;
162
+ onCopy: () => void;
163
+ onPaste: () => void;
164
+ /** The rows' words in the host's language (`useEditorStrings().styleClipboard`, 0.95.0). Left out, English. */
165
+ words?: StyleClipboardWords;
166
+ }): EditorContextMenuItem[];
167
+ /**
168
+ * Is this key press Copy style or Paste style?
169
+ *
170
+ * ⌘⌥C / ⌘⌥V on a Mac, Ctrl+Alt+C / Ctrl+Alt+V elsewhere — the keys Figma and
171
+ * Google Docs use. Read from `code`, not `key`: on a Mac, Option turns `c` into
172
+ * `ç`. A press inside something you type in is left alone: there the merchant
173
+ * is editing words, not choosing a component.
174
+ */
175
+ declare function styleShortcut(e: {
176
+ code: string;
177
+ metaKey: boolean;
178
+ ctrlKey: boolean;
179
+ altKey: boolean;
180
+ shiftKey: boolean;
181
+ target?: unknown;
182
+ }): 'copy' | 'paste' | null;
183
+
184
+ export { COPY_STYLE_SHORTCUT, PASTE_STYLE_SHORTCUT, type PastePlan, STYLE_KIND, StyleClipboardWords, type StylePath, type StyleRow, type StyleSnapshot, choiceKind, clearStyle, copyStyle, fieldKind, heldStyle, pasteMessage, planPaste, styleMenuItems, styleShortcut, subscribeStyle, withStyleKind };
@@ -0,0 +1 @@
1
+ import{DEFAULT_STYLE_CLIPBOARD_WORDS as t}from"../chunk-6WY6ZXJG.js";import{CopyStyleIcon as e,PasteStyleIcon as n}from"../chunk-YNZZYHA4.js";import{jsx as o}from"react/jsx-runtime";var r="__styleKind";function u(t,e){return t[r]=e,t}function c(t,e){return`${t}:${e.map(t=>String(t)).sort().join("|")}`}function i(t){if(!t)return null;const e=t.indexOf(":");if(e<0)return null;const n=t.slice(0,e);return s.has(n)?{control:n,values:t.slice(e+1).split("|")}:null}var s=/* @__PURE__ */new Set(["choice","select","radio"]);function l(t){if(!t||"object"!=typeof t)return null;const e=t;return"string"==typeof e[r]?e[r]:"text"===e.type||"textarea"===e.type||"number"===e.type?e.type:"select"!==e.type&&"radio"!==e.type||!Array.isArray(e.options)?null:c(e.type,e.options.map(t=>t?.value))}function a(t){return null!=t&&""!==t}function p(t,e){let n=t;for(const t of e){if(!n||"object"!=typeof n)return;n=n[t]}return n}function f(t){return void 0===t?t:JSON.parse(JSON.stringify(t))}function d(t,e,n){const[o,...r]=e,u={...t};if(0===r.length)return void 0===n?delete u[o]:u[o]=n,u;const c=u[o],i=d(c&&"object"==typeof c?c:{},r,n);return 0===Object.keys(i).length?delete u[o]:u[o]=i,u}var y=t=>t.join("."),h="editor-shell:style-clipboard",g=null,m=!1,v=/* @__PURE__ */new Set;function S(){try{return"undefined"==typeof sessionStorage?null:sessionStorage}catch{return null}}function b(){if(!m){m=!0;try{const t=S()?.getItem(h);t&&(g=JSON.parse(t))}catch{g=null}}}function k(t){b(),g={scope:t.scope,sourceType:t.type,sourceLabel:t.label,rows:t.rows.map(e=>{const n=p(t.props,e.path);return{path:[...e.path],kind:e.kind,counted:!1!==e.counted,value:a(n)?f(n):void 0}})};try{S()?.setItem(h,JSON.stringify(g))}catch{}return v.forEach(t=>t()),g}function N(t){return b(),g&&g.scope===t?g:null}function j(t){return v.add(t),()=>{v.delete(t)}}function C(){g=null,m=!0;try{S()?.removeItem(h)}catch{}v.forEach(t=>t())}function w(t,e,n){if(t.kind===e.kind)return null!==e.kind||n;const o=i(t.kind),r=i(e.kind);return!(!o||!r||o.control!==r.control)&&(!a(t.value)||r.values.includes(String(t.value)))}function O(t,e){const n=t.sourceType===e.type,o=new Map(t.rows.map(t=>[y(t.path),t]));let r=e.props,u=0,c=!1;for(const t of e.rows){const e=o.get(y(t.path));if(!e)continue;if(!w(e,t,n))continue;const i=p(r,t.path),s=void 0===e.value?void 0:f(e.value);JSON.stringify(a(i)?i:void 0)!==JSON.stringify(s)&&(r=d(r,t.path,s),c=!0),!1!==t.counted&&(u+=1)}return{props:r,pasted:u,changed:c}}function E(e,n,o,r=t){return 0===e.pasted?r.nothing({source:n,target:o}):r.pasted({count:e.pasted,source:n})}var J="⌥⌘C",K="⌥⌘V";function T(r){const u=r.words??t,c=[];return r.canCopy&&c.push({label:u.copy,icon:/* @__PURE__ */o(e,{}),shortcut:"⌥⌘C",onClick:r.onCopy}),r.held&&c.push({label:u.paste({source:r.held.sourceLabel}),icon:/* @__PURE__ */o(n,{}),shortcut:"⌥⌘V",onClick:r.onPaste}),c}function x(t){if(!t.altKey||t.shiftKey||!t.metaKey&&!t.ctrlKey)return null;const e=t.target;return e&&(e.isContentEditable||"INPUT"===e.tagName||"TEXTAREA"===e.tagName||"SELECT"===e.tagName)?null:"KeyC"===t.code?"copy":"KeyV"===t.code?"paste":null}export{J as COPY_STYLE_SHORTCUT,K as PASTE_STYLE_SHORTCUT,r as STYLE_KIND,c as choiceKind,C as clearStyle,k as copyStyle,l as fieldKind,N as heldStyle,E as pasteMessage,O as planPaste,T as styleMenuItems,x as styleShortcut,j as subscribeStyle,u as withStyleKind};
@@ -0,0 +1,173 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react';
3
+ import { T as TreeWords } from '../words-BK-npta-.js';
4
+
5
+ type LayersTreeProps = {
6
+ /** Extra classes — a host's scroll-bar class, for instance. */
7
+ className?: string;
8
+ children?: ReactNode;
9
+ /** Attributes for the tree's root — `efficient-easel/drag`'s `rootProps()`. */
10
+ rootProps?: HTMLAttributes<HTMLDivElement>;
11
+ };
12
+ type TreeGroupLabelProps = {
13
+ children: ReactNode;
14
+ };
15
+ type SubtreeProps = {
16
+ children?: ReactNode;
17
+ };
18
+ type TreeRowFold = {
19
+ /** Whether the row's subtree is showing. */
20
+ open: boolean;
21
+ /** What folds, in lower case — "section", "column". The caret reads "Collapse section". */
22
+ label: string;
23
+ onToggle: () => void;
24
+ };
25
+ type TreeRowProps = {
26
+ /** A 13px glyph for the row's type. */
27
+ icon: ReactNode;
28
+ /** The row's name — the type's label, or a name the person gave it. */
29
+ name: ReactNode;
30
+ /**
31
+ * The thing's own first words, drawn quieter after the name — "Text BACK IN
32
+ * STOCK NOW". NEW in 0.42.0: a tree of fifteen rows called "Section" cannot be
33
+ * searched by eye. The host decides the words and shortens them.
34
+ */
35
+ preview?: ReactNode;
36
+ selected?: boolean;
37
+ /** Hidden from the page: the name is struck through and the eye stays showing. */
38
+ hidden?: boolean;
39
+ /**
40
+ * The row can be picked up — whole, by `efficient-easel/drag` (0.74.0). It says
41
+ * so four ways: a faint ⋮ at its left on hover, the grab cursor, a hint after
42
+ * 0.7 s ("Drag to move · ⌥↑ ⌥↓"), and the row lifted with a shadow while it
43
+ * moves. A draggable row draws no `handle`: the row IS the handle.
44
+ */
45
+ draggable?: boolean;
46
+ /** The hint a draggable row shows after 0.7 s. Default `words.dragHint`: "Drag to move · ⌥↑ ⌥↓". */
47
+ dragHint?: string;
48
+ /** This row is being dragged. */
49
+ dragging?: boolean;
50
+ /** Where a drop would land relative to this row, while a drag is over it. */
51
+ drop?: 'before' | 'after' | 'into';
52
+ /** Present when the row folds; the row leads with its caret. Absent: the caret column is held open. */
53
+ fold?: TreeRowFold;
54
+ /**
55
+ * Props for the drag handle button (the host's drag-and-drop wiring). Absent:
56
+ * no handle. The handle is the ONLY drag source, so it is hidden with opacity
57
+ * and never made unreachable.
58
+ *
59
+ * @deprecated since 0.74.0 — a row driven by `efficient-easel/drag` is picked
60
+ * up whole (`draggable`), and a draggable row draws no handle. Kept until both
61
+ * editors have moved (blueprint 3.3 / 3.4).
62
+ */
63
+ handle?: ButtonHTMLAttributes<HTMLButtonElement>;
64
+ /** `TreeRowAction`s — laid over the END of the name, shown on hover and focus. */
65
+ actions?: ReactNode;
66
+ /** Stands in for the name — a rename box. It must not move the row by a pixel. */
67
+ nameSlot?: ReactNode;
68
+ /**
69
+ * Selects the row. Present: the name box becomes a real <button> — Enter and
70
+ * Space select from the keyboard — and a selected row says `aria-current`.
71
+ * Absent: the row selects through `rowProps.onClick`, as 0.42.0 did.
72
+ */
73
+ onSelect?: () => void;
74
+ /** Named state glyphs drawn right after the name — "Hidden on phones". */
75
+ marks?: ReadonlyArray<TreeRowMark>;
76
+ /** A count or position pill after the name — "1 of 2". */
77
+ badge?: ReactNode;
78
+ /** A non-interactive mark at the row's end — the Lock on a pinned section. */
79
+ trailing?: {
80
+ label: string;
81
+ icon: ReactNode;
82
+ };
83
+ /** Drawn inside the row after everything else: overlays such as a drop line. */
84
+ children?: ReactNode;
85
+ /** Attributes for the row itself: onClick, drag-over handlers, data-*. */
86
+ rowProps?: HTMLAttributes<HTMLDivElement> & Record<`data-${string}`, string | number | undefined>;
87
+ /**
88
+ * The row's own words — the drag hint and the caret's — in the host's
89
+ * language: `useEditorStrings().tree` (0.95.0). Left out, English. `dragHint`
90
+ * above still wins for its one line.
91
+ */
92
+ words?: TreeWords;
93
+ };
94
+ type TreeRowMark = {
95
+ key: string;
96
+ /** Tooltip and accessible name — the mark is a picture of a state. */
97
+ label: string;
98
+ icon: ReactNode;
99
+ };
100
+ type TreeRowActionProps = {
101
+ /**
102
+ * `delete` is the red trash; `eye` shows or hides; `plain` is any other action
103
+ * (a phone toggle, "Duplicate column") in the neutral colour.
104
+ */
105
+ kind: 'delete' | 'eye' | 'plain';
106
+ /** For a toggle: whether it is on. Adds aria-pressed and keeps it visible at rest. */
107
+ pressed?: boolean;
108
+ disabled?: boolean;
109
+ /** Tooltip and accessible name — "Delete Text", "Show Section". */
110
+ label: string;
111
+ onClick: () => void;
112
+ children: ReactNode;
113
+ };
114
+ type TreeSeamProps = {
115
+ /** A drag is over this gap and may land in it: draw the drop line. */
116
+ drop?: boolean;
117
+ /** Present: the gap offers a "+" that adds here (it opens the Add panel at this place). */
118
+ onAdd?: () => void;
119
+ /** The "+"'s accessible name and tooltip — "Add block here". Required with `onAdd`. */
120
+ label?: string;
121
+ /** The drag handlers — `efficient-easel/drag`'s `seam(…).seamProps`. */
122
+ seamProps?: HTMLAttributes<HTMLDivElement>;
123
+ };
124
+
125
+ /**
126
+ * The Layers tree's container. It declares the TREE's own rhythm — 10px above a
127
+ * group label, 6px between rows — under the shell's names, so the settings panel
128
+ * beside it keeps its own 48 / 38 (the owner picked the tree's two numbers by eye,
129
+ * 2026-08-31; they are not a scale of the panel's).
130
+ */
131
+ declare function LayersTree({ className, children, rootProps }: LayersTreeProps): react.JSX.Element;
132
+ /** A quiet uppercase label over a run of rows — "Header", "Body", "Footer". */
133
+ declare function TreeGroupLabel({ children }: TreeGroupLabelProps): react.JSX.Element;
134
+ /**
135
+ * The rows one level down. THE ONLY THING IN THE TREE THAT SAYS DEPTH: 16px of
136
+ * indent and a hairline down the left that runs the height of the group, so a
137
+ * child eight rows down still shows which parent it is under.
138
+ */
139
+ declare function Subtree({ children }: SubtreeProps): react.JSX.Element;
140
+
141
+ /** What a draggable row says after 0.7 s on it. */
142
+ declare const TREE_DRAG_HINT: string;
143
+ /**
144
+ * ONE row, at every depth (the owner's "One Ladder", 2026-09-04).
145
+ *
146
+ * Lifted from the website editor's `.sf-row` (efficient-shop). Left to right:
147
+ * the drag handle (out of flow, in the row's left padding), the caret — or a
148
+ * spacer holding its column open, so every glyph below lines up whether its row
149
+ * folds or not — the type glyph, then the name box. The actions are laid over
150
+ * the END of the name rather than holding space beside it; that choice won the
151
+ * name 138px → 223px of room in the website tree.
152
+ *
153
+ * Depth is said by `Subtree` and by nothing here. A row's look never changes
154
+ * with how deep it sits.
155
+ *
156
+ * Hook-free and controlled: selection, folding, hiding and dragging all belong
157
+ * to the host. So are its words: the drag hint and the caret's come in as
158
+ * `words` (./words), English when left out — a hook-free row reads no provider.
159
+ */
160
+ declare function TreeRow({ icon, name, preview, selected, hidden, draggable, dragHint, dragging, drop, fold, handle, actions, nameSlot, onSelect, marks, badge, trailing, children, rowProps, words, }: TreeRowProps): react.JSX.Element;
161
+ /**
162
+ * A 22px action over the end of a row's name — delete (red) or show / hide.
163
+ * Pressing it never selects the row underneath.
164
+ */
165
+ declare function TreeRowAction({ kind, label, onClick, pressed, disabled, children }: TreeRowActionProps): react.JSX.Element;
166
+ /**
167
+ * The gap between two rows, as a place (0.74.0): a drop between them lands here,
168
+ * and — when the host offers it — a "+" adds here. It takes exactly the space
169
+ * the gap already had, so a list reads the same with seams or without.
170
+ */
171
+ declare function TreeSeam({ drop, onAdd, label, seamProps }: TreeSeamProps): react.JSX.Element;
172
+
173
+ export { LayersTree, type LayersTreeProps, Subtree, type SubtreeProps, TREE_DRAG_HINT, TreeGroupLabel, type TreeGroupLabelProps, TreeRow, TreeRowAction, type TreeRowActionProps, type TreeRowFold, type TreeRowMark, type TreeRowProps, TreeSeam, type TreeSeamProps, TreeWords };
@@ -0,0 +1 @@
1
+ export{LayersTree,Subtree,TREE_DRAG_HINT,TreeGroupLabel,TreeRow,TreeRowAction,TreeSeam}from"../chunk-STFMZJKU.js";import"../chunk-H4B72XCJ.js";import"../chunk-YNZZYHA4.js";