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,208 @@
|
|
|
1
|
+
import { S as SlotAddress, N as Node, a as DataConfig, D as Data, F as Found, P as Place, b as SlotEntry, W as WalkInfo } from '../types-DziKCwsN.js';
|
|
2
|
+
export { B as BlockShape, c as FieldShape, d as FieldsShape, e as NodeProps, R as RootData } from '../types-DziKCwsN.js';
|
|
3
|
+
export { R as ResolvableItem, a as ResolveCache, b as ResolveOptions, c as ResolveTrigger, d as changedProps, e as createResolveCache, f as deepEqual, r as resolveNodeData } from '../resolve-DpH8YQOA.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Finding a node's slots, and reading or writing a value at a prop path.
|
|
7
|
+
*
|
|
8
|
+
* A slot is found the way Puck finds one: by the FIELDS, not by the value. A
|
|
9
|
+
* prop is a slot when its field says `type: 'slot'`; a slot can also sit inside
|
|
10
|
+
* an `object` field or inside each item of an `array` field, and its name is
|
|
11
|
+
* then the prop path Puck gives it — `items[0].content`, `box.content`.
|
|
12
|
+
*
|
|
13
|
+
* One deliberate difference from Puck: Puck also walks an object whose field is
|
|
14
|
+
* NOT an `object` field using the PARENT's field map, so a key there that
|
|
15
|
+
* happens to share a slot's name is treated as a slot. No document relies on
|
|
16
|
+
* that (a non-array value there crashes Puck's own walk), and a rule that
|
|
17
|
+
* depends on a coincidence of names is not one to copy.
|
|
18
|
+
*
|
|
19
|
+
* Order is DECLARATION order — the order the fields are written in the config,
|
|
20
|
+
* which is the order a person reads the block in (`col1` before `col2`). Puck
|
|
21
|
+
* visits slots in reverse, as a side effect of how it fills empty ones; nothing
|
|
22
|
+
* in either app depends on that.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** One step of a prop path: a key, or an index into an array. */
|
|
26
|
+
type Segment = string | number;
|
|
27
|
+
declare function getIn(value: unknown, segments: readonly Segment[]): unknown;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* efficient-easel/data — the document, as pure functions.
|
|
31
|
+
*
|
|
32
|
+
* The bottom layer of the engine that replaces Puck (the blueprint "Puck From
|
|
33
|
+
* The Ground Up For Efficient Editor", 2026-09-29, step 1.1). Every function
|
|
34
|
+
* takes a document and returns a new one; nothing here holds state, knows
|
|
35
|
+
* about React, or touches a browser global, so a server component, a Node
|
|
36
|
+
* script, a test and the editor's store all call the same code.
|
|
37
|
+
*
|
|
38
|
+
* find · walk · slotsOf read
|
|
39
|
+
* insert · move · remove · duplicate change the tree
|
|
40
|
+
* setProps · setRoot change one node's props, or the root's
|
|
41
|
+
* normalizeData what the editor does to a document it opens
|
|
42
|
+
* createNode · newId make a block the way the Add panel does
|
|
43
|
+
*
|
|
44
|
+
* THE FORMAT DOES NOT CHANGE. A document goes in and comes out in exactly the
|
|
45
|
+
* shape Puck 0.20 saves (see ./types). Four rules keep it that way, and the
|
|
46
|
+
* round-trip spec pins each one:
|
|
47
|
+
*
|
|
48
|
+
* 1. An empty slot is written as `[]`, and `zones` is written as `{}`.
|
|
49
|
+
* 2. Default props follow Puck: a new block is created WITH its
|
|
50
|
+
* `defaultProps`; the root's `defaultProps` are filled in on open; an
|
|
51
|
+
* existing block is never back-filled.
|
|
52
|
+
* 3. A root prop that no field declares (a site's `theme`, `fonts`, `scales`)
|
|
53
|
+
* is kept exactly as it is. Nothing here drops a key it doesn't know.
|
|
54
|
+
* 4. The two older formats are READ — root props written directly on `root`,
|
|
55
|
+
* and children parked in `zones` — and converted on open, the way Puck's
|
|
56
|
+
* `migrate()` converts them. They are never written back.
|
|
57
|
+
*
|
|
58
|
+
* KEY ORDER. The backend stores these documents in Postgres `jsonb`, which does
|
|
59
|
+
* not keep key order, so "the same document as Puck" means the same keys and
|
|
60
|
+
* values. What this layer does promise is stronger for its own round trip: a
|
|
61
|
+
* document that is already in shape comes back as THE SAME OBJECT (so
|
|
62
|
+
* `normalizeData(doc) === doc`), and every edit copies only the nodes on the
|
|
63
|
+
* path to the change — an untouched block is the same object before and after.
|
|
64
|
+
*
|
|
65
|
+
* ERRORS. An edit that names a node or slot that is not there, an index off the
|
|
66
|
+
* end of a list, a move into the node's own subtree, or an insert that would
|
|
67
|
+
* put a second copy of an id into the document throws a `DataError`. The
|
|
68
|
+
* editor's store decides what an operator sees; this layer never guesses.
|
|
69
|
+
*
|
|
70
|
+
* LEAF DISCIPLINE: no imports outside this folder, no dependency at all.
|
|
71
|
+
* `crypto` is read inside `newId`, never at module evaluation.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What `normalizeData` accepts: any saved document, as loosely as Puck's own
|
|
76
|
+
* `Data` type describes one (`root.props` optional). Converting the older
|
|
77
|
+
* shapes is its job, so it must take them without a cast. Every OTHER function
|
|
78
|
+
* here takes the normalised `Data` it returns.
|
|
79
|
+
*/
|
|
80
|
+
interface DataInput {
|
|
81
|
+
root?: {
|
|
82
|
+
props?: Record<string, unknown>;
|
|
83
|
+
[key: string]: unknown;
|
|
84
|
+
};
|
|
85
|
+
content?: unknown[];
|
|
86
|
+
zones?: Record<string, unknown[]>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The root's id in a zone key, and the parent of the top-level list. */
|
|
90
|
+
declare const ROOT_ID = "root";
|
|
91
|
+
/** The top-level list's slot name. */
|
|
92
|
+
declare const ROOT_SLOT = "default-zone";
|
|
93
|
+
/** The top-level list, as an address. */
|
|
94
|
+
declare const ROOT_LIST: SlotAddress;
|
|
95
|
+
/** `{ parentId, slot }` → Puck's zone key `"<parentId>:<slot>"`. */
|
|
96
|
+
declare const zoneKey: ({ parentId, slot }: SlotAddress) => string;
|
|
97
|
+
/** Puck's zone key → `{ parentId, slot }`. Ids and prop paths hold no colon. */
|
|
98
|
+
declare function parseZoneKey(key: string): SlotAddress;
|
|
99
|
+
type DataErrorCode = 'not-found' | 'no-slot' | 'bad-index' | 'duplicate-id' | 'cycle' | 'id-change' | 'unknown-type';
|
|
100
|
+
declare class DataError extends Error {
|
|
101
|
+
readonly code: DataErrorCode;
|
|
102
|
+
constructor(code: DataErrorCode, message: string);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* `<Type>-<uuid>` — the id every saved block already carries, in both apps.
|
|
106
|
+
* Where `crypto.randomUUID` is unavailable (a non-secure origin, an old test
|
|
107
|
+
* runtime) it falls back to time plus randomness: ids are persisted, so they
|
|
108
|
+
* must stay unique across page loads, which a counter would not.
|
|
109
|
+
*/
|
|
110
|
+
declare function newId(type: string): string;
|
|
111
|
+
/** A node's slots in declaration order. A missing top-level slot reads as `[]`. */
|
|
112
|
+
declare function slotsOf(node: Node, config: DataConfig): SlotEntry[];
|
|
113
|
+
/**
|
|
114
|
+
* Visit every node once, depth first, parent before children: the top-level
|
|
115
|
+
* list, then any slots on the root, then anything still parked in a legacy
|
|
116
|
+
* `zones` entry. Entries in a list that are not nodes are skipped, and indexes
|
|
117
|
+
* are always positions in the stored list.
|
|
118
|
+
*
|
|
119
|
+
* `visit` may return a node to put in this one's place (its id must not
|
|
120
|
+
* change); its children are then walked from the replacement. Returning
|
|
121
|
+
* nothing keeps the node. The result is `data` itself when nothing was
|
|
122
|
+
* replaced.
|
|
123
|
+
*/
|
|
124
|
+
declare function walk(data: Data, config: DataConfig, visit: (node: Node, info: WalkInfo) => Node | void): Data;
|
|
125
|
+
/** Where the node with this id is, or `null`. The first match in walk order. */
|
|
126
|
+
declare function find(data: Data, config: DataConfig, id: string): Found | null;
|
|
127
|
+
/** The list at an address, or `null` when no such slot exists. */
|
|
128
|
+
declare function getList(data: Data, config: DataConfig, address: SlotAddress): Node[] | null;
|
|
129
|
+
/**
|
|
130
|
+
* What the editor does to a document when it opens it — the same result Puck
|
|
131
|
+
* reaches on load, with the older formats converted (rule 4):
|
|
132
|
+
*
|
|
133
|
+
* - `root` holding its props directly becomes `{ props: { …those keys } }`;
|
|
134
|
+
* - the root's `defaultProps` fill any root prop that is missing (rule 2);
|
|
135
|
+
* - every declared top-level slot exists, `[]` when empty (rule 1);
|
|
136
|
+
* - `content` is a list and `zones` exists, `{}` when there is nothing in it;
|
|
137
|
+
* - children parked in `zones["<id>:<slot>"]` move into that slot when the
|
|
138
|
+
* node declares it and the slot is empty. Anything else in `zones` stays
|
|
139
|
+
* where it is — never dropped, never merged over content.
|
|
140
|
+
*
|
|
141
|
+
* A document already in shape is returned as the same object.
|
|
142
|
+
*/
|
|
143
|
+
declare function normalizeData(data: DataInput | Data, config: DataConfig): Data;
|
|
144
|
+
/**
|
|
145
|
+
* A new block, the way the Add panel makes one — Puck's `insert` recipe:
|
|
146
|
+
* `{ ...defaultProps, id }`, plus any `props` the caller starts it with. The
|
|
147
|
+
* defaults are copied, so two blocks never share an array or object, and every
|
|
148
|
+
* child a default slot seeds gets its own id.
|
|
149
|
+
*/
|
|
150
|
+
declare function createNode(config: DataConfig, type: string, props?: Record<string, unknown>, options?: {
|
|
151
|
+
id?: string;
|
|
152
|
+
}): Node;
|
|
153
|
+
/**
|
|
154
|
+
* Put `node` into the list at `place`. The node is brought into shape (default
|
|
155
|
+
* slots, ids for children that have none) and must not bring an id the
|
|
156
|
+
* document already holds — that is the bug where two blocks select, hide and
|
|
157
|
+
* delete together.
|
|
158
|
+
*/
|
|
159
|
+
declare function insert(data: Data, config: DataConfig, place: Place, node: Node): Data;
|
|
160
|
+
/** Take the node, and everything under it, out of the document. */
|
|
161
|
+
declare function remove(data: Data, config: DataConfig, id: string): Data;
|
|
162
|
+
/**
|
|
163
|
+
* Move a node to `to`. Within one list, `to.index` is the node's index AFTER
|
|
164
|
+
* the move (Puck's rule), so `0 … length-1`; into another list it is the index
|
|
165
|
+
* to insert at, `0 … length`. A move into the node's own subtree is refused.
|
|
166
|
+
*/
|
|
167
|
+
declare function move(data: Data, config: DataConfig, id: string, to: Place): Data;
|
|
168
|
+
/**
|
|
169
|
+
* Copy a node, and everything under it, to the place just after it. Every
|
|
170
|
+
* copied node gets a fresh id. `strip` sees each copied node (with its new id)
|
|
171
|
+
* and returns what to keep — the hook for "a copy must not claim the
|
|
172
|
+
* original's baked image" (`bakedId`), which Puck's own duplicate copied
|
|
173
|
+
* verbatim.
|
|
174
|
+
*/
|
|
175
|
+
declare function duplicate(data: Data, config: DataConfig, id: string, options?: {
|
|
176
|
+
id?: string;
|
|
177
|
+
strip?: (node: Node) => Node;
|
|
178
|
+
}): Data;
|
|
179
|
+
/**
|
|
180
|
+
* A copy of `node` and everything under it, every copied node with a FRESH id —
|
|
181
|
+
* what a paste inserts, so a clipboard can be pasted twice without two blocks
|
|
182
|
+
* sharing an id. `strip` sees each copied node, as in `duplicate`. The input is
|
|
183
|
+
* not touched.
|
|
184
|
+
*/
|
|
185
|
+
declare function copyNode(node: Node, config: DataConfig, options?: {
|
|
186
|
+
id?: string;
|
|
187
|
+
strip?: (node: Node) => Node;
|
|
188
|
+
}): Node;
|
|
189
|
+
/**
|
|
190
|
+
* Merge `patch` into a node's props — Puck's `replace`, for the common case of
|
|
191
|
+
* changing some props. A key keeps its place; a new key goes on the end;
|
|
192
|
+
* `undefined` clears a key (JSON drops it on save). New children in a patched
|
|
193
|
+
* slot are brought into shape. Changing `id` is refused. A patch that changes
|
|
194
|
+
* nothing returns `data` itself.
|
|
195
|
+
*/
|
|
196
|
+
declare function setProps(data: Data, config: DataConfig, id: string, patch: Record<string, unknown>): Data;
|
|
197
|
+
/**
|
|
198
|
+
* Replace a node wholesale — Puck's `replace` action: the node keeps its place
|
|
199
|
+
* and its id, and every prop is the new node's (a key it does not carry is
|
|
200
|
+
* gone, which `setProps` cannot do). Children in its slots that arrive without
|
|
201
|
+
* an id get one, and an id another node already holds is refused, as `insert`
|
|
202
|
+
* refuses it.
|
|
203
|
+
*/
|
|
204
|
+
declare function replaceNode(data: Data, config: DataConfig, id: string, node: Node): Data;
|
|
205
|
+
/** Merge `patch` into the root's props, the same way `setProps` does. */
|
|
206
|
+
declare function setRoot(data: Data, config: DataConfig, patch: Record<string, unknown>): Data;
|
|
207
|
+
|
|
208
|
+
export { Data, DataConfig, DataError, type DataErrorCode, type DataInput, Found, Node, Place, ROOT_ID, ROOT_LIST, ROOT_SLOT, type Segment, SlotAddress, SlotEntry, WalkInfo, copyNode, createNode, duplicate, find, getIn, getList, insert, move, newId, normalizeData, parseZoneKey, remove, replaceNode, setProps, setRoot, slotsOf, walk, zoneKey };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{DataError,ROOT_ID,ROOT_LIST,ROOT_SLOT,changedProps,copyNode,createNode,createResolveCache,deepEqual,duplicate,find,getList,insert,move,newId,normalizeData,parseZoneKey,remove,replaceNode,resolveNodeData,setProps,setRoot,slotsOf,walk,zoneKey}from"../chunk-LPRQDDQO.js";export{getIn}from"../chunk-NKB3KVAB.js";
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* efficient-easel/drag.css — the canvas drag layer (blueprint step 3.2): the
|
|
3
|
+
* hover handle, the drop line, "+ Add section" between sections and "+ Add
|
|
4
|
+
* block" in an empty slot. The tree's drag look is tree.css.
|
|
5
|
+
*
|
|
6
|
+
* The layer sits over the canvas frame in the host page and lets every click
|
|
7
|
+
* through to the canvas except on its own few controls. Needs
|
|
8
|
+
* `efficient-easel/shell.css` for the `--es-*` tokens.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.es-canvas-layer {
|
|
12
|
+
position: absolute;
|
|
13
|
+
inset: 0;
|
|
14
|
+
z-index: 5;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* ONE handle, over the smallest box under the pointer. Never unmounted: a
|
|
19
|
+
native drag aborts if its source leaves the page. */
|
|
20
|
+
.es-canvas-handle {
|
|
21
|
+
position: absolute;
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: 4px;
|
|
25
|
+
height: 22px;
|
|
26
|
+
padding: 0 8px 0 6px;
|
|
27
|
+
border-radius: 11px;
|
|
28
|
+
background: var(--es-toast-bg);
|
|
29
|
+
color: var(--es-on-accent);
|
|
30
|
+
font: 500 11px/22px var(--es-font);
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
box-shadow: 0 2px 6px rgba(16, 24, 40, 0.2);
|
|
33
|
+
cursor: grab;
|
|
34
|
+
opacity: 0;
|
|
35
|
+
pointer-events: none;
|
|
36
|
+
transition: opacity 0.12s;
|
|
37
|
+
}
|
|
38
|
+
.es-canvas-handle.is-shown {
|
|
39
|
+
opacity: 1;
|
|
40
|
+
pointer-events: auto;
|
|
41
|
+
}
|
|
42
|
+
.es-canvas-handle:active {
|
|
43
|
+
cursor: grabbing;
|
|
44
|
+
}
|
|
45
|
+
/* Frozen, not removed, while it is being dragged. */
|
|
46
|
+
.es-canvas-handle.is-dragging {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
}
|
|
49
|
+
.es-canvas-grip {
|
|
50
|
+
font-size: 10px;
|
|
51
|
+
letter-spacing: -2px;
|
|
52
|
+
opacity: 0.8;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* While a drag is on — from here, the tree or the Add list — the whole canvas
|
|
56
|
+
is one drop surface in the host page. */
|
|
57
|
+
.es-canvas-catch {
|
|
58
|
+
position: absolute;
|
|
59
|
+
inset: 0;
|
|
60
|
+
pointer-events: auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* The drop line: 2px of accent, across a list or down between columns. */
|
|
64
|
+
.es-canvas-line {
|
|
65
|
+
position: absolute;
|
|
66
|
+
background: var(--es-accent);
|
|
67
|
+
border-radius: 2px;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* "+ Add section", in the gap between two sections, on hover. */
|
|
72
|
+
.es-canvas-seam {
|
|
73
|
+
position: absolute;
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
pointer-events: auto;
|
|
78
|
+
}
|
|
79
|
+
.es-canvas-seam::before {
|
|
80
|
+
content: "";
|
|
81
|
+
position: absolute;
|
|
82
|
+
left: 8px;
|
|
83
|
+
right: 8px;
|
|
84
|
+
top: 50%;
|
|
85
|
+
height: 2px;
|
|
86
|
+
margin-top: -1px;
|
|
87
|
+
background: var(--es-accent);
|
|
88
|
+
border-radius: 2px;
|
|
89
|
+
opacity: 0;
|
|
90
|
+
transition: opacity 0.12s;
|
|
91
|
+
}
|
|
92
|
+
.es-canvas-seam-add {
|
|
93
|
+
position: relative;
|
|
94
|
+
padding: 2px 10px;
|
|
95
|
+
border: none;
|
|
96
|
+
border-radius: 999px;
|
|
97
|
+
background: var(--es-accent);
|
|
98
|
+
color: var(--es-on-accent);
|
|
99
|
+
font: 600 11px/16px var(--es-font);
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
opacity: 0;
|
|
102
|
+
transform: scale(0.9);
|
|
103
|
+
transition: opacity 0.12s, transform 0.12s;
|
|
104
|
+
}
|
|
105
|
+
.es-canvas-seam:hover::before,
|
|
106
|
+
.es-canvas-seam:focus-within::before,
|
|
107
|
+
.es-canvas-seam:hover .es-canvas-seam-add,
|
|
108
|
+
.es-canvas-seam-add:focus-visible {
|
|
109
|
+
opacity: 1;
|
|
110
|
+
}
|
|
111
|
+
.es-canvas-seam:hover .es-canvas-seam-add,
|
|
112
|
+
.es-canvas-seam-add:focus-visible {
|
|
113
|
+
transform: scale(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* An empty slot: a faint dashed cell that adds a block there, and takes a drop. */
|
|
117
|
+
.es-canvas-empty {
|
|
118
|
+
position: absolute;
|
|
119
|
+
box-sizing: border-box;
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
123
|
+
border: 1.5px dashed var(--es-input-border);
|
|
124
|
+
border-radius: 6px;
|
|
125
|
+
background: color-mix(in srgb, var(--es-app-bg) 60%, transparent);
|
|
126
|
+
color: var(--es-muted);
|
|
127
|
+
font: 500 12px/1.3 var(--es-font);
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
pointer-events: auto;
|
|
130
|
+
transition: border-color 0.12s, background 0.12s, color 0.12s;
|
|
131
|
+
}
|
|
132
|
+
.es-canvas-empty:hover,
|
|
133
|
+
.es-canvas-empty:focus-visible,
|
|
134
|
+
.es-canvas-empty.is-over {
|
|
135
|
+
border-color: var(--es-accent);
|
|
136
|
+
background: var(--es-accent-tint);
|
|
137
|
+
color: var(--es-accent-text);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* ── The Add panel's shell ───────────────────────────────────────────────────
|
|
141
|
+
The campaign designer's Add view (redesign PR 6b), itself the website
|
|
142
|
+
picker's grammar at the left panel's width: a search that says so when
|
|
143
|
+
nothing matches, uppercase group headings with a count, and tiles that are
|
|
144
|
+
clicked to add and dragged to place. */
|
|
145
|
+
.es-add {
|
|
146
|
+
font-family: var(--es-font);
|
|
147
|
+
}
|
|
148
|
+
.es-add-at {
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: space-between;
|
|
152
|
+
gap: 8px;
|
|
153
|
+
margin: 0 0 10px;
|
|
154
|
+
padding: 7px 8px 7px 11px;
|
|
155
|
+
border-radius: 8px;
|
|
156
|
+
background: var(--es-accent-tint);
|
|
157
|
+
color: var(--es-accent-text);
|
|
158
|
+
font-size: 12.5px;
|
|
159
|
+
font-weight: 600;
|
|
160
|
+
}
|
|
161
|
+
.es-add-at-clear {
|
|
162
|
+
flex: none;
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
justify-content: center;
|
|
166
|
+
width: 20px;
|
|
167
|
+
height: 20px;
|
|
168
|
+
border: none;
|
|
169
|
+
border-radius: 5px;
|
|
170
|
+
background: transparent;
|
|
171
|
+
color: inherit;
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
}
|
|
174
|
+
.es-add-at-clear:hover {
|
|
175
|
+
background: var(--es-accent-tint-strong);
|
|
176
|
+
}
|
|
177
|
+
.es-add-hint {
|
|
178
|
+
margin: 0 0 10px;
|
|
179
|
+
font-size: 12.5px;
|
|
180
|
+
color: var(--es-muted);
|
|
181
|
+
}
|
|
182
|
+
.es-add-search {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 9px;
|
|
186
|
+
margin-bottom: 16px;
|
|
187
|
+
padding: 0 12px;
|
|
188
|
+
height: 38px;
|
|
189
|
+
background: var(--es-track-bg);
|
|
190
|
+
border: 1px solid var(--es-input-border);
|
|
191
|
+
border-radius: 10px;
|
|
192
|
+
color: var(--es-muted);
|
|
193
|
+
transition: border-color 0.12s, box-shadow 0.12s, background 0.12s;
|
|
194
|
+
}
|
|
195
|
+
.es-add-search:focus-within {
|
|
196
|
+
border-color: var(--es-accent);
|
|
197
|
+
background: var(--es-panel);
|
|
198
|
+
box-shadow: 0 0 0 3px var(--es-accent-ring);
|
|
199
|
+
}
|
|
200
|
+
.es-add-search > svg {
|
|
201
|
+
flex: none;
|
|
202
|
+
}
|
|
203
|
+
.es-add-search input {
|
|
204
|
+
flex: 1;
|
|
205
|
+
min-width: 0;
|
|
206
|
+
border: none;
|
|
207
|
+
background: transparent;
|
|
208
|
+
outline: none;
|
|
209
|
+
box-shadow: none;
|
|
210
|
+
padding: 0;
|
|
211
|
+
font: inherit;
|
|
212
|
+
font-size: 13.5px;
|
|
213
|
+
color: var(--es-text);
|
|
214
|
+
}
|
|
215
|
+
.es-add-search input::placeholder {
|
|
216
|
+
color: var(--es-field-placeholder);
|
|
217
|
+
opacity: 1;
|
|
218
|
+
}
|
|
219
|
+
.es-add-search input::-webkit-search-cancel-button {
|
|
220
|
+
display: none;
|
|
221
|
+
}
|
|
222
|
+
.es-add-search-clear {
|
|
223
|
+
flex: none;
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
width: 20px;
|
|
228
|
+
height: 20px;
|
|
229
|
+
border: none;
|
|
230
|
+
border-radius: 5px;
|
|
231
|
+
background: transparent;
|
|
232
|
+
color: var(--es-muted);
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
}
|
|
235
|
+
.es-add-search-clear:hover {
|
|
236
|
+
background: var(--es-divider);
|
|
237
|
+
color: var(--es-text);
|
|
238
|
+
}
|
|
239
|
+
.es-add-group + .es-add-group {
|
|
240
|
+
margin-top: 22px;
|
|
241
|
+
}
|
|
242
|
+
.es-add-group-head {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: baseline;
|
|
245
|
+
gap: 8px;
|
|
246
|
+
margin-bottom: 11px;
|
|
247
|
+
}
|
|
248
|
+
.es-add-group-title {
|
|
249
|
+
font-size: 11px;
|
|
250
|
+
font-weight: 700;
|
|
251
|
+
letter-spacing: 0.07em;
|
|
252
|
+
text-transform: uppercase;
|
|
253
|
+
color: var(--es-muted);
|
|
254
|
+
}
|
|
255
|
+
.es-add-group-count {
|
|
256
|
+
font-size: 11px;
|
|
257
|
+
color: var(--es-field-placeholder);
|
|
258
|
+
}
|
|
259
|
+
.es-add-grid {
|
|
260
|
+
display: grid;
|
|
261
|
+
grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
|
|
262
|
+
gap: 6px;
|
|
263
|
+
}
|
|
264
|
+
.es-add-tile {
|
|
265
|
+
display: flex;
|
|
266
|
+
flex-direction: column;
|
|
267
|
+
align-items: center;
|
|
268
|
+
justify-content: center;
|
|
269
|
+
gap: 6px;
|
|
270
|
+
min-height: 64px;
|
|
271
|
+
padding: 9px 6px;
|
|
272
|
+
border: 1px solid var(--es-line);
|
|
273
|
+
border-radius: 7px;
|
|
274
|
+
background: var(--es-panel);
|
|
275
|
+
color: var(--es-text);
|
|
276
|
+
font: inherit;
|
|
277
|
+
font-size: 12px;
|
|
278
|
+
line-height: 1.2;
|
|
279
|
+
text-align: center;
|
|
280
|
+
cursor: grab;
|
|
281
|
+
transition: background 0.12s, border-color 0.12s;
|
|
282
|
+
}
|
|
283
|
+
.es-add-tile:hover {
|
|
284
|
+
background: var(--es-hover);
|
|
285
|
+
border-color: var(--es-accent);
|
|
286
|
+
}
|
|
287
|
+
.es-add-tile:active {
|
|
288
|
+
cursor: grabbing;
|
|
289
|
+
}
|
|
290
|
+
.es-add-tile:focus-visible {
|
|
291
|
+
outline: 2px solid var(--es-accent);
|
|
292
|
+
outline-offset: 1px;
|
|
293
|
+
}
|
|
294
|
+
.es-add-tile[aria-disabled='true'] {
|
|
295
|
+
cursor: not-allowed;
|
|
296
|
+
opacity: 0.5;
|
|
297
|
+
background: var(--es-panel);
|
|
298
|
+
border-color: var(--es-line);
|
|
299
|
+
}
|
|
300
|
+
.es-add-tile-glyph {
|
|
301
|
+
display: flex;
|
|
302
|
+
color: var(--es-icon);
|
|
303
|
+
}
|
|
304
|
+
.es-add-tile-glyph svg {
|
|
305
|
+
width: 20px;
|
|
306
|
+
height: 20px;
|
|
307
|
+
}
|
|
308
|
+
.es-add-tile:hover .es-add-tile-glyph {
|
|
309
|
+
color: var(--es-accent);
|
|
310
|
+
}
|
|
311
|
+
/* Cards (0.80.0): a group whose tiles all carry a cover — two across, the
|
|
312
|
+
picture above the name, so a section is chosen by how it looks. */
|
|
313
|
+
.es-add-grid.is-cards {
|
|
314
|
+
grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
|
|
315
|
+
gap: 8px;
|
|
316
|
+
}
|
|
317
|
+
.es-add-tile.has-cover {
|
|
318
|
+
align-items: stretch;
|
|
319
|
+
justify-content: flex-start;
|
|
320
|
+
gap: 5px;
|
|
321
|
+
min-height: 0;
|
|
322
|
+
padding: 5px 5px 7px;
|
|
323
|
+
text-align: left;
|
|
324
|
+
}
|
|
325
|
+
/* The containing block for the host's picture: a cover drawn to FILL its box
|
|
326
|
+
(the storefront's scenes are positioned against it) must not escape the
|
|
327
|
+
tile and paint over the panel. */
|
|
328
|
+
.es-add-tile-cover {
|
|
329
|
+
position: relative;
|
|
330
|
+
display: block;
|
|
331
|
+
overflow: hidden;
|
|
332
|
+
aspect-ratio: 16 / 10;
|
|
333
|
+
border-radius: 4px;
|
|
334
|
+
background: var(--es-app-bg);
|
|
335
|
+
}
|
|
336
|
+
.es-add-tile-cover > * {
|
|
337
|
+
display: block;
|
|
338
|
+
width: 100%;
|
|
339
|
+
height: 100%;
|
|
340
|
+
}
|
|
341
|
+
.es-add-tile.has-cover .es-add-tile-label {
|
|
342
|
+
padding: 0 2px;
|
|
343
|
+
}
|
|
344
|
+
.es-add-empty {
|
|
345
|
+
display: flex;
|
|
346
|
+
flex-direction: column;
|
|
347
|
+
align-items: center;
|
|
348
|
+
gap: 9px;
|
|
349
|
+
padding: 48px 12px;
|
|
350
|
+
text-align: center;
|
|
351
|
+
color: var(--es-muted);
|
|
352
|
+
}
|
|
353
|
+
.es-add-empty-title {
|
|
354
|
+
font-size: 14px;
|
|
355
|
+
font-weight: 600;
|
|
356
|
+
color: var(--es-text);
|
|
357
|
+
}
|
|
358
|
+
.es-add-empty-sub {
|
|
359
|
+
font-size: 12.5px;
|
|
360
|
+
}
|