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.
- package/LICENSE +10 -0
- package/README.md +723 -4
- package/dist/autosave/index.d.ts +101 -0
- package/dist/autosave/index.js +1 -0
- package/dist/blocks/index.d.ts +295 -0
- package/dist/blocks/index.js +0 -0
- package/dist/buckets-1vRlQceY.d.ts +13 -0
- package/dist/canvas/index.d.ts +194 -0
- package/dist/canvas/index.js +1 -0
- package/dist/chrome/chrome.css +570 -0
- package/dist/chrome/index.d.ts +219 -0
- package/dist/chrome/index.js +21 -0
- package/dist/chunk-2LUSK32B.js +1 -0
- package/dist/chunk-2OAZYSWJ.js +1 -0
- package/dist/chunk-67VZRQLG.js +13 -0
- package/dist/chunk-6JDII7N3.js +1 -0
- package/dist/chunk-6OPSJGAI.js +1 -0
- package/dist/chunk-6WY6ZXJG.js +1 -0
- package/dist/chunk-7A7O2JWQ.js +1 -0
- package/dist/chunk-BZ5ASXOL.js +1 -0
- package/dist/chunk-CERTFXCV.js +13 -0
- package/dist/chunk-EFTTSIMK.js +1 -0
- package/dist/chunk-FI23ALVJ.js +1 -0
- package/dist/chunk-FJE4IDBQ.js +1 -0
- package/dist/chunk-GGTPNGFJ.js +1 -0
- package/dist/chunk-H4B72XCJ.js +1 -0
- package/dist/chunk-H6DDIBQ4.js +1 -0
- package/dist/chunk-KDIVA6WD.js +1 -0
- package/dist/chunk-LPRQDDQO.js +1 -0
- package/dist/chunk-MCLBHENY.js +1 -0
- package/dist/chunk-MFIRYGHW.js +1 -0
- package/dist/chunk-NKB3KVAB.js +1 -0
- package/dist/chunk-O2NLQBFX.js +1 -0
- package/dist/chunk-OGZFDJ6G.js +1 -0
- package/dist/chunk-OWBO4WKA.js +0 -0
- package/dist/chunk-QB3BZIRA.js +5 -0
- package/dist/chunk-RE4LP46F.js +1 -0
- package/dist/chunk-STFMZJKU.js +14 -0
- package/dist/chunk-SUO5627P.js +1 -0
- package/dist/chunk-TDA4HH54.js +1 -0
- package/dist/chunk-WK3AFPXY.js +1 -0
- package/dist/chunk-YIMRTIBN.js +3 -0
- package/dist/chunk-YNZZYHA4.js +450 -0
- package/dist/chunk-ZAOM6PFH.js +1 -0
- package/dist/chunk-ZNCEOSXO.js +1 -0
- package/dist/copy/copy.css +282 -0
- package/dist/copy/index.d.ts +336 -0
- package/dist/copy/index.js +10 -0
- package/dist/data/index.d.ts +208 -0
- package/dist/data/index.js +1 -0
- package/dist/drag/drag.css +360 -0
- package/dist/drag/index.d.ts +379 -0
- package/dist/drag/index.js +18 -0
- package/dist/edit-lock/index.d.ts +354 -0
- package/dist/edit-lock/index.js +1 -0
- package/dist/editor/editor.css +58 -0
- package/dist/editor/index.d.ts +361 -0
- package/dist/editor/index.js +9 -0
- package/dist/fields/fields.css +207 -0
- package/dist/fields/index.d.ts +273 -0
- package/dist/fields/index.js +46 -0
- package/dist/gold/gold.css +196 -0
- package/dist/gold/index.d.ts +280 -0
- package/dist/gold/index.js +1 -0
- package/dist/icons/index.d.ts +474 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -0
- package/dist/markup/index.d.ts +452 -0
- package/dist/markup/index.js +1 -0
- package/dist/menu/index.d.ts +49 -0
- package/dist/menu/index.js +15 -0
- package/dist/menu/menu.css +254 -0
- package/dist/node-ops/index.d.ts +91 -0
- package/dist/node-ops/index.js +1 -0
- package/dist/panel/index.d.ts +1088 -0
- package/dist/panel/index.js +16 -0
- package/dist/panel/panel.css +1460 -0
- package/dist/pick/index.d.ts +153 -0
- package/dist/pick/index.js +14 -0
- package/dist/pick/pick.css +235 -0
- package/dist/plan-DtNw2VuI.d.ts +88 -0
- package/dist/rail/index.d.ts +145 -0
- package/dist/rail/index.js +1 -0
- package/dist/rail/rail.css +175 -0
- package/dist/render/index.d.ts +117 -0
- package/dist/render/index.js +5 -0
- package/dist/resolve-DpH8YQOA.d.ts +57 -0
- package/dist/shell/index.d.ts +159 -0
- package/dist/shell/index.js +1 -0
- package/dist/shell/shell.css +122 -0
- package/dist/strings/catalogue.d.ts +141 -0
- package/dist/strings/catalogue.js +1 -0
- package/dist/strings/index.d.ts +56 -0
- package/dist/strings/index.js +2 -0
- package/dist/style-clipboard/index.d.ts +184 -0
- package/dist/style-clipboard/index.js +1 -0
- package/dist/tree/index.d.ts +173 -0
- package/dist/tree/index.js +1 -0
- package/dist/tree/tree.css +618 -0
- package/dist/types-CEqUFH2j.d.ts +129 -0
- package/dist/types-CuNiSnX6.d.ts +204 -0
- package/dist/types-DNrn_w4V.d.ts +88 -0
- package/dist/types-DziKCwsN.d.ts +105 -0
- package/dist/types-_Vayx8ov.d.ts +153 -0
- package/dist/types-pGBXevBU.d.ts +131 -0
- package/dist/useNodeOps-6IAej9jy.d.ts +68 -0
- package/dist/watch-DmasQA61.d.ts +60 -0
- package/dist/wordFit-9B9nqyf3.d.ts +35 -0
- package/dist/words-B2YLaKEr.d.ts +30 -0
- package/dist/words-B87GIFWH.d.ts +86 -0
- package/dist/words-BK-npta-.d.ts +27 -0
- package/dist/words-CD3WADyb.d.ts +51 -0
- package/dist/words-CsRWXkY4.d.ts +129 -0
- package/dist/words-D9ChvbNC.d.ts +52 -0
- package/dist/words-DOhF-4D4.d.ts +35 -0
- package/dist/words-DagJdwIA.d.ts +78 -0
- package/dist/words-DfxEp_RH.d.ts +142 -0
- package/package.json +202 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { S as SettingsBucket } from './buckets-1vRlQceY.js';
|
|
3
|
+
|
|
4
|
+
/** What every control shares: an id for its label, a name for screen readers, and a read-only switch. */
|
|
5
|
+
interface ControlBase {
|
|
6
|
+
id?: string;
|
|
7
|
+
readOnly?: boolean;
|
|
8
|
+
ariaLabel?: string;
|
|
9
|
+
ariaLabelledBy?: string;
|
|
10
|
+
}
|
|
11
|
+
interface FieldTextProps extends ControlBase {
|
|
12
|
+
value: string | undefined;
|
|
13
|
+
onChange: (value: string) => void;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
/** Several lines (a `<textarea>`); one line otherwise. */
|
|
16
|
+
multiline?: boolean;
|
|
17
|
+
rows?: number;
|
|
18
|
+
}
|
|
19
|
+
interface FieldNumberProps extends ControlBase {
|
|
20
|
+
value: number | undefined;
|
|
21
|
+
/** `undefined` when the box is emptied: the setting goes back to the block's own. */
|
|
22
|
+
onChange: (value: number | undefined) => void;
|
|
23
|
+
min?: number;
|
|
24
|
+
max?: number;
|
|
25
|
+
step?: number;
|
|
26
|
+
/** Shown after the box, e.g. `px`. */
|
|
27
|
+
unit?: string;
|
|
28
|
+
/** − and + beside the box. */
|
|
29
|
+
steppers?: boolean;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
}
|
|
32
|
+
interface FieldOption<V = unknown> {
|
|
33
|
+
label: string;
|
|
34
|
+
value: V;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
icon?: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
interface FieldSelectProps<V = unknown> extends ControlBase {
|
|
39
|
+
value: V | undefined;
|
|
40
|
+
options: readonly FieldOption<V>[];
|
|
41
|
+
onChange: (value: V) => void;
|
|
42
|
+
/** Shown when the value names no option. */
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
}
|
|
45
|
+
interface FieldMeta {
|
|
46
|
+
label?: string;
|
|
47
|
+
/** The tab this field opens; inherited by the fields after it (see above). */
|
|
48
|
+
bucket?: SettingsBucket;
|
|
49
|
+
/** Only says where a bucket starts. Draws nothing. */
|
|
50
|
+
marker?: boolean;
|
|
51
|
+
visible?: boolean;
|
|
52
|
+
/** Drawn before the label. */
|
|
53
|
+
icon?: ReactNode;
|
|
54
|
+
/** One quiet line under the control. */
|
|
55
|
+
hint?: ReactNode;
|
|
56
|
+
/** Anything else an app keeps on its fields rides along untouched. */
|
|
57
|
+
[extra: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
interface TextFieldDef extends FieldMeta {
|
|
60
|
+
type: 'text';
|
|
61
|
+
placeholder?: string;
|
|
62
|
+
}
|
|
63
|
+
interface TextareaFieldDef extends FieldMeta {
|
|
64
|
+
type: 'textarea';
|
|
65
|
+
placeholder?: string;
|
|
66
|
+
}
|
|
67
|
+
interface NumberFieldDef extends FieldMeta {
|
|
68
|
+
type: 'number';
|
|
69
|
+
min?: number;
|
|
70
|
+
max?: number;
|
|
71
|
+
step?: number;
|
|
72
|
+
placeholder?: string;
|
|
73
|
+
}
|
|
74
|
+
interface SelectFieldDef extends FieldMeta {
|
|
75
|
+
type: 'select';
|
|
76
|
+
options: readonly FieldOption[];
|
|
77
|
+
}
|
|
78
|
+
interface RadioFieldDef extends FieldMeta {
|
|
79
|
+
type: 'radio';
|
|
80
|
+
options: readonly FieldOption[];
|
|
81
|
+
}
|
|
82
|
+
interface ObjectFieldDef extends FieldMeta {
|
|
83
|
+
type: 'object';
|
|
84
|
+
objectFields: Record<string, InspectorFieldDef>;
|
|
85
|
+
}
|
|
86
|
+
interface ArrayFieldDef extends FieldMeta {
|
|
87
|
+
type: 'array';
|
|
88
|
+
arrayFields: Record<string, InspectorFieldDef>;
|
|
89
|
+
defaultItemProps?: Record<string, unknown>;
|
|
90
|
+
getItemSummary?: (item: Record<string, unknown>, index?: number) => ReactNode;
|
|
91
|
+
min?: number;
|
|
92
|
+
max?: number;
|
|
93
|
+
/** One item's word, lower case ("slide"): its rows and buttons say it. Default "item". */
|
|
94
|
+
noun?: string;
|
|
95
|
+
}
|
|
96
|
+
interface SlotFieldDef extends FieldMeta {
|
|
97
|
+
type: 'slot';
|
|
98
|
+
}
|
|
99
|
+
interface CustomFieldDef extends FieldMeta {
|
|
100
|
+
type: 'custom';
|
|
101
|
+
render: (props: CustomFieldRenderProps) => ReactNode;
|
|
102
|
+
}
|
|
103
|
+
type InspectorFieldDef = TextFieldDef | TextareaFieldDef | NumberFieldDef | SelectFieldDef | RadioFieldDef | ObjectFieldDef | ArrayFieldDef | SlotFieldDef | CustomFieldDef;
|
|
104
|
+
type InspectorFields = Record<string, InspectorFieldDef>;
|
|
105
|
+
/**
|
|
106
|
+
* What a custom render is called with: Puck's own six props, so every custom
|
|
107
|
+
* field both apps wrote keeps working unchanged, plus what the host hands in as
|
|
108
|
+
* `context` (the node, the root, the device — blueprint §3.1).
|
|
109
|
+
*/
|
|
110
|
+
interface CustomFieldRenderProps<V = any> {
|
|
111
|
+
field: CustomFieldDef;
|
|
112
|
+
name: string;
|
|
113
|
+
id: string;
|
|
114
|
+
value: V;
|
|
115
|
+
onChange: (value: V) => void;
|
|
116
|
+
readOnly?: boolean;
|
|
117
|
+
[context: string]: unknown;
|
|
118
|
+
}
|
|
119
|
+
/** A field type's own drawing, swapped in by the host (e.g. Puck's array until 4.5). */
|
|
120
|
+
type FieldRenderer = (props: {
|
|
121
|
+
field: InspectorFieldDef;
|
|
122
|
+
name: string;
|
|
123
|
+
id: string;
|
|
124
|
+
value: unknown;
|
|
125
|
+
onChange: (value: unknown) => void;
|
|
126
|
+
readOnly?: boolean;
|
|
127
|
+
}) => ReactNode;
|
|
128
|
+
|
|
129
|
+
export type { ArrayFieldDef as A, CustomFieldDef as C, FieldRenderer as F, InspectorFields as I, NumberFieldDef as N, ObjectFieldDef as O, RadioFieldDef as R, SelectFieldDef as S, TextFieldDef as T, InspectorFieldDef as a, FieldTextProps as b, FieldNumberProps as c, FieldSelectProps as d, FieldOption as e, CustomFieldRenderProps as f, SlotFieldDef as g, TextareaFieldDef as h };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { a as EditorRailItem } from './types-DNrn_w4V.js';
|
|
2
|
+
import { CSSProperties, ReactNode, Ref } from 'react';
|
|
3
|
+
import { EditAccess, SaveConflict } from './edit-lock/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The side-panel widths every editor opens on and drags between (px).
|
|
7
|
+
*
|
|
8
|
+
* Lifted from `efficient-shop/components/panel-widths.ts`, where the website
|
|
9
|
+
* editor measured them:
|
|
10
|
+
*
|
|
11
|
+
* RIGHT is 320, not 308 (card 8810). At 308 the settings tab strip was left
|
|
12
|
+
* 269px once the inspector's 14px sides and its 11px scrollbar were off it —
|
|
13
|
+
* one pixel under the 270 the strip's widest tier needs, so the tab words
|
|
14
|
+
* turned into glyphs at the width every merchant opens on.
|
|
15
|
+
*
|
|
16
|
+
* MIN_PANEL_W is load-bearing on the top bar: six 34px rail buttons are 204px,
|
|
17
|
+
* and at 220 the rail column spreads them with nothing to spare.
|
|
18
|
+
*
|
|
19
|
+
* The campaign designer opened at 288 / 288 with a 520 ceiling until the
|
|
20
|
+
* 2026-09-17 redesign. One set of numbers now, here.
|
|
21
|
+
*/
|
|
22
|
+
declare const DEFAULT_PANEL_W: {
|
|
23
|
+
readonly left: 288;
|
|
24
|
+
readonly right: 320;
|
|
25
|
+
};
|
|
26
|
+
declare const MIN_PANEL_W = 220;
|
|
27
|
+
declare const MAX_PANEL_W = 560;
|
|
28
|
+
/** Which side panel a width belongs to. */
|
|
29
|
+
type PanelSide = 'left' | 'right';
|
|
30
|
+
type PanelWidths = {
|
|
31
|
+
left: number;
|
|
32
|
+
right: number;
|
|
33
|
+
};
|
|
34
|
+
/** Clamp a width into the panel band and round to a whole pixel. */
|
|
35
|
+
declare function clampPanelWidth(width: number): number;
|
|
36
|
+
/**
|
|
37
|
+
* The inline style that carries the widths, as `--es-left-w` / `--es-right-w`.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ PUT IT ON THE EDITOR ROOT, the nearest ancestor of EVERYTHING that reads
|
|
40
|
+
* it. The website editor learned this on 2026-09-04: the vars sat on the panel
|
|
41
|
+
* row, the top bar is that row's SIBLING, and the rail column read a width with
|
|
42
|
+
* no declaration in scope — which is not an error, it silently falls back to
|
|
43
|
+
* `auto` and the column shrinks to its buttons.
|
|
44
|
+
*/
|
|
45
|
+
declare function panelWidthVars(widths: PanelWidths): CSSProperties;
|
|
46
|
+
|
|
47
|
+
type TopBarProps = {
|
|
48
|
+
children?: ReactNode;
|
|
49
|
+
};
|
|
50
|
+
type TopBarClusterProps = {
|
|
51
|
+
/**
|
|
52
|
+
* `rail` — the LEFT column: exactly the left panel's width (`--es-left-w`),
|
|
53
|
+
* carrying the panel's hairline up through the bar. Holds the
|
|
54
|
+
* `EditorRail` in `orientation="row"` and nothing else.
|
|
55
|
+
* `right` — the actions a person takes: save state, ghost buttons, the one
|
|
56
|
+
* primary button.
|
|
57
|
+
* omitted — the centre: undo / redo, a divider, the device switch, the panel
|
|
58
|
+
* toggles.
|
|
59
|
+
*/
|
|
60
|
+
kind?: 'rail' | 'right';
|
|
61
|
+
style?: CSSProperties;
|
|
62
|
+
children?: ReactNode;
|
|
63
|
+
};
|
|
64
|
+
type EditorIconButtonProps = {
|
|
65
|
+
/** Tooltip AND accessible name. An icon-only button has no other. */
|
|
66
|
+
label: string;
|
|
67
|
+
onClick?: () => void;
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* For a toggle (the panel toggles): `active` draws the glyph strong,
|
|
71
|
+
* `inactive` draws it in the disabled grey. Omit for a plain action.
|
|
72
|
+
*/
|
|
73
|
+
state?: 'active' | 'inactive';
|
|
74
|
+
buttonRef?: Ref<HTMLButtonElement>;
|
|
75
|
+
children: ReactNode;
|
|
76
|
+
};
|
|
77
|
+
type DeviceOption<Id extends string = string> = {
|
|
78
|
+
id: Id;
|
|
79
|
+
/** Tooltip and accessible name — the strip itself shows only glyphs. */
|
|
80
|
+
label: string;
|
|
81
|
+
/** A 15px glyph. Decorative: the button carries the name. */
|
|
82
|
+
icon: ReactNode;
|
|
83
|
+
};
|
|
84
|
+
type DeviceSwitchProps<Id extends string = string> = {
|
|
85
|
+
/** The devices this editor previews, in order. The website has three; an email has two. */
|
|
86
|
+
devices: ReadonlyArray<DeviceOption<Id>>;
|
|
87
|
+
value: Id;
|
|
88
|
+
/** Asked for a different device. Never called for the current one. */
|
|
89
|
+
onChange: (next: Id) => void;
|
|
90
|
+
/** The strip's own name. Defaults to "Preview device" (`EditorStrings.chrome.device`). */
|
|
91
|
+
ariaLabel?: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* What the save pill is saying.
|
|
95
|
+
*
|
|
96
|
+
* idle nothing changed yet — grey dot, no words
|
|
97
|
+
* saved a save landed — GREEN dot, no words (owner, 2026-09-09)
|
|
98
|
+
* saving a write is queued or going out — amber, with words
|
|
99
|
+
* error the last write failed — red, with words
|
|
100
|
+
* off autosave is paused by the person — boxed and muted
|
|
101
|
+
* off-unsaved paused AND an edit is waiting — boxed and amber
|
|
102
|
+
*/
|
|
103
|
+
type SaveTone = 'idle' | 'saved' | 'saving' | 'error' | 'off' | 'off-unsaved';
|
|
104
|
+
type SaveStateProps = {
|
|
105
|
+
tone: SaveTone;
|
|
106
|
+
/** The words. The editor owns them; an empty string draws the quiet dot alone. */
|
|
107
|
+
label: string;
|
|
108
|
+
/** Draw the emptying ring instead of the dot while a save waits. */
|
|
109
|
+
counting?: boolean;
|
|
110
|
+
/** How long the ring takes to empty — pass the editor's autosave debounce. */
|
|
111
|
+
waitMs?: number;
|
|
112
|
+
/**
|
|
113
|
+
* The ring node, so the host can restart the countdown on a keystroke
|
|
114
|
+
* without re-rendering (remove `is-counting`, read `offsetWidth`, add it back).
|
|
115
|
+
*/
|
|
116
|
+
ringRef?: Ref<HTMLSpanElement>;
|
|
117
|
+
/** `status` where the pill is the only word on the save (a read-only view). */
|
|
118
|
+
role?: 'status';
|
|
119
|
+
};
|
|
120
|
+
type SaveStateToggleProps = {
|
|
121
|
+
/** True while autosave is OFF — the switch is "pressed" into the paused state. */
|
|
122
|
+
pressed: boolean;
|
|
123
|
+
title: string;
|
|
124
|
+
onToggle: () => void;
|
|
125
|
+
children: ReactNode;
|
|
126
|
+
};
|
|
127
|
+
type PanelResizeHandleProps = {
|
|
128
|
+
/** `left` = the left panel's right edge; `right` = the right panel's left edge. */
|
|
129
|
+
side: PanelSide;
|
|
130
|
+
width: number;
|
|
131
|
+
min: number;
|
|
132
|
+
max: number;
|
|
133
|
+
defaultWidth: number;
|
|
134
|
+
/** What this grip resizes, for a screen reader. Required: a default can only lie. */
|
|
135
|
+
label: string;
|
|
136
|
+
/** An absolute width (drag, reset) or an updater (keyboard nudge, so a burst accumulates). */
|
|
137
|
+
onResize: (width: number | ((prev: number) => number)) => void;
|
|
138
|
+
};
|
|
139
|
+
type InspectorHeadProps = {
|
|
140
|
+
/** A 15px glyph for the selected thing's type. */
|
|
141
|
+
icon: ReactNode;
|
|
142
|
+
/** The selected thing's name. Rendered as the panel's `h2`. */
|
|
143
|
+
name: ReactNode;
|
|
144
|
+
/** Its kind — Section, Block, Page, Email… */
|
|
145
|
+
kind: ReactNode;
|
|
146
|
+
/** `InspectorIconButton`s, right-aligned. Omit when the thing has no actions. */
|
|
147
|
+
actions?: ReactNode;
|
|
148
|
+
};
|
|
149
|
+
type InspectorUpLevelProps = {
|
|
150
|
+
/** What the level above is called — the section, the column, the group. */
|
|
151
|
+
name: string;
|
|
152
|
+
/** Select the level above. */
|
|
153
|
+
onSelect: () => void;
|
|
154
|
+
/** The glyph before the name. The host's own until the icon set carries one. */
|
|
155
|
+
icon?: ReactNode;
|
|
156
|
+
/**
|
|
157
|
+
* The tooltip, and the start of the accessible name ("… to ‹name›"). Default
|
|
158
|
+
* "Go up one level" (`EditorStrings.chrome.upLevel`); how the two are joined
|
|
159
|
+
* is `EditorStrings.chrome.upLevelTo`.
|
|
160
|
+
*/
|
|
161
|
+
label?: string;
|
|
162
|
+
};
|
|
163
|
+
type InspectorIconButtonProps = {
|
|
164
|
+
/** Accessible name, and the tooltip unless `title` says something else. */
|
|
165
|
+
label: string;
|
|
166
|
+
/**
|
|
167
|
+
* A tooltip that differs from the name — the reason a disabled button is
|
|
168
|
+
* disabled ("Sections in the header cannot be duplicated"), while the name
|
|
169
|
+
* stays what the button is.
|
|
170
|
+
*/
|
|
171
|
+
title?: string;
|
|
172
|
+
/** For a button that opens a menu: announced as a menu button. */
|
|
173
|
+
hasPopup?: 'menu';
|
|
174
|
+
onClick?: () => void;
|
|
175
|
+
disabled?: boolean;
|
|
176
|
+
/** Delete and the like: tints red on hover. */
|
|
177
|
+
danger?: boolean;
|
|
178
|
+
/** For a button that opens a menu: tinted while the menu is open. */
|
|
179
|
+
expanded?: boolean;
|
|
180
|
+
buttonRef?: Ref<HTMLButtonElement>;
|
|
181
|
+
children: ReactNode;
|
|
182
|
+
};
|
|
183
|
+
type TopBarRailProps = {
|
|
184
|
+
/** The rail's buttons (`EditorRail` items). */
|
|
185
|
+
items: EditorRailItem[];
|
|
186
|
+
/** The toolbar's own name, e.g. "Left panel views". */
|
|
187
|
+
ariaLabel?: string;
|
|
188
|
+
};
|
|
189
|
+
type EditNoticeProps = {
|
|
190
|
+
/** Who may write the document now (`efficient-easel/edit-lock`). */
|
|
191
|
+
access: EditAccess;
|
|
192
|
+
/** A refused save, or null. Outranks the lock. */
|
|
193
|
+
conflict: SaveConflict | null;
|
|
194
|
+
/** What the document is called in a sentence: `'email'`, `'page'` — in the host's language, as the sentences are. */
|
|
195
|
+
noun: string;
|
|
196
|
+
/** Take the document: from "someone is editing", from read-only, or back after a take-over. */
|
|
197
|
+
onTakeOver: () => void;
|
|
198
|
+
/** Look without touching. */
|
|
199
|
+
onViewReadOnly: () => void;
|
|
200
|
+
/** Throw this screen away and load what is saved — the only action that discards work. */
|
|
201
|
+
onReload: () => void;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export { type DeviceSwitchProps as D, type EditorIconButtonProps as E, type InspectorHeadProps as I, MAX_PANEL_W as M, type PanelResizeHandleProps as P, type SaveTone as S, type TopBarProps as T, type TopBarClusterProps as a, type TopBarRailProps as b, type SaveStateProps as c, type SaveStateToggleProps as d, type InspectorIconButtonProps as e, type InspectorUpLevelProps as f, type EditNoticeProps as g, DEFAULT_PANEL_W as h, type DeviceOption as i, MIN_PANEL_W as j, type PanelSide as k, type PanelWidths as l, clampPanelWidth as m, panelWidthVars as p };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { W as WordFit } from './wordFit-9B9nqyf3.js';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One entry in the rail. A rail item is either a *view switch* (Pages, Layers,
|
|
6
|
+
* Styles, …) or an *action* (the quick-add "+"). The shape is the same for both;
|
|
7
|
+
* what tells them apart is `active`:
|
|
8
|
+
*
|
|
9
|
+
* - a view item passes `active: true | false` → it renders as a toggle
|
|
10
|
+
* (`aria-pressed`) and lights up when its surface is open;
|
|
11
|
+
* - an action item omits `active` → it renders as a plain button with no
|
|
12
|
+
* pressed state, because "Add" is a thing you *do*, not a thing you're *in*.
|
|
13
|
+
*
|
|
14
|
+
* The rail is purely presentational: it owns the LOOK, never the app state. The
|
|
15
|
+
* host decides which item is `active` and what each `onSelect` does.
|
|
16
|
+
*/
|
|
17
|
+
interface EditorRailItem {
|
|
18
|
+
/** Stable identity — used as the React key and to correlate selection. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Accessible name; becomes both the `aria-label` and the hover `title`. */
|
|
21
|
+
label: string;
|
|
22
|
+
/**
|
|
23
|
+
* The glyph. Pass one of the icons this package ships
|
|
24
|
+
* (`<PagesIcon />`, `<AddIcon />`, …) so every editor's rail draws the same
|
|
25
|
+
* set. Any 18×18, `currentColor`-stroked node works.
|
|
26
|
+
*/
|
|
27
|
+
icon: ReactNode;
|
|
28
|
+
/** Present ⇒ this is a view toggle and this is whether its surface is open. */
|
|
29
|
+
active?: boolean;
|
|
30
|
+
/** Dim + non-interactive. */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Click handler. Skipped while `disabled`. */
|
|
33
|
+
onSelect?: () => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Which way the rail runs.
|
|
37
|
+
*
|
|
38
|
+
* `'column'` — the original, and the default: a slim fixed-width strip down the
|
|
39
|
+
* side of the editor, wearing its own card (a background, a 12px radius and a
|
|
40
|
+
* drop shadow).
|
|
41
|
+
*
|
|
42
|
+
* `'row'` — the same buttons laid left to right, with NO fixed width and NO
|
|
43
|
+
* card, so the rail can sit in the middle of a top bar that already has its own
|
|
44
|
+
* surface. It exists because the column costs a 1280px laptop 56px of preview
|
|
45
|
+
* (46px of rail plus the 10px gap) that the top bar has going spare.
|
|
46
|
+
*
|
|
47
|
+
* Only the CONTAINER changes. Button size, icon size, the gap, and the hover
|
|
48
|
+
* and active look are the same in both directions — the row re-points three
|
|
49
|
+
* existing `--es-rail-*` custom properties and touches nothing else.
|
|
50
|
+
*/
|
|
51
|
+
type EditorRailOrientation = 'column' | 'row';
|
|
52
|
+
interface EditorRailProps {
|
|
53
|
+
/** The buttons, top to bottom, in render order (place the "+" where you want it). */
|
|
54
|
+
items: EditorRailItem[];
|
|
55
|
+
/** Names the toolbar for assistive tech. Defaults to `"Editor"` (`EditorStrings.rail.label`). */
|
|
56
|
+
ariaLabel?: string;
|
|
57
|
+
/** Extra class(es) merged onto the rail container. */
|
|
58
|
+
className?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Which way the buttons run. Defaults to `'column'`, which renders exactly
|
|
61
|
+
* what every editor draws today — adding this prop changed no existing
|
|
62
|
+
* output. Pass `'row'` to lay the rail into a top bar.
|
|
63
|
+
*/
|
|
64
|
+
orientation?: EditorRailOrientation;
|
|
65
|
+
/**
|
|
66
|
+
* A ROW rail's words, measured: the sum of every label's width and the widest
|
|
67
|
+
* one, in the rail's own type (`WordFit`). Given, every button carries its
|
|
68
|
+
* word in the markup and the stylesheet picks one of three states by the
|
|
69
|
+
* column's width — every word, the glyphs plus the active button's word, or
|
|
70
|
+
* glyphs only — the settings tabs' rule. The rail does not measure (it has
|
|
71
|
+
* no hooks, so a server component can render it); `TopBarRail`
|
|
72
|
+
* (efficient-easel/chrome) does, and passes this. Ignored on a column rail,
|
|
73
|
+
* which is one button wide. Omitted, the markup is exactly the icon-only rail.
|
|
74
|
+
*/
|
|
75
|
+
wordFit?: WordFit;
|
|
76
|
+
}
|
|
77
|
+
interface EditorRailButtonProps {
|
|
78
|
+
label: string;
|
|
79
|
+
/** Put the label beside the icon, for the stylesheet to show or clip (see `EditorRail`). */
|
|
80
|
+
worded?: boolean;
|
|
81
|
+
icon: ReactNode;
|
|
82
|
+
active?: boolean;
|
|
83
|
+
disabled?: boolean;
|
|
84
|
+
onSelect?: () => void;
|
|
85
|
+
className?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type { EditorRailButtonProps as E, EditorRailItem as a, EditorRailOrientation as b, EditorRailProps as c };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document, and the smallest slice of a block config the data layer reads.
|
|
3
|
+
*
|
|
4
|
+
* THE DOCUMENT IS PUCK'S, BYTE FOR BYTE. Every campaign and every page already
|
|
5
|
+
* saved is a Puck 0.20 `Data` object, and the backend reads it too (campaign
|
|
6
|
+
* copy, coupon deadlines, the assistant's knowledge tool, the media and
|
|
7
|
+
* neutralization manifests). So the engine keeps that exact shape — no version
|
|
8
|
+
* key, no wrapper, no rename — and nothing saved needs to migrate when Puck goes.
|
|
9
|
+
*
|
|
10
|
+
* { root: { props }, content: Node[], zones: {} }
|
|
11
|
+
*
|
|
12
|
+
* A node's children live INLINE in one of its props, under a field whose
|
|
13
|
+
* `type` is `"slot"`. `zones` is Puck's older DropZone store: neither editor
|
|
14
|
+
* writes it, so it is `{}` on every document, and it is kept that way on save.
|
|
15
|
+
*/
|
|
16
|
+
/** One block in the document. `props.id` is `<Type>-<uuid>`. */
|
|
17
|
+
interface Node {
|
|
18
|
+
type: string;
|
|
19
|
+
props: NodeProps;
|
|
20
|
+
/** Puck's per-prop read-only map. Carried through untouched. */
|
|
21
|
+
readOnly?: Record<string, boolean>;
|
|
22
|
+
}
|
|
23
|
+
interface NodeProps {
|
|
24
|
+
id: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
interface RootData {
|
|
28
|
+
props: Record<string, unknown>;
|
|
29
|
+
readOnly?: Record<string, boolean>;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
interface Data {
|
|
33
|
+
root: RootData;
|
|
34
|
+
content: Node[];
|
|
35
|
+
zones?: Record<string, Node[]>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Where a node sits: the parent that owns the list, the slot it is in, and its
|
|
39
|
+
* index in that list.
|
|
40
|
+
*
|
|
41
|
+
* The top-level list is `{ parentId: 'root', slot: 'default-zone' }`, and a
|
|
42
|
+
* slot is named by its PROP PATH (`col1`, `items[0].content`) — the same two
|
|
43
|
+
* halves as Puck's zone key `"<parentId>:<slot>"`, so an app that still speaks
|
|
44
|
+
* zone keys converts with `zoneKey` / `parseZoneKey`.
|
|
45
|
+
*/
|
|
46
|
+
interface SlotAddress {
|
|
47
|
+
parentId: string;
|
|
48
|
+
slot: string;
|
|
49
|
+
}
|
|
50
|
+
interface Place extends SlotAddress {
|
|
51
|
+
index: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The part of a field definition the data layer needs: which props are slots.
|
|
55
|
+
* A Puck `Field` satisfies it, so an app hands in the config it already has.
|
|
56
|
+
*/
|
|
57
|
+
interface FieldShape {
|
|
58
|
+
type: string;
|
|
59
|
+
objectFields?: Record<string, FieldShape>;
|
|
60
|
+
arrayFields?: Record<string, FieldShape>;
|
|
61
|
+
}
|
|
62
|
+
type FieldsShape = Record<string, FieldShape>;
|
|
63
|
+
/**
|
|
64
|
+
* `fields` and `defaultProps` are typed as ANY OBJECT on purpose. A Puck config
|
|
65
|
+
* typed with its blocks' own props (`Config<{ Heading: HeadingProps }>`) carries
|
|
66
|
+
* `Fields<HeadingProps>` and `HeadingProps`, which no shared shape can name, and
|
|
67
|
+
* an app must be able to hand in the config it has. The data layer reads only
|
|
68
|
+
* each field's `type` (and `objectFields` / `arrayFields`), through
|
|
69
|
+
* `fieldsFrom` / `defaultsFrom` in ./slots.
|
|
70
|
+
*/
|
|
71
|
+
interface BlockShape {
|
|
72
|
+
fields?: object;
|
|
73
|
+
defaultProps?: object;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The config as the data layer sees it. A Puck `Config` satisfies it: only
|
|
77
|
+
* `components[type].fields` / `.defaultProps` and `root.fields` /
|
|
78
|
+
* `.defaultProps` are ever read. A type missing from `components` has no slots
|
|
79
|
+
* and is kept verbatim — never dropped — so content can land before its code.
|
|
80
|
+
*/
|
|
81
|
+
interface DataConfig {
|
|
82
|
+
root?: BlockShape;
|
|
83
|
+
components: Record<string, BlockShape | undefined>;
|
|
84
|
+
}
|
|
85
|
+
/** A node's slot, as found by `slotsOf`. */
|
|
86
|
+
interface SlotEntry {
|
|
87
|
+
slot: string;
|
|
88
|
+
content: Node[];
|
|
89
|
+
}
|
|
90
|
+
interface Found {
|
|
91
|
+
node: Node;
|
|
92
|
+
place: Place;
|
|
93
|
+
/** The slots from the top-level list down to the one holding `node`. */
|
|
94
|
+
path: SlotAddress[];
|
|
95
|
+
}
|
|
96
|
+
interface WalkInfo {
|
|
97
|
+
place: Place;
|
|
98
|
+
path: SlotAddress[];
|
|
99
|
+
/** `null` for a top-level node. */
|
|
100
|
+
parent: Node | null;
|
|
101
|
+
/** 0 for a top-level node. */
|
|
102
|
+
depth: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type { BlockShape as B, Data as D, Found as F, Node as N, Place as P, RootData as R, SlotAddress as S, WalkInfo as W, DataConfig as a, SlotEntry as b, FieldShape as c, FieldsShape as d, NodeProps as e };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { DataInput } from './data/index.js';
|
|
2
|
+
import { I as ItemSelector, N as NodeSelection } from './plan-DtNw2VuI.js';
|
|
3
|
+
import { D as Data, S as SlotAddress, a as DataConfig, N as Node } from './types-DziKCwsN.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The editor's UI state, in Puck's shape so the apps' `appState.ui` reads move
|
|
7
|
+
* over unchanged. `itemSelector` and `selectedSlot` are DERIVED from the
|
|
8
|
+
* selection the store holds by id; every other key is the host's own and is
|
|
9
|
+
* kept as written.
|
|
10
|
+
*/
|
|
11
|
+
type EditorUi = Record<string, unknown> & {
|
|
12
|
+
itemSelector: ItemSelector | null;
|
|
13
|
+
/** The selected slot (a column), when a slot rather than a node is selected. */
|
|
14
|
+
selectedSlot: SlotAddress | null;
|
|
15
|
+
/** Puck's own UI keys, which a host's chrome reads and writes (`setUi`).
|
|
16
|
+
* They start at Puck's defaults (`DEFAULT_UI`). */
|
|
17
|
+
leftSideBarVisible: boolean;
|
|
18
|
+
rightSideBarVisible: boolean;
|
|
19
|
+
previewMode: 'edit' | 'interactive';
|
|
20
|
+
isDragging: boolean;
|
|
21
|
+
};
|
|
22
|
+
/** Puck's public `appState`. */
|
|
23
|
+
interface AppState {
|
|
24
|
+
data: Data;
|
|
25
|
+
ui: EditorUi;
|
|
26
|
+
}
|
|
27
|
+
type Recordable = {
|
|
28
|
+
recordHistory?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** What `setData` merges in: part of the document — in this package's shape,
|
|
31
|
+
* or in Puck's looser one (`root.props` optional), which the apps' documents
|
|
32
|
+
* are typed as and `<Puck>` accepted. */
|
|
33
|
+
type SetDataPatch = Partial<Data> | DataInput;
|
|
34
|
+
/**
|
|
35
|
+
* The actions the apps dispatch today — Puck 0.20's, with Puck's meaning. Zone
|
|
36
|
+
* keys are `"<parentId>:<slot>"`; `root:default-zone` is the page's own list.
|
|
37
|
+
*/
|
|
38
|
+
type EditorAction = ({
|
|
39
|
+
type: 'setData';
|
|
40
|
+
data: SetDataPatch | ((prev: Data) => SetDataPatch);
|
|
41
|
+
} & Recordable) | ({
|
|
42
|
+
type: 'setUi';
|
|
43
|
+
ui: Partial<EditorUi> | ((prev: EditorUi) => Partial<EditorUi>);
|
|
44
|
+
} & Recordable) | ({
|
|
45
|
+
type: 'set';
|
|
46
|
+
state: Partial<AppState> | ((prev: AppState) => Partial<AppState>);
|
|
47
|
+
} & Recordable) | ({
|
|
48
|
+
type: 'insert';
|
|
49
|
+
componentType: string;
|
|
50
|
+
destinationIndex: number;
|
|
51
|
+
destinationZone: string;
|
|
52
|
+
id?: string;
|
|
53
|
+
} & Recordable) | ({
|
|
54
|
+
type: 'replace';
|
|
55
|
+
destinationIndex: number;
|
|
56
|
+
destinationZone: string;
|
|
57
|
+
data: Node;
|
|
58
|
+
} & Recordable) | ({
|
|
59
|
+
type: 'replaceRoot';
|
|
60
|
+
root: {
|
|
61
|
+
props?: Record<string, unknown>;
|
|
62
|
+
readOnly?: Record<string, boolean>;
|
|
63
|
+
};
|
|
64
|
+
} & Recordable) | ({
|
|
65
|
+
type: 'duplicate';
|
|
66
|
+
sourceIndex: number;
|
|
67
|
+
sourceZone: string;
|
|
68
|
+
} & Recordable) | ({
|
|
69
|
+
type: 'reorder';
|
|
70
|
+
sourceIndex: number;
|
|
71
|
+
destinationIndex: number;
|
|
72
|
+
destinationZone: string;
|
|
73
|
+
} & Recordable) | ({
|
|
74
|
+
type: 'move';
|
|
75
|
+
sourceIndex: number;
|
|
76
|
+
sourceZone: string;
|
|
77
|
+
destinationIndex: number;
|
|
78
|
+
destinationZone: string;
|
|
79
|
+
} & Recordable) | ({
|
|
80
|
+
type: 'remove';
|
|
81
|
+
index: number;
|
|
82
|
+
zone: string;
|
|
83
|
+
} & Recordable);
|
|
84
|
+
/** What a block, the root or the whole editor allows. Puck's five. */
|
|
85
|
+
interface Permissions {
|
|
86
|
+
drag: boolean;
|
|
87
|
+
duplicate: boolean;
|
|
88
|
+
delete: boolean;
|
|
89
|
+
edit: boolean;
|
|
90
|
+
insert: boolean;
|
|
91
|
+
[key: string]: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** The config the store needs: the data layer's shape, plus each block's own
|
|
94
|
+
* `permissions` (Puck's per-component permissions). Rendering keys are the
|
|
95
|
+
* renderer's business and pass through untouched. */
|
|
96
|
+
interface EditorConfig extends DataConfig {
|
|
97
|
+
root?: DataConfig['root'] & {
|
|
98
|
+
permissions?: Partial<Permissions>;
|
|
99
|
+
};
|
|
100
|
+
components: Record<string, (NonNullable<DataConfig['components'][string]> & {
|
|
101
|
+
permissions?: Partial<Permissions>;
|
|
102
|
+
}) | undefined>;
|
|
103
|
+
}
|
|
104
|
+
/** The state the store holds. The selection is BY ID. */
|
|
105
|
+
interface EditorCore {
|
|
106
|
+
data: Data;
|
|
107
|
+
selection: NodeSelection;
|
|
108
|
+
/** The host's own UI keys — everything but `itemSelector` / `selectedSlot`. */
|
|
109
|
+
ui: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
/** One undo step: the whole state, as Puck's history holds the whole app state. */
|
|
112
|
+
interface HistoryEntry {
|
|
113
|
+
id: string;
|
|
114
|
+
state: AppState;
|
|
115
|
+
}
|
|
116
|
+
/** Puck's PUBLIC `history` (what `usePuck` hands out): `hasPast` and
|
|
117
|
+
* `hasFuture` are booleans there, read at the state they came with. */
|
|
118
|
+
interface EditorHistory {
|
|
119
|
+
back: () => void;
|
|
120
|
+
forward: () => void;
|
|
121
|
+
hasPast: boolean;
|
|
122
|
+
hasFuture: boolean;
|
|
123
|
+
histories: HistoryEntry[];
|
|
124
|
+
index: number;
|
|
125
|
+
/** Go straight to one step (Puck's `setHistoryIndex`). */
|
|
126
|
+
setHistoryIndex: (index: number) => void;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* What `useEditor` reads — Puck's `usePuck` state, so the apps' selectors move
|
|
130
|
+
* over by renaming the hook: `appState`, `selectedItem`, `dispatch`, `config`,
|
|
131
|
+
* `history`, `getPermissions`, `getItemById`, `getSelectorForId`. Plus the
|
|
132
|
+
* selection by id (`selection`), which Puck never had.
|
|
133
|
+
*/
|
|
134
|
+
interface EditorView<C extends EditorConfig = EditorConfig> {
|
|
135
|
+
appState: AppState;
|
|
136
|
+
selection: NodeSelection;
|
|
137
|
+
selectedItem: Node | null;
|
|
138
|
+
config: C;
|
|
139
|
+
dispatch: (action: EditorAction) => void;
|
|
140
|
+
history: EditorHistory;
|
|
141
|
+
getPermissions: (params?: {
|
|
142
|
+
item?: Node | null;
|
|
143
|
+
type?: string;
|
|
144
|
+
root?: boolean;
|
|
145
|
+
}) => Permissions;
|
|
146
|
+
getItemById: (id: string) => Node | undefined;
|
|
147
|
+
getItemBySelector: (selector: ItemSelector) => Node | undefined;
|
|
148
|
+
/** Puck's: the node's place, its zone always named. Never throws — Puck's
|
|
149
|
+
* did, across a data swap, which the website editor wrapped. */
|
|
150
|
+
getSelectorForId: (id: string) => Required<ItemSelector> | undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type { AppState as A, EditorConfig as E, HistoryEntry as H, Permissions as P, SetDataPatch as S, EditorView as a, EditorAction as b, EditorCore as c, EditorUi as d, EditorHistory as e };
|