efficient-easel 0.0.0 → 0.96.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 +723 -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 +295 -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-6JDII7N3.js +1 -0
  17. package/dist/chunk-6OPSJGAI.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-EFTTSIMK.js +1 -0
  23. package/dist/chunk-FI23ALVJ.js +1 -0
  24. package/dist/chunk-FJE4IDBQ.js +1 -0
  25. package/dist/chunk-GGTPNGFJ.js +1 -0
  26. package/dist/chunk-H4B72XCJ.js +1 -0
  27. package/dist/chunk-H6DDIBQ4.js +1 -0
  28. package/dist/chunk-KDIVA6WD.js +1 -0
  29. package/dist/chunk-LPRQDDQO.js +1 -0
  30. package/dist/chunk-MCLBHENY.js +1 -0
  31. package/dist/chunk-MFIRYGHW.js +1 -0
  32. package/dist/chunk-NKB3KVAB.js +1 -0
  33. package/dist/chunk-O2NLQBFX.js +1 -0
  34. package/dist/chunk-OGZFDJ6G.js +1 -0
  35. package/dist/chunk-OWBO4WKA.js +0 -0
  36. package/dist/chunk-QB3BZIRA.js +5 -0
  37. package/dist/chunk-RE4LP46F.js +1 -0
  38. package/dist/chunk-STFMZJKU.js +14 -0
  39. package/dist/chunk-SUO5627P.js +1 -0
  40. package/dist/chunk-TDA4HH54.js +1 -0
  41. package/dist/chunk-WK3AFPXY.js +1 -0
  42. package/dist/chunk-YIMRTIBN.js +3 -0
  43. package/dist/chunk-YNZZYHA4.js +450 -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 +208 -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-DtNw2VuI.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-DpH8YQOA.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-CEqUFH2j.d.ts +129 -0
  102. package/dist/types-CuNiSnX6.d.ts +204 -0
  103. package/dist/types-DNrn_w4V.d.ts +88 -0
  104. package/dist/types-DziKCwsN.d.ts +105 -0
  105. package/dist/types-_Vayx8ov.d.ts +153 -0
  106. package/dist/types-pGBXevBU.d.ts +131 -0
  107. package/dist/useNodeOps-6IAej9jy.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,131 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+
3
+ /** One row of the menu.
4
+ *
5
+ * Carried over unchanged from `efficient-admin-portal`'s `RowContextMenuItem`
6
+ * (card 66039), because the campaign designer's registry is typed against it and
7
+ * what the menus OFFER is not this card's to move. A `label` of `'—'` renders a
8
+ * separator; `header: true` renders a caption, and the FIRST such caption names
9
+ * the menu for assistive tech. */
10
+ interface EditorContextMenuItem {
11
+ label: string;
12
+ onClick: () => void;
13
+ danger?: boolean;
14
+ header?: boolean;
15
+ /** A SUBMENU: this item opens a flyout of these instead of acting itself.
16
+ * One level only — a child carrying its own `children` is drawn as a plain
17
+ * item, which is all any caller has needed and keeps the flyout from becoming
18
+ * a tree nobody can navigate with a mouse. */
19
+ children?: EditorContextMenuItem[];
20
+ /** Draw a tick beside this item — for a submenu whose entries are STATES
21
+ * rather than actions, so it can say which one you are already on. */
22
+ checked?: boolean;
23
+ /**
24
+ * A glyph for the row, drawn before the label.
25
+ *
26
+ * ⚠️ A TYPE SLOT, NOT A LOOKUP. The box renders what it is handed and does
27
+ * not know what a glyph means — it must never import the icon set, or the
28
+ * leaf stops being a leaf (`tests/menu-leaf-safety.test.ts` (a) fails if it
29
+ * tries). Hosts pass an element from `efficient-easel/icons`, whose four
30
+ * menu glyphs — copy, paste, move-up, move-down — shipped in 0.30.0 for
31
+ * exactly this row.
32
+ *
33
+ * OMIT IT AND NOTHING IS DRAWN — not an empty span. That is what keeps the
34
+ * admin portal's three shipped surfaces byte-identical, and it is pinned by
35
+ * `tests/menu-layer.test.tsx` (g).
36
+ */
37
+ icon?: ReactNode;
38
+ /**
39
+ * The keyboard shortcut, drawn right-aligned after the label (the `.k`
40
+ * column of the approved drawing).
41
+ *
42
+ * Display text, not a binding — the box neither registers nor honours it;
43
+ * the host owns the key handling. `aria-hidden`, like the tick and the
44
+ * chevron: "Duplicate ⌘D" read aloud is worse than "Duplicate".
45
+ *
46
+ * OMIT IT AND NOTHING IS DRAWN. Same guard as `icon`.
47
+ */
48
+ shortcut?: string;
49
+ }
50
+ /**
51
+ * What the box is dressed in.
52
+ *
53
+ * ⚠️ A SKIN IS TOTAL. Pass one and it replaces {@link ES_MENU_SKIN} outright —
54
+ * nothing from the default is merged in underneath. That is not tidiness, it is
55
+ * the only arrangement that keeps the admin portal's three shipped render sites
56
+ * looking the way they do:
57
+ *
58
+ * · their look is Tailwind classes, and Tailwind v4 scans the project root and
59
+ * NOT `node_modules` (`efficient-admin-portal/src/index.css:13-22` says so in
60
+ * its own words, and adds `@source` lines for efficient-shell because of it);
61
+ * · so those class strings have to keep living in that project's own `src/`,
62
+ * where its build still sees them — they cannot move into this package;
63
+ * · and a merge would append a class of ours to a caller's string, which is
64
+ * the one thing that stops the rendered markup being byte-identical.
65
+ *
66
+ * A slot left empty is left empty. The default fills all of them.
67
+ */
68
+ interface EditorContextMenuSkin {
69
+ /** The box. */
70
+ menu?: string;
71
+ /** The FLYOUT box. Falls back to {@link menu} when a skin gives only one —
72
+ * they are the same surface in the default look, but not everywhere: the
73
+ * admin portal's box carries `overflow-y-auto` (it takes a `maxHeight`) and
74
+ * its flyout does not. Collapsing the two would quietly change the markup of
75
+ * a surface already in front of users. */
76
+ menuSub?: string;
77
+ /** A plain row. */
78
+ item?: string;
79
+ /** A row that destroys something. */
80
+ itemDanger?: string;
81
+ /** The `'—'` separator. */
82
+ divider?: string;
83
+ /** A `header: true` caption. */
84
+ header?: string;
85
+ /** The label span of a row that has something beside it — a chevron, a tick.
86
+ * A row that is only a label renders bare text and takes no class at all,
87
+ * which is what the two grid callers have always drawn. */
88
+ label?: string;
89
+ /** The label span inside a FLYOUT. Falls back to {@link label} when a skin
90
+ * gives only one — the admin portal wants them different, because a state
91
+ * name in a flyout can be long enough to need truncating and a row in the box
92
+ * never is. */
93
+ labelSub?: string;
94
+ /** The glyph column, when a row carries an `icon`. */
95
+ icon?: string;
96
+ /** The right-aligned shortcut column, when a row carries a `shortcut`. */
97
+ shortcut?: string;
98
+ /** The `›` on a row that opens a flyout. */
99
+ chevron?: string;
100
+ /** The tick column of a flyout whose entries are states. */
101
+ tick?: string;
102
+ /** Merged into the box's own inline style, under the placement this component
103
+ * computes — so a caller can hand it a surface (the admin passes
104
+ * `glassStyle()` from efficient-shell) without being able to move it. */
105
+ surface?: CSSProperties;
106
+ /** Merged into each row's inline style. */
107
+ itemStyle?: CSSProperties;
108
+ }
109
+ interface EditorContextMenuProps {
110
+ /** Where the pointer was. The box is placed from here and then kept on
111
+ * screen — see the note on the component. */
112
+ x: number;
113
+ y: number;
114
+ items: EditorContextMenuItem[];
115
+ onClose: () => void;
116
+ /** Cap the box's height; it scrolls past that. */
117
+ maxHeight?: number;
118
+ /** Defaults to {@link ES_MENU_SKIN}, the storefront editor's approved look. */
119
+ skin?: EditorContextMenuSkin;
120
+ /**
121
+ * Where to portal to. Defaults to `document.body`.
122
+ *
123
+ * A host that OVERRIDES `--es-*` on its own editor root should pass that root
124
+ * instead: the default skin re-declares the token defaults on the box (see the
125
+ * component's note), which is right for a box hanging off `<body>` and wrong
126
+ * for one that should inherit a re-themed editor's values.
127
+ */
128
+ portalTo?: HTMLElement | null;
129
+ }
130
+
131
+ export type { EditorContextMenuItem as E, EditorContextMenuProps as a, EditorContextMenuSkin as b };
@@ -0,0 +1,68 @@
1
+ import { N as Node, S as SlotAddress, D as Data, a as DataConfig, P as Place } from './types-DziKCwsN.js';
2
+ import { N as NodeSelection, A as AfterRemove, S as StepBounds } from './plan-DtNw2VuI.js';
3
+
4
+ /**
5
+ * The node clipboard — what Copy and Cut hold, and Paste reads.
6
+ *
7
+ * In memory, one per editor (the host makes it once, at module scope, the way
8
+ * the storefront's NodeContextMenu kept a module-level `clipboard`), with
9
+ * subscribers so a menu that is open when something is copied can offer Paste
10
+ * straight away. Not the system clipboard, and not storage: a block is a JSON
11
+ * tree with ids that only mean something inside this document.
12
+ *
13
+ * Framework-free; ./useNodeOps reads it through useSyncExternalStore.
14
+ */
15
+
16
+ interface ClipboardEntry {
17
+ /** The block as it was copied — pasted through `copyNode`, so every paste gets fresh ids. */
18
+ node: Node;
19
+ /** What the menu calls it: "Paste ‹label›". */
20
+ label: string;
21
+ /** Where it was copied from. A host that only pastes into the same kind of
22
+ * list (the storefront: same zone) compares against this. */
23
+ from: SlotAddress;
24
+ }
25
+ interface NodeClipboard {
26
+ get(): ClipboardEntry | null;
27
+ set(entry: ClipboardEntry | null): void;
28
+ subscribe(listener: () => void): () => void;
29
+ }
30
+ declare function createNodeClipboard(): NodeClipboard;
31
+
32
+ interface NodeOpsHost {
33
+ data: Data;
34
+ config: DataConfig;
35
+ /** Write the next document and selection — ONCE. `select: undefined` keeps the selection. */
36
+ commit(data: Data, options: {
37
+ select: NodeSelection | undefined;
38
+ }): void;
39
+ readOnly?: boolean;
40
+ /** Applied to every copied node (duplicate, paste): e.g. release a baked image's id. */
41
+ strip?: (node: Node) => Node;
42
+ /** What a delete selects: nothing (default) or a neighbour. */
43
+ afterRemove?: AfterRemove;
44
+ /** The range a node may step within its list; default the whole list. */
45
+ stepBounds?: StepBounds;
46
+ /** Is a slot (a column) a level of its own for "up one level"? */
47
+ slotIsLevel?: (parent: Node, slot: string) => boolean;
48
+ clipboard?: NodeClipboard;
49
+ }
50
+ declare function useNodeOps(host: NodeOpsHost): {
51
+ /** What Copy / Cut last put on the clipboard. */
52
+ held: ClipboardEntry | null;
53
+ remove: (id: string) => boolean;
54
+ duplicate: (id: string) => boolean;
55
+ move: (id: string, to: Place) => boolean;
56
+ step: (id: string, dir: -1 | 1) => boolean;
57
+ canStep: (id: string, dir: -1 | 1) => boolean;
58
+ setProps: (id: string, patch: Record<string, unknown>) => boolean;
59
+ copy: (id: string, label: string) => boolean;
60
+ /** Copy, then delete — the delete is the one commit. */
61
+ cut: (id: string, label: string) => boolean;
62
+ paste: (at: Place) => boolean;
63
+ /** Where "up one level" goes from `selection`, or null when there is nothing above. */
64
+ parentOf: (selection: NodeSelection) => NodeSelection;
65
+ };
66
+ type NodeOps = ReturnType<typeof useNodeOps>;
67
+
68
+ export { type ClipboardEntry as C, type NodeClipboard as N, type NodeOps as a, type NodeOpsHost as b, createNodeClipboard as c, useNodeOps as u };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Finding the canvas frame and its document — ONCE, with no polling.
3
+ *
4
+ * Both editors draw the canvas inside an iframe (today Puck's
5
+ * `<iframe id="preview-frame">`; later the engine's own CanvasFrame). The frame
6
+ * gives no "ready" callback, its document arrives ASYNCHRONOUSLY (a srcdoc
7
+ * frame starts on an empty about:blank document and swaps to the real one on
8
+ * `load`), and the frame element itself can be replaced by a re-render. The
9
+ * apps answered that with eight separate setInterval / setTimeout loops, each
10
+ * with its own interval and its own idea of when to give up (blueprint §2 row
11
+ * 6). This is the one watcher they all read:
12
+ *
13
+ * - a MutationObserver on the editor's root notices the frame being added,
14
+ * removed or replaced;
15
+ * - the frame's own `load` event says its document was (re)placed;
16
+ * - `onChange` is called with the current `{ frame, document, window }` —
17
+ * or `null` — only when one of those actually changed.
18
+ *
19
+ * Pure DOM, no React (the hook in ./index wraps it). Nothing runs at import.
20
+ */
21
+ interface CanvasTarget {
22
+ frame: HTMLIFrameElement;
23
+ document: Document;
24
+ window: Window;
25
+ }
26
+ interface WatchOptions {
27
+ /** How the frame is found under `root`. Puck's canvas is the default. */
28
+ selector?: string;
29
+ /**
30
+ * Publish the frame only once its document holds an element matching this.
31
+ * A loaded frame can still be EMPTY — Puck portals the blocks in after
32
+ * `load` — and a measurement taken then measures nothing. For the editor's
33
+ * canvas pass `BLOCK_SELECTOR`. Watched with a MutationObserver on the frame's
34
+ * document, never polled.
35
+ */
36
+ readySelector?: string;
37
+ }
38
+ /**
39
+ * Every block the canvas draws carries this attribute. The markup is the one
40
+ * Puck 0.20 drew, kept by the engine's own canvas (`efficient-easel/editor`)
41
+ * because both apps' canvas CSS and checks read it.
42
+ */
43
+ declare const BLOCK_SELECTOR = "[data-puck-component]";
44
+ /** The canvas frame: Puck 0.20's, and the engine's own `CanvasFrame` since 0.89.0. */
45
+ declare const CANVAS_SELECTOR = "iframe#preview-frame";
46
+ /**
47
+ * Is this frame's CURRENT document the real one, not the placeholder it starts
48
+ * with? A `srcdoc` frame is real once its document says `about:srcdoc`; a `src`
49
+ * frame once it has left the initial about:blank; either must have finished
50
+ * parsing (`readyState` past `loading`) so its `<body>` exists.
51
+ */
52
+ declare function isFrameReady(frame: HTMLIFrameElement): boolean;
53
+ /**
54
+ * Watch `root` for the canvas frame. Calls `onChange` once straight away with
55
+ * the current state, then again whenever the frame or its document changes.
56
+ * Returns a function that stops watching.
57
+ */
58
+ declare function watchCanvasFrame(root: Node, onChange: (target: CanvasTarget | null) => void, { selector, readySelector }?: WatchOptions): () => void;
59
+
60
+ export { BLOCK_SELECTOR as B, type CanvasTarget as C, type WatchOptions as W, CANVAS_SELECTOR as a, isFrameReady as i, watchCanvasFrame as w };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * WHERE A STRIP'S WORDS STOP FITTING — the one thing the shell cannot know
3
+ * about a strip whose words belong to the host, measured by whoever owns the
4
+ * words.
5
+ *
6
+ * Two strips use it, and they use ONE rule (`panel.css`, "word fit"):
7
+ *
8
+ * `SettingsSegmented` the host passes its measured `fit`;
9
+ * the top-bar rail `TopBarRail` measures the words in the browser and
10
+ * hands `EditorRail` the result.
11
+ *
12
+ * Both numbers are the width of TEXT ONLY, in the strip's own type, and neither
13
+ * includes any padding, glyph or gap: those are the strip's, and the sheet adds
14
+ * them. That split is the point — a card that re-pads a button moves every
15
+ * threshold with it, instead of leaving each consumer holding a number that
16
+ * went stale silently.
17
+ *
18
+ * AN OVER-ESTIMATE IS SAFE and an under-estimate is not: too large drops the
19
+ * words a little early, too small draws a button past the strip's edge.
20
+ */
21
+ interface WordFit {
22
+ /** The sum of every label's width, px. Decides where ALL the words go. */
23
+ labelsPx: number;
24
+ /**
25
+ * The WIDEST single label, px. Decides where the chosen one goes too.
26
+ *
27
+ * The widest and not the current one: the person may be on any option, and
28
+ * a strip that fits while "Bold" is chosen and overflows on "Semibold" is a
29
+ * strip that overflows. It also keeps the strip in one state while the
30
+ * person clicks along it — the state follows the width, not the click.
31
+ */
32
+ widestLabelPx: number;
33
+ }
34
+
35
+ export type { WordFit as W };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copy style and Paste style's own words (`efficient-easel/strings`, 0.95.0):
3
+ * the two menu rows and what a merchant reads after a paste. A component's
4
+ * name (`source`, `target`) is the host's.
5
+ *
6
+ * A plain module with no import. The style clipboard imports React's JSX
7
+ * runtime and nothing else (`tests/style-clipboard-leaf-safety.test.ts`), so it
8
+ * reads no provider: `styleMenuItems` and `pasteMessage` take these as an
9
+ * argument, and a host passes `useEditorStrings().styleClipboard`.
10
+ */
11
+ type StyleClipboardWords = {
12
+ /** The Copy style row. */
13
+ copy: string;
14
+ /** The Paste style row. `source` is what the held style was copied from. */
15
+ paste: (v: {
16
+ source: string;
17
+ }) => string;
18
+ /** After a paste where no setting matched. */
19
+ nothing: (v: {
20
+ source: string;
21
+ target: string;
22
+ }) => string;
23
+ /** After a paste. `count` is the settings a merchant sees in the panel, one or more. */
24
+ pasted: (v: {
25
+ count: number;
26
+ source: string;
27
+ }) => string;
28
+ };
29
+
30
+ export type { StyleClipboardWords as S };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The pickers' own words (`efficient-easel/strings`, 0.95.0). The row's name
3
+ * (`label`) and a theme colour's name are the host's.
4
+ *
5
+ * A plain module with no import, in the pickers' own folder: they may reach
6
+ * nothing else (`tests/pick-leaf-safety.test.ts`), so they do NOT read the
7
+ * strings provider. `ColourPicker` and `ImageBox` take their group as `words`,
8
+ * and a host passes `useEditorStrings().pick.colour` / `.pick.image`.
9
+ */
10
+ type PickWords = {
11
+ colour: {
12
+ /** What the row says while the setting has no colour. */
13
+ notSet: string;
14
+ /** The swatch's accessible name. `shown` is the hex, the caption or `notSet`. */
15
+ swatch: (v: {
16
+ label: string;
17
+ shown: string;
18
+ }) => string;
19
+ /** The accessible name of the box the swatch opens. */
20
+ dialog: (v: {
21
+ label: string;
22
+ }) => string;
23
+ /** The group of theme colours at the top of that box. */
24
+ theme: string;
25
+ /** A theme colour's tooltip. */
26
+ themeSwatchTitle: (v: {
27
+ name: string;
28
+ hex: string;
29
+ }) => string;
30
+ /** A theme colour's accessible name. */
31
+ themeSwatch: (v: {
32
+ name: string;
33
+ hex: string;
34
+ }) => string;
35
+ /** The square's accessible name. */
36
+ square: string;
37
+ /** The square's value, read out. Both are percentages: "40%". */
38
+ squareValue: (v: {
39
+ saturation: string;
40
+ brightness: string;
41
+ }) => string;
42
+ /** The strip's accessible name. */
43
+ hue: string;
44
+ /** The hex box's accessible name. */
45
+ hex: (v: {
46
+ label: string;
47
+ }) => string;
48
+ };
49
+ image: {
50
+ /** While the library is open. */
51
+ choosing: string;
52
+ /** The library threw. */
53
+ failed: string;
54
+ /** The empty box, for a picture. */
55
+ chooseImage: string;
56
+ /** The empty box, for a video. */
57
+ chooseVideo: string;
58
+ /** The empty box's accessible name. `placeholder` is the host's own words for the empty box, when it gave any. */
59
+ empty: (v: {
60
+ label: string;
61
+ kind: 'image' | 'video';
62
+ placeholder?: string;
63
+ }) => string;
64
+ /** The line under the empty box's words. */
65
+ hint: string;
66
+ /** The picture's (or the video's) accessible name, once its file has a name. */
67
+ named: (v: {
68
+ label: string;
69
+ name: string;
70
+ }) => string;
71
+ /** The Replace button. */
72
+ replace: string;
73
+ /** The Replace button's accessible name. */
74
+ replaceNamed: (v: {
75
+ label: string;
76
+ }) => string;
77
+ /** The Remove button. */
78
+ remove: string;
79
+ /** The Remove button's accessible name. */
80
+ removeNamed: (v: {
81
+ label: string;
82
+ }) => string;
83
+ };
84
+ };
85
+
86
+ export type { PickWords as P };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The Layers tree's own words (`efficient-easel/strings`, 0.95.0): the drag
3
+ * hint and the caret's. A row's name, its marks and its actions are the
4
+ * host's.
5
+ *
6
+ * The tree is hook-free, so it does not read the strings provider: `TreeRow`
7
+ * takes these as its `words` prop and a host passes
8
+ * `useEditorStrings().tree`.
9
+ */
10
+ type TreeWords = {
11
+ /** What a draggable row says after 0.7 s on it. */
12
+ dragHint: string;
13
+ /** The caret's tooltip while the row is open. */
14
+ collapse: string;
15
+ /** The caret's tooltip while the row is folded. */
16
+ expand: string;
17
+ /** The caret's accessible name while open. `label` is the host's word for what folds: "section". */
18
+ collapseNamed: (v: {
19
+ label: string;
20
+ }) => string;
21
+ /** The caret's accessible name while folded. */
22
+ expandNamed: (v: {
23
+ label: string;
24
+ }) => string;
25
+ };
26
+
27
+ export type { TreeWords as T };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The settings rows' own words (`efficient-easel/strings`, 0.95.0): the number
3
+ * box's two buttons, the dropdown's empty state and everything a list field
4
+ * says. A row's label, its options and its hint are the block's.
5
+ *
6
+ * A plain module with no import. `noun` is one item's word, lower case — the
7
+ * field's own (`noun: 'slide'`) or `list.item`.
8
+ */
9
+ type FieldsWords = {
10
+ /** `FieldNumber`: the − button. */
11
+ less: string;
12
+ /** `FieldNumber`: the + button. */
13
+ more: string;
14
+ /** `FieldSelect`: what the box says while nothing is chosen. */
15
+ choose: string;
16
+ /** `ListField` — an array field drawn as rows. */
17
+ list: {
18
+ /** One item's word when the field names none. */
19
+ item: string;
20
+ /** An item's name when it has none of its own: "Slide 2". `n` counts from 1. */
21
+ itemName: (v: {
22
+ noun: string;
23
+ n: number;
24
+ }) => string;
25
+ /** Where the open item sits: "2 of 5". */
26
+ position: (v: {
27
+ n: number;
28
+ total: number;
29
+ }) => string;
30
+ /** The open item's copy button. */
31
+ duplicate: string;
32
+ /** A row's copy action. */
33
+ duplicateItem: (v: {
34
+ noun: string;
35
+ }) => string;
36
+ /** A row's delete action, and the open item's delete button. */
37
+ deleteItem: (v: {
38
+ noun: string;
39
+ }) => string;
40
+ /** The button under the list. */
41
+ add: (v: {
42
+ noun: string;
43
+ }) => string;
44
+ /** The "+" between two rows. */
45
+ addHere: (v: {
46
+ noun: string;
47
+ }) => string;
48
+ };
49
+ };
50
+
51
+ export type { FieldsWords as F };
@@ -0,0 +1,129 @@
1
+ import { S as SaveTone } from './types-CuNiSnX6.js';
2
+
3
+ /** Time and timers, passed in so a test can run minutes in a millisecond. */
4
+ type AutosaveClock = {
5
+ now: () => number;
6
+ setTimeout: (fn: () => void, ms: number) => unknown;
7
+ clearTimeout: (handle: unknown) => void;
8
+ };
9
+ /** What an editor needs to draw about its saving. Replaced, never mutated. */
10
+ type AutosaveSnapshot = Readonly<{
11
+ /** An edit is not on the server yet (held, waiting, being written or failed). */
12
+ pending: boolean;
13
+ /** …and has not been for `unsavedAfterMs`: the one time to say so. */
14
+ unsaved: boolean;
15
+ /** The last save failed; the next try is on the backoff (or "Retry now"). */
16
+ failed: boolean;
17
+ /** Failed saves in a row. Back to 0 once one lands. */
18
+ attempt: number;
19
+ /** A save has landed since this editor opened (the pill's green dot). */
20
+ saved: boolean;
21
+ /** The operator paused autosave: edits are held, not written. */
22
+ paused: boolean;
23
+ /** A save was refused (`stop()`): nothing more is written. */
24
+ stopped: boolean;
25
+ }>;
26
+ type AutosaveOptions<T> = {
27
+ /** Write one document. A rejection is a failed save and starts the backoff.
28
+ * Optional so a host can create the autosave first and install the save
29
+ * later (`setSave`) — a React host's save closes over props that change. */
30
+ save?: (value: T) => Promise<void>;
31
+ /** Told about each failed save, with its 0-based number in the current run —
32
+ * a host warns on 0 only, not once per retry. Not called for a save that
33
+ * `stop()` was called during: that one is the host's own to report. */
34
+ onFailure?: (error: unknown, attempt: number) => void;
35
+ debounceMs?: number;
36
+ maxWaitMs?: number;
37
+ unsavedAfterMs?: number;
38
+ retryDelays?: readonly number[];
39
+ clock?: AutosaveClock;
40
+ };
41
+ type Autosave<T> = {
42
+ /** Swap in the current save. The autosave is built ONCE (it holds the edit). */
43
+ setSave: (save: (value: T) => Promise<void>) => void;
44
+ /** The newest edit. Replaces any earlier unsaved one: only the latest is worth writing. */
45
+ push: (value: T) => void;
46
+ /** Write everything now, skipping the wait — even while paused, because the
47
+ * operator (or the host on their behalf) asked. Resolves `true` when nothing
48
+ * is left unsaved, `false` when a save failed or the autosave is stopped. */
49
+ flush: () => Promise<boolean>;
50
+ /** "Retry now": the backoff starts again from its first step, and the save goes now. */
51
+ retryNow: () => Promise<boolean>;
52
+ /** Hold edits instead of writing them on a timer. */
53
+ pause: () => void;
54
+ /** Stop holding, and write what was held at once. */
55
+ resume: () => Promise<boolean>;
56
+ /** A save was refused: write nothing more, keep the edit. Not undone by `resume`. */
57
+ stop: () => void;
58
+ /** Start over for ANOTHER document, in the same editor: the pending edit is
59
+ * dropped (the host has already saved or discarded it), and a failure, a
60
+ * refusal and the saved dot go with it — they were about the old document.
61
+ * A save still in flight finishes, but its answer no longer touches anything.
62
+ * A pause stays: it is the operator's, not the document's. */
63
+ reset: () => void;
64
+ /** An edit is not on the server yet — what the close-tab warning reads. */
65
+ hasPending: () => boolean;
66
+ getSnapshot: () => AutosaveSnapshot;
67
+ /** Called when the snapshot changes. Returns the unsubscribe. */
68
+ subscribe: (listener: () => void) => () => void;
69
+ /** Schedule nothing more. Keeps the edit; `flush()` first if it should go. */
70
+ dispose: () => void;
71
+ };
72
+ declare function createAutosave<T>(options?: AutosaveOptions<T>): Autosave<T>;
73
+
74
+ /**
75
+ * What the save pill says, for both editors — ONE copy.
76
+ *
77
+ * There were three: the campaign designer's `saveStatusLabel`
78
+ * (efficient-admin-portal `src/components/email/autosaveStatus.ts`), the
79
+ * website editor's `autosaveStatusLabel` (efficient-shop
80
+ * `lib/editor/autosave-status.ts`, which said of the first "a MIRRORED copy …
81
+ * whether that pair should collapse into `efficient-easel` is called out
82
+ * rather than decided here") and the website chrome's own words for its
83
+ * autosave switch. This decides it.
84
+ *
85
+ * The rule they all kept, and keep here (card 66103): **the pill speaks only
86
+ * when it has something to say.** A save that lands is silence — the dot goes
87
+ * green, no word. An edit younger than five seconds is silence too: with a
88
+ * one-second save it is about to land, and saying "unsaved" at every keystroke
89
+ * would blink the bar once a second. What is left is worth a word each:
90
+ *
91
+ * - the work has been off the server for five seconds — "Unsaved changes";
92
+ * - a save failed and is being retried — "Save failed" (with "Retry now");
93
+ * - the operator paused autosave — "Auto-save off", and whether that is
94
+ * holding anything;
95
+ * - a save was refused — "Auto-save stopped", which must never read as a
96
+ * switch somebody flicked themselves.
97
+ *
98
+ * The tones are `efficient-easel/chrome`'s `SaveState` tones, so the result
99
+ * goes straight onto the pill: `<SaveState {...saveIndicator(state)} />`.
100
+ */
101
+
102
+ type SaveIndicator = {
103
+ tone: SaveTone;
104
+ label: string;
105
+ };
106
+ /**
107
+ * The pill's five things to say (`efficient-easel/strings`, 0.95.0). Kept in
108
+ * autosave's own folder, with no import: autosave may reach nothing else
109
+ * (`tests/autosave-leaf-safety.test.ts`).
110
+ */
111
+ type SaveWords = {
112
+ /** A save was refused: nothing will be written. */
113
+ stopped: string;
114
+ /** The operator paused autosave, and an edit is waiting. */
115
+ pausedUnsaved: string;
116
+ /** The operator paused autosave. */
117
+ paused: string;
118
+ /** A save failed and is being retried. */
119
+ failed: string;
120
+ /** The work has been off the server for five seconds. */
121
+ unsaved: string;
122
+ };
123
+ /**
124
+ * `words` is the host's language (`useEditorStrings().save`); left out, the
125
+ * pill speaks English as it always has.
126
+ */
127
+ declare function saveIndicator(state: AutosaveSnapshot, words?: SaveWords): SaveIndicator;
128
+
129
+ export { type Autosave as A, type SaveWords as S, type AutosaveSnapshot as a, type SaveIndicator as b, type AutosaveOptions as c, type AutosaveClock as d, createAutosave as e, saveIndicator as s };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Drag-and-drop's own words (`efficient-easel/strings`, 0.95.0): the Add
3
+ * panel's shell and the canvas layer's handle and "+" buttons. A tile's
4
+ * caption, a group's heading and a block's name are the host's.
5
+ *
6
+ * A plain module with no import, in drag's own folder: drag may reach only its
7
+ * lane (`tests/drag-leaf-safety.test.ts`), so `AddPanel` and `CanvasDragLayer`
8
+ * do NOT read the strings provider. They take their group as `words`, and a
9
+ * host passes `useEditorStrings().drag.add` / `.drag.canvas`.
10
+ */
11
+ type DragWords = {
12
+ add: {
13
+ /** The "×" beside "Adding to Column 2". */
14
+ stop: string;
15
+ /** The line under the title. */
16
+ hint: string;
17
+ /** The search field's name. */
18
+ search: string;
19
+ /** The search field's placeholder. `label` is the field's name. */
20
+ searchPlaceholder: (v: {
21
+ label: string;
22
+ }) => string;
23
+ /** The "×" in the search field. */
24
+ clearSearch: string;
25
+ /** Nothing matched. `query` is what was typed, trimmed. */
26
+ nothing: (v: {
27
+ query: string;
28
+ }) => string;
29
+ /** The line under it. */
30
+ tryAnother: string;
31
+ /** A tile's accessible name and tooltip. `label` is its caption. */
32
+ tile: (v: {
33
+ label: string;
34
+ }) => string;
35
+ };
36
+ canvas: {
37
+ /** The button in a gap between two sections. */
38
+ addSection: string;
39
+ /** The button in an empty slot. */
40
+ addBlock: string;
41
+ /** How either button is drawn. `label` is one of the two above, or the host's. */
42
+ addButton: (v: {
43
+ label: string;
44
+ }) => string;
45
+ /** The handle's tooltip. `label` is the block's name. */
46
+ handle: (v: {
47
+ label: string;
48
+ }) => string;
49
+ };
50
+ };
51
+
52
+ export type { DragWords as D };