@gramlot/gramlot-examples 0.3.3 → 0.3.5

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 (32) hide show
  1. package/README.md +6 -3
  2. package/package.json +4 -3
  3. package/src/gramlot_examples/catalog.json +54 -0
  4. package/src/gramlot_examples/gallery/dist/frame.js +548 -36
  5. package/src/gramlot_examples/gallery/dist/gallery.js +2467 -16677
  6. package/src/gramlot_examples/gallery/dist/inputs.js +12 -6
  7. package/src/gramlot_examples/gallery/dist/inspector.js +547 -35
  8. package/src/gramlot_examples/gallery/dist/layout.js +847 -28
  9. package/src/gramlot_examples/gallery/dist/notices.json +0 -24
  10. package/src/gramlot_examples/gallery/dist/theme-styles.js +1 -1
  11. package/src/gramlot_examples/gallery/gallery-page.js +77 -47
  12. package/src/gramlot_examples/gallery/gallery.css +61 -38
  13. package/src/gramlot_examples/gallery/guided-demo.js +151 -0
  14. package/src/gramlot_examples/gallery/page.js +6 -6
  15. package/src/gramlot_examples/index.js +1 -0
  16. package/src/gramlot_examples/pages/layout/01_border.js +80 -0
  17. package/src/gramlot_examples/pages/layout/01_border.md +23 -0
  18. package/src/gramlot_examples/pages/layout/02_nested.js +99 -0
  19. package/src/gramlot_examples/pages/layout/02_nested.md +23 -0
  20. package/src/gramlot_examples/pages/layout/03_rounded.js +111 -0
  21. package/src/gramlot_examples/pages/layout/03_rounded.md +25 -0
  22. package/src/gramlot_examples/pages/layout/04_drawer_handles.js +140 -0
  23. package/src/gramlot_examples/pages/layout/04_drawer_handles.md +33 -0
  24. package/src/gramlot_examples/pages/layout/05_nested_lab.js +251 -0
  25. package/src/gramlot_examples/pages/layout/05_nested_lab.md +25 -0
  26. package/src/gramlot_examples/pages/layout/06_border_events.js +118 -0
  27. package/src/gramlot_examples/pages/layout/06_border_events.md +25 -0
  28. package/src/gramlot_examples/pages/layout/07_tabs.js +300 -0
  29. package/src/gramlot_examples/pages/layout/07_tabs.md +37 -0
  30. package/src/gramlot_examples/pages/layout/08_stack.js +142 -0
  31. package/src/gramlot_examples/pages/layout/08_stack.md +27 -0
  32. package/src/gramlot_examples/pages/layout/README.md +15 -0
@@ -93,6 +93,239 @@
93
93
  function readParameterEnums(node) {
94
94
  return Object.fromEntries(Object.entries(node._getMeta("gramlot_component")?.parameters ?? {}).filter(([, spec]) => spec.enum).map(([name, spec]) => [name, spec.enum]));
95
95
  }
96
+ var InspectorHistory = class {
97
+ #undo = [];
98
+ #redo = [];
99
+ #references = /* @__PURE__ */ new WeakMap();
100
+ constructor(gramlot) {
101
+ this.gramlot = gramlot;
102
+ this.Source = gramlot.src.source.constructor;
103
+ this.Bag = gramlot.data.constructor;
104
+ }
105
+ get state() {
106
+ return { undo: this.#undo.at(-1)?.label ?? "", redo: this.#redo.at(-1)?.label ?? "" };
107
+ }
108
+ clear() {
109
+ this.#undo = [];
110
+ this.#redo = [];
111
+ }
112
+ #ref(node) {
113
+ if (!this.#references.has(node)) this.#references.set(node, { node });
114
+ return this.#references.get(node);
115
+ }
116
+ #bind(ref, node) {
117
+ ref.node = node;
118
+ this.#references.set(node, ref);
119
+ }
120
+ #capture(value) {
121
+ if (!(value instanceof this.Bag)) return value;
122
+ return value.getNodes().map((node) => [
123
+ this.#ref(node),
124
+ node.nodeTag,
125
+ node.resolver,
126
+ Object.entries(node.getAttr()).sort(([a], [b]) => a.localeCompare(b)),
127
+ this.#capture(node.getValue(true))
128
+ ]);
129
+ }
130
+ #equal(a, b) {
131
+ return Object.is(a, b) || Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => this.#equal(v, b[i]));
132
+ }
133
+ #commit(label, capture, redo, undo) {
134
+ const before = capture();
135
+ let error;
136
+ try {
137
+ redo();
138
+ } catch (caught) {
139
+ error = caught;
140
+ }
141
+ const after = capture();
142
+ if (!this.#equal(before, after)) {
143
+ this.#undo.push({ label, capture, before, after, undo, redo });
144
+ this.#redo = [];
145
+ }
146
+ if (error) throw error;
147
+ }
148
+ #step(from, to, direction, expected, result) {
149
+ const action = from.at(-1);
150
+ if (!action) return;
151
+ if (this.gramlot.state === "disposed") throw new Error("Inspected page disconnected");
152
+ if (!this.#equal(action.capture(), action[expected])) throw new Error("The target changed outside the inspector. Undo/Redo would overwrite those changes.");
153
+ let error;
154
+ try {
155
+ action[direction]();
156
+ } catch (caught) {
157
+ error = caught;
158
+ }
159
+ if (this.#equal(action.capture(), action[result])) {
160
+ from.pop();
161
+ to.push(action);
162
+ } else if (!error) error = new Error("The application changed the target during Undo/Redo.");
163
+ if (error) throw error;
164
+ }
165
+ undo() {
166
+ this.#step(this.#undo, this.#redo, "undo", "after", "before");
167
+ }
168
+ redo() {
169
+ this.#step(this.#redo, this.#undo, "redo", "before", "after");
170
+ }
171
+ edit(node, attrs, value, changeAttrs, changeValue) {
172
+ const ref = this.#ref(node), oldAttrs = { ...node.getAttr() }, oldValue = node.getValue(true);
173
+ const capture = () => [ref.node.parentBag, this.#captureNode(ref.node)];
174
+ const write = (attributes, content) => {
175
+ if (changeAttrs) ref.node.setAttr(attributes, true, false, false);
176
+ if (changeValue) ref.node.setValue(content);
177
+ };
178
+ this.#commit("Edit value or attributes", capture, () => write(attrs, value), () => write(oldAttrs, oldValue));
179
+ }
180
+ #captureNode(node) {
181
+ return [node.resolver, Object.entries(node.getAttr()).sort(([a], [b]) => a.localeCompare(b)), this.#capture(node.getValue(true))];
182
+ }
183
+ #sourceCapture = () => this.#capture(this.gramlot.src.source);
184
+ #node(path) {
185
+ if (typeof path !== "string" || !path) throw new Error("Select a Source node");
186
+ const node = this.gramlot.src.source.getNode(path);
187
+ if (!node || node.resolver) throw new Error("Source node is missing or deferred");
188
+ return node;
189
+ }
190
+ #removable(node) {
191
+ if (node.parentBag === this.gramlot.src.source) throw new Error("The page Source root cannot be removed or moved");
192
+ }
193
+ #saved(node) {
194
+ return { ref: this.#ref(node), label: node.label, tag: node.nodeTag, value: node.getValue(true), attrs: { ...node.getAttr() } };
195
+ }
196
+ #restore(saved, bag, index) {
197
+ if (bag.getNode(saved.label)) throw new Error(`Source label already exists: ${saved.label}`);
198
+ try {
199
+ bag.setItem(saved.label, saved.value, saved.attrs, index, false, false, "inspector", false, true, null, saved.tag);
200
+ } finally {
201
+ const inserted = bag.getNode(saved.label);
202
+ if (inserted) this.#bind(saved.ref, inserted);
203
+ }
204
+ }
205
+ #parentState(parent) {
206
+ return { value: parent.getValue(true), attrs: { ...parent.getAttr() } };
207
+ }
208
+ #setParent(parent, state) {
209
+ parent.setValue(state.value, true, state.attrs, false);
210
+ parent.setAttr(state.attrs, true, false, false);
211
+ }
212
+ childFields(parent) {
213
+ const specifications = parent._getMeta("gramlot_component")?.childParameters ?? {};
214
+ return Object.entries(specifications).filter(([, spec]) => spec.required).map(([name, spec]) => {
215
+ const children = parent.getValue(true);
216
+ const used = spec.unique && children instanceof this.Source ? children.getNodes().map((child) => child.getAttr(name)) : [];
217
+ return { name, choices: spec.enum.filter((value) => !used.includes(value)) };
218
+ });
219
+ }
220
+ validateChild(parent, attrs, moving = null) {
221
+ const specifications = parent._getMeta("gramlot_component")?.childParameters ?? {};
222
+ for (const [name, spec] of Object.entries(specifications)) {
223
+ const value = attrs[name];
224
+ if (spec.required && value == null) throw new Error(`Choose ${name} before inserting the child`);
225
+ if (spec.enum && value != null && !spec.enum.includes(value)) throw new Error(`Invalid ${name}: ${value}`);
226
+ const children = parent.getValue(true);
227
+ if (spec.unique && children instanceof this.Source && children.getNodes().some((child) => child !== moving && child.getAttr(name) === value)) {
228
+ throw new Error(`${name} is already in use: ${value}`);
229
+ }
230
+ }
231
+ }
232
+ catalog(path) {
233
+ const node = this.#node(path), builder = node.builder;
234
+ return Object.entries(builder.schema).filter(([, info]) => !info._meta?.data_element && !info._meta?.subbuilder && !info._meta?.container).map(([tag, info]) => ({ tag, kind: info._meta?.gramlot_component ? "component" : "html", fields: this.childFields(node) }));
235
+ }
236
+ insert(path, requestedTag, attrs = {}) {
237
+ const parent = this.#node(path), builder = parent.builder;
238
+ const tag = builder.schemaTag(requestedTag);
239
+ if (!this.catalog(path).some((entry) => entry.tag === tag)) throw new Error("Choose an available component or HTML tag");
240
+ this.validateChild(parent, attrs);
241
+ const previous = this.#parentState(parent), parentRef = this.#ref(parent);
242
+ let saved, promoted;
243
+ const redo = () => {
244
+ if (saved) {
245
+ if (!(previous.value instanceof this.Source)) this.#setParent(parentRef.node, promoted);
246
+ this.#restore(saved, parentRef.node.getValue(true), parentRef.node.getValue(true).getNodes().length);
247
+ return;
248
+ }
249
+ const existing = previous.value instanceof this.Source ? new Set(previous.value.getNodes()) : /* @__PURE__ */ new Set();
250
+ try {
251
+ builder.commandOnNode(parentRef.node, tag, null, attrs);
252
+ } finally {
253
+ const body = parentRef.node.getValue(true);
254
+ if (body instanceof this.Source) {
255
+ const added = body.getNodes().find((node) => !existing.has(node));
256
+ if (added) {
257
+ saved = this.#saved(added);
258
+ promoted = this.#parentState(parentRef.node);
259
+ }
260
+ }
261
+ }
262
+ };
263
+ const undo = () => {
264
+ if (saved) saved.ref.node.parentBag.popNode(saved.ref.node.label, "inspector");
265
+ if (!(previous.value instanceof this.Source)) this.#setParent(parentRef.node, previous);
266
+ };
267
+ this.#commit("Add element", this.#sourceCapture, redo, undo);
268
+ return `${path}.${saved.label}`;
269
+ }
270
+ remove(path) {
271
+ const node = this.#node(path);
272
+ this.#removable(node);
273
+ const saved = this.#saved(node), bag = node.parentBag, index = bag.getNodes().indexOf(node);
274
+ this.#commit(
275
+ "Remove branch",
276
+ this.#sourceCapture,
277
+ () => saved.ref.node.parentBag.popNode(saved.label, "inspector"),
278
+ () => this.#restore(saved, bag, index)
279
+ );
280
+ return path.slice(0, path.lastIndexOf("."));
281
+ }
282
+ move(path, destination, placement) {
283
+ if (!["before", "inside", "after"].includes(placement)) throw new Error("Invalid drop position");
284
+ const node = this.#node(path), target = this.#node(destination);
285
+ this.#removable(node);
286
+ if (path === destination || destination.startsWith(path + ".")) throw new Error("A branch cannot be moved into itself");
287
+ const parent = placement === "inside" ? target : target.parentBag.parentNode;
288
+ if (!parent) throw new Error("Drop inside the page Source root");
289
+ const builder = parent.builder;
290
+ if (node.builder !== builder) throw new Error("Branches cannot cross builder dialects");
291
+ this.validateChild(parent, node.getAttr(), node);
292
+ const parentRef = this.#ref(parent);
293
+ const previous = this.#parentState(parent), origin = node.parentBag, oldIndex = origin.getNodes().indexOf(node);
294
+ const body = previous.value;
295
+ if (body instanceof this.Source && body !== origin && body.getNode(node.label)) throw new Error(`Source label already exists: ${node.label}`);
296
+ const children = body instanceof this.Source ? body.getNodes().filter((child) => child !== node) : [];
297
+ if (parent.nodeTag) {
298
+ builder.validateParent(node.nodeTag, parent.nodeTag);
299
+ builder.validateChildren(parent.nodeTag, [...children.map((child) => child.nodeTag), node.nodeTag]);
300
+ }
301
+ let index = placement === "inside" ? children.length : children.indexOf(target) + (placement === "after" ? 1 : 0);
302
+ if (index < 0) throw new Error("Drop target changed");
303
+ if (body === origin) {
304
+ const ref = this.#ref(node);
305
+ this.#commit("Move branch", this.#sourceCapture, () => origin.move(origin.getNodes().indexOf(ref.node), index), () => origin.move(origin.getNodes().indexOf(ref.node), oldIndex));
306
+ } else {
307
+ const saved = this.#saved(node);
308
+ const destinationBag = body instanceof this.Source ? body : new this.Source(null, builder);
309
+ let promoted;
310
+ this.#commit("Move branch", this.#sourceCapture, () => {
311
+ if (!(body instanceof this.Source)) {
312
+ if (promoted) this.#setParent(parentRef.node, promoted);
313
+ else {
314
+ builder.promoteNodeValue(parentRef.node, body, destinationBag);
315
+ promoted = this.#parentState(parentRef.node);
316
+ }
317
+ }
318
+ saved.ref.node.parentBag.popNode(saved.label, "inspector");
319
+ this.#restore(saved, destinationBag, index);
320
+ }, () => {
321
+ saved.ref.node.parentBag.popNode(saved.label, "inspector");
322
+ this.#restore(saved, origin, oldIndex);
323
+ if (!(body instanceof this.Source)) this.#setParent(parentRef.node, previous);
324
+ });
325
+ }
326
+ return `${placement === "inside" ? destination : destination.slice(0, destination.lastIndexOf("."))}.${node.label}`;
327
+ }
328
+ };
96
329
  var InspectorStyles = class {
97
330
  #document;
98
331
  #element = null;
@@ -168,6 +401,7 @@
168
401
  const document2 = gramlot.src.renderer.destination.ownerDocument;
169
402
  const window2 = document2.defaultView;
170
403
  const styles = new InspectorStyles(document2);
404
+ const history = new InspectorHistory(gramlot);
171
405
  const subscriber = `inspector-bridge-${window2.crypto.getRandomValues(new Uint32Array(4)).join("-")}`;
172
406
  const identities = /* @__PURE__ */ new WeakMap();
173
407
  const versions = /* @__PURE__ */ new WeakMap();
@@ -202,7 +436,8 @@
202
436
  const snapshot = () => ({
203
437
  data: describe(bag("data"), "data"),
204
438
  source: describe(bag("source"), "source"),
205
- breakpoints: [...breakpoints.values()].map(({ test, ...record }) => record)
439
+ breakpoints: [...breakpoints.values()].map(({ test, ...record }) => record),
440
+ history: history.state
206
441
  });
207
442
  const send = (data) => {
208
443
  if (!stopped) port.postMessage(data);
@@ -275,8 +510,7 @@
275
510
  if (node.resolver || !scalarType(node.getValue(true))) throw new Error("This value is read-only");
276
511
  value = decodeScalar2(command.value);
277
512
  }
278
- if (command.set.length || command.remove.length) node.setAttr(attrs, true, false, false);
279
- if (Object.hasOwn(command, "value")) node.setValue(value);
513
+ history.edit(node, attrs, value, !!(command.set.length || command.remove.length), Object.hasOwn(command, "value"));
280
514
  };
281
515
  const setBreakpoint = (command) => {
282
516
  bag(command.root);
@@ -302,10 +536,27 @@
302
536
  send({ type: "result", id: message.id, ok: true, styles: styles.snapshot() });
303
537
  return;
304
538
  }
305
- if (message.type === "edit") edit(message.command);
539
+ let selection;
540
+ if (message.type === "catalog") {
541
+ send({ type: "result", id: message.id, ok: true, catalog: history.catalog(message.command.path) });
542
+ return;
543
+ }
544
+ if (["insert", "remove", "move"].includes(message.type)) {
545
+ const command = message.command, node = bag("source").getNode(command.path);
546
+ if (!node || identity(node) !== command.nodeId || (versions.get(node) ?? 0) !== command.revision) throw new Error("Source node changed. Select it again.");
547
+ if (message.type === "insert") selection = history.insert(command.path, command.tag, command.attrs);
548
+ if (message.type === "remove") selection = history.remove(command.path);
549
+ if (message.type === "move") {
550
+ const target = bag("source").getNode(command.destination);
551
+ if (!target || identity(target) !== command.targetId || (versions.get(target) ?? 0) !== command.targetRevision) throw new Error("Drop target changed. Try again.");
552
+ selection = history.move(command.path, command.destination, command.placement);
553
+ }
554
+ } else if (message.type === "undo") history.undo();
555
+ else if (message.type === "redo") history.redo();
556
+ else if (message.type === "edit") edit(message.command);
306
557
  else if (message.type === "breakpoint") setBreakpoint(message.command);
307
558
  else if (message.type !== "refresh") throw new Error(`Unknown inspector command: ${message.type}`);
308
- send({ type: "result", id: message.id, ok: true, snapshot: snapshot() });
559
+ send({ type: "result", id: message.id, ok: true, selection, snapshot: snapshot() });
309
560
  } catch (error) {
310
561
  send({ type: "result", id: message.id, ok: false, error: error.message });
311
562
  }
@@ -316,6 +567,7 @@
316
567
  stopped = true;
317
568
  for (const root of roots) bag(root).unsubscribe(subscriber, { any: true });
318
569
  breakpoints.clear();
570
+ history.clear();
319
571
  styles.dispose();
320
572
  port.removeEventListener("message", receive);
321
573
  window2.removeEventListener("pagehide", dispose);
@@ -347,6 +599,7 @@
347
599
  error = "";
348
600
  breakpoints = [];
349
601
  styles = null;
602
+ history = { undo: "", redo: "" };
350
603
  #info = /* @__PURE__ */ new WeakMap();
351
604
  #listeners = /* @__PURE__ */ new Set();
352
605
  #pending = /* @__PURE__ */ new Map();
@@ -378,6 +631,7 @@
378
631
  if (message.snapshot) {
379
632
  for (const root of roots2) this.#reconcile(this[root], message.snapshot[root]);
380
633
  this.breakpoints = message.snapshot.breakpoints;
634
+ this.history = message.snapshot.history;
381
635
  this.state = "started";
382
636
  this.error = "";
383
637
  } else if (message.type === "error") this.error = message.error;
@@ -388,7 +642,7 @@
388
642
  const pending = this.#pending.get(message.id);
389
643
  if (pending) {
390
644
  this.#pending.delete(message.id);
391
- if (message.ok && !this.error) pending.resolve();
645
+ if (message.ok && !this.error) pending.resolve(message);
392
646
  else pending.reject(new Error(message.error || this.error));
393
647
  }
394
648
  }
@@ -451,8 +705,8 @@
451
705
  this.#notify();
452
706
  }
453
707
  };
454
- var gramlot_tools_inspector_default = ":host { all: initial; color-scheme: light; font: 12px/1.4 system-ui, sans-serif; color: #38414b; }\n* { box-sizing: border-box; }\nbutton, input, select { font: inherit; color: inherit; }\nbutton { cursor: pointer; }\nbutton:focus-visible, input:focus-visible, select:focus-visible { outline: 1px solid #3574cb; outline-offset: -1px; }\n.tree { padding: 7px 3px; }\n.properties { background: white; }\n.properties-heading { font-size: 11px; display: flex; align-items: center; justify-content: space-between; height: 29px; padding: 0 8px; background: #f1f3f5; color: #6d7989; border-bottom: 1px solid #d1d7df; }\n.properties-heading button { background: white; border: 1px solid #c5cdda; border-radius: 2px; width: 23px; height: 23px; padding: 0; font-size: 18px; line-height: 20px; }\nfieldset { border: 0; margin: 0; padding: 0; min-width: 0; }\n.property-row { display: grid; grid-template-columns: minmax(95px, 30%) minmax(0, 1fr); min-height: 20px; border-bottom: 1px solid #d1d7df; font-size: 11px; }\n.property-name { display: flex; align-items: center; justify-content: flex-end; background: #888; color: white; font-weight: 700; padding: 1px 6px; border-right: 1px solid #d1d7df; overflow-wrap: anywhere; text-align: right; }\n.property-value { display: flex; align-items: center; min-width: 0; }\n.property-row input { border: 0; border-radius: 0; min-width: 0; width: 100%; height: 19px; padding: 1px 5px; background: transparent; }\n.property-name input { color: white; text-align: right; padding: 0; }\n.property-value input { flex: 1; }\n.property-value input[type=checkbox] { flex: none; width: 14px; height: 14px; margin: 2px 6px; }\n.property-value select { max-width: 82px; height: 20px; padding: 0; border: 0; border-left: 1px solid #d1d7df; background: #f1f3f5; font-size: 10px; }\n.remove { display: inline-flex; align-items: center; justify-content: center; margin-left: auto; flex: none; width: 22px; height: 20px; padding: 3px; border: 0; background: transparent; color: #7c8797; opacity: 0; }\n.remove svg { width: 13px; height: 13px; }\n.property-row:hover .remove, .property-row:focus-within .remove { opacity: 1; }\n.remove:hover { color: #b82323; background: #fbe9e9; }\n:disabled { cursor: default; opacity: .65; }\n.path { flex: none; padding: 5px 9px; border-top: 1px solid #c9cfd6; background: #f5f7f9; color: #697687; font-size: 11px; min-height: 27px; overflow-wrap: anywhere; }\n.error, .edit-status, .breakpoint-status, .breakpoint-line { font-size: 11px; margin: 0; padding: 3px 8px; overflow-wrap: anywhere; }\n.error, .edit-status, .breakpoint-status { color: #a12121; }\n.breakpoint-line { background: #fff2ed; color: #854127; border-bottom: 1px solid #d1d7df; }\n.error:empty, .edit-status:empty, .breakpoint-status:empty, .breakpoint-line:empty { display: none; }\n.breakpoint-dialog { width: min(340px, 90vw); color: #38414b; border: 1px solid #bbc3cf; border-radius: 4px; padding: 12px; box-shadow: 0 8px 28px #0003; font: 12px/1.4 system-ui, sans-serif; }\n.breakpoint-dialog::backdrop { background: #0002; }\n.breakpoint-dialog h2 { font-size: 13px; margin: 0 0 10px; overflow-wrap: anywhere; }\n.breakpoint-dialog select, .breakpoint-dialog input { width: 100%; border: 1px solid #bbc3cf; padding: 4px; margin: 0 0 7px; background: white; }\n.dialog-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 7px; }\n.dialog-actions button { padding: 3px 10px; border: 1px solid #bbc3cf; border-radius: 3px; background: #f1f3f5; }\n.heading-label { flex: 1; }\n.properties-heading .breakpoint-count { width: auto; padding: 0 5px; margin-right: 5px; font-size: 11px; color: #b04a35; }\n.breakpoint-entry { display: flex; align-items: center; gap: 6px; padding: 4px 0; border-bottom: 1px solid #d1d7df; }\n.breakpoint-entry span { flex: 1; overflow-wrap: anywhere; }\n.breakpoint-entry button { background: transparent; border: 0; font-size: 17px; }\n[hidden] { display: none !important; }\n.targetbar { flex: none; height: 28px; padding: 2px 6px; background: #edf2fa; border-bottom: 1px solid #dadce0; }\n.targetbar select { width: 100%; height: 23px; border: 0; background: transparent; font-size: 11px; }\n.styles-panel { flex: 1; min-height: 0; overflow: auto; background: #fff; font-size: 11px; }\n.styles-toolbar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 6px 8px; background: #f1f3f5; border-bottom: 1px solid #d1d7df; }\n.styles-toolbar span { flex: 1; font-weight: 600; }\n.styles-toolbar button, .styles-toolbar a { padding: 3px 7px; border: 1px solid #c5cdda; border-radius: 3px; background: white; }\n.styles-toolbar a { color: inherit; text-decoration: none; }\n.styles-toolbar a[aria-disabled=true] { cursor: default; opacity: .65; }\n.styles-toolbar a:focus-visible { outline: 1px solid #3574cb; outline-offset: -1px; }\n.styles-help, .styles-empty, .styles-status { margin: 0; padding: 7px 9px; color: #697687; }\n.styles-status { color: #a12121; }\n.styles-status:empty { display: none; }\n.styles-group h3 { margin: 0; padding: 6px 9px; font-size: 11px; background: #edf2fa; border-block: 1px solid #d1d7df; }\n.style-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 25px 23px; align-items: center; gap: 8px; min-width: 0; padding: 3px 9px; border-bottom: 1px solid #e8ebef; }\n.style-row label { min-width: 0; font: 11px/1.4 ui-monospace, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.style-row input { min-width: 0; width: 100%; height: 23px; padding: 2px 5px; border: 1px solid #c5cdda; border-radius: 3px; background: white; }\n.style-row input[type=color] { width: 25px; padding: 1px; cursor: pointer; }\n.style-row button { grid-column: 4; width: 23px; height: 23px; padding: 0; border: 1px solid #c5cdda; border-radius: 3px; background: #f5f7f9; font-size: 15px; }\n\n:host([embedded]) { display: block; height: 100%; min-height: 0; }\n:host([embedded]) > div { height: 100%; }\n.embedded-panel { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }\n\n.property-value select.enum-value { flex: 1; min-width: 0; max-width: none; width: 100%; border: 0; background: white; font: inherit; padding: 1px 5px; }\n\n/* Presentation changes preserve the inspector runtime and selection. */\n:host([hidden]) { display: none; }\n:host([docked]) { display: block; height: 100%; min-height: 0; }\n:host([docked][hidden]) { display: none; }\n:host([docked]) > div { height: 100%; }\n:host([docked]) .targetbar select { min-height: 40px; font-size: 16px; }\n:host([docked]) .property-row { min-height: 36px; font-size: 13px; }\n:host([docked]) .property-row input { min-height: 35px; font-size: 16px; }\n:host([docked]) .remove { opacity: 1; min-height: 35px; width: 32px; }\n\n:host([docked]) { --tree-action-size: 28px; --tree-font-size: 14px; --tree-row-height: 40px; --separator-size: 14px; }\n:host([docked]) .targetbar { height: 44px; }\n:host([docked]) gramlot-poc-tabs > button { height: 44px; font-size: 14px; }\n:host([docked]) .properties-heading { height: 40px; font-size: 13px; }\n:host([docked]) .properties-heading button { width: 36px; height: 36px; }\n:host([docked]) .property-value select { height: 35px; font-size: 14px; }\n";
455
- var gramlot_poc_tree_default = "/* Copyright 2025-2026 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0\n * Inspector subset of the DevTools storeTree stylesheet and inspector.css variables. */\n:host { display:block; font:var(--tree-font-size,11px)/var(--tree-row-height,18px) -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; color:#38414b; }\n:host([hidden]) { display:none; }\nul { list-style:none; margin:0; padding-left:17px; }\n.tree-root { padding-left:calc(var(--tree-action-size,14px) + 2px); }\n.tree-root > ul { padding-left:2px; }\nsummary, .leaf { position:relative; }\nli { line-height:var(--tree-row-height,18px); }\nsummary { display:flex; align-items:center; gap:7px; padding:0 5px; cursor:pointer; user-select:none; list-style:none; }\nsummary::-webkit-details-marker { display:none; }\nsummary::before { content:''; width:5px; height:5px; border-right:1.5px solid #78818a; border-bottom:1.5px solid #78818a; transform:rotate(-45deg); flex:none; margin:0 3px; }\ndetails[open] > summary::before { transform:rotate(45deg); }\n.leaf { display:flex; align-items:center; gap:7px; padding:0 5px 0 23px; cursor:pointer; }\nsummary:hover,.leaf:hover { background:#f0f3f6; }\n.selected,.selected:hover { background:transparent; color:#315dbd; font-weight:700; border-radius:2px; }\n.caption { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n/* Reserve a left gutter; showing an action must not shift the node label. */\n.actions { position:absolute; left:calc(-1 * var(--tree-action-size,14px)); top:0; display:inline-flex; opacity:0; pointer-events:none; }\nsummary:hover > .actions,.leaf:hover > .actions,summary:focus-visible > .actions,.leaf:focus-visible > .actions,\n.actions:has(button:focus-visible),.actions:has(button.active) { opacity:1; pointer-events:auto; }\n.actions button { border:0; background:transparent; color:#80868b; font:inherit; font-size:11px; line-height:var(--tree-row-height,18px); width:var(--tree-action-size,14px); height:var(--tree-row-height,18px); padding:0; cursor:pointer; border-radius:3px; }\n.actions button:hover { background:#e1e5ed; }\n.actions button[data-action=breakpoint] { font-size:0; }\n.actions button[data-action=breakpoint]::before { content:''; display:inline-block; box-sizing:border-box; width:8px; height:8px; border:1.5px solid currentColor; border-radius:50%; vertical-align:middle; }\n.actions button.active { color:#b3261e; }\n.actions button.active.conditional { color:#f9ab00; }\n.actions button.active[data-action=breakpoint]::before { background:currentColor; }\n.actions button:focus-visible,summary:focus-visible,.leaf:focus-visible { outline:2px solid #356f9f; outline-offset:-2px; }\n\n@media (hover: none) { .actions { opacity:1; pointer-events:auto; } }\n";
708
+ var gramlot_tools_inspector_default = ":host { all: initial; color-scheme: light; font: 12px/1.4 system-ui, sans-serif; color: #38414b; }\n* { box-sizing: border-box; }\nbutton, input, select { font: inherit; color: inherit; }\nbutton { cursor: pointer; }\nbutton:focus-visible, input:focus-visible, select:focus-visible { outline: 1px solid #3574cb; outline-offset: -1px; }\n.tree { padding: 7px 3px; }\n.properties { background: white; }\n.properties-heading { font-size: 11px; display: flex; align-items: center; justify-content: space-between; height: 29px; padding: 0 8px; background: #f1f3f5; color: #6d7989; border-bottom: 1px solid #d1d7df; }\n.properties-heading button { background: white; border: 1px solid #c5cdda; border-radius: 2px; width: 23px; height: 23px; padding: 0; font-size: 18px; line-height: 20px; }\nfieldset { border: 0; margin: 0; padding: 0; min-width: 0; }\n.property-row { display: grid; grid-template-columns: minmax(95px, 30%) minmax(0, 1fr); min-height: 20px; border-bottom: 1px solid #d1d7df; font-size: 11px; }\n.property-name { display: flex; align-items: center; justify-content: flex-end; background: #888; color: white; font-weight: 700; padding: 1px 6px; border-right: 1px solid #d1d7df; overflow-wrap: anywhere; text-align: right; }\n.property-value { display: flex; align-items: center; min-width: 0; }\n.property-row input { border: 0; border-radius: 0; min-width: 0; width: 100%; height: 19px; padding: 1px 5px; background: transparent; }\n.property-name input { color: white; text-align: right; padding: 0; }\n.property-value input { flex: 1; }\n.property-value input[type=checkbox] { flex: none; width: 14px; height: 14px; margin: 2px 6px; }\n.property-value select { max-width: 82px; height: 20px; padding: 0; border: 0; border-left: 1px solid #d1d7df; background: #f1f3f5; font-size: 10px; }\n.remove { display: inline-flex; align-items: center; justify-content: center; margin-left: auto; flex: none; width: 22px; height: 20px; padding: 3px; border: 0; background: transparent; color: #7c8797; opacity: 0; }\n.remove svg { width: 13px; height: 13px; }\n.property-row:hover .remove, .property-row:focus-within .remove { opacity: 1; }\n.remove:hover { color: #b82323; background: #fbe9e9; }\n:disabled { cursor: default; opacity: .65; }\n.path { flex: none; padding: 5px 9px; border-top: 1px solid #c9cfd6; background: #f5f7f9; color: #697687; font-size: 11px; min-height: 27px; overflow-wrap: anywhere; }\n.error, .edit-status, .breakpoint-status, .breakpoint-line { font-size: 11px; margin: 0; padding: 3px 8px; overflow-wrap: anywhere; }\n.error, .edit-status, .breakpoint-status { color: #a12121; }\n.breakpoint-line { background: #fff2ed; color: #854127; border-bottom: 1px solid #d1d7df; }\n.error:empty, .edit-status:empty, .breakpoint-status:empty, .breakpoint-line:empty { display: none; }\n.breakpoint-dialog { width: min(340px, 90vw); color: #38414b; border: 1px solid #bbc3cf; border-radius: 4px; padding: 12px; box-shadow: 0 8px 28px #0003; font: 12px/1.4 system-ui, sans-serif; }\n.breakpoint-dialog::backdrop { background: #0002; }\n.breakpoint-dialog h2 { font-size: 13px; margin: 0 0 10px; overflow-wrap: anywhere; }\n.breakpoint-dialog select, .breakpoint-dialog input { width: 100%; border: 1px solid #bbc3cf; padding: 4px; margin: 0 0 7px; background: white; }\n.dialog-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 7px; }\n.dialog-actions button { padding: 3px 10px; border: 1px solid #bbc3cf; border-radius: 3px; background: #f1f3f5; }\n.heading-label { flex: 1; }\n.properties-heading .breakpoint-count { width: auto; padding: 0 5px; margin-right: 5px; font-size: 11px; color: #b04a35; }\n.breakpoint-entry { display: flex; align-items: center; gap: 6px; padding: 4px 0; border-bottom: 1px solid #d1d7df; }\n.breakpoint-entry span { flex: 1; overflow-wrap: anywhere; }\n.breakpoint-entry button { background: transparent; border: 0; font-size: 17px; }\n[hidden] { display: none !important; }\n.targetbar { flex: none; height: 28px; padding: 2px 6px; background: #edf2fa; border-bottom: 1px solid #dadce0; }\n.targetbar select { width: 100%; height: 23px; border: 0; background: transparent; font-size: 11px; }\n.styles-panel { flex: 1; min-height: 0; overflow: auto; background: #fff; font-size: 11px; }\n.styles-toolbar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 6px 8px; background: #f1f3f5; border-bottom: 1px solid #d1d7df; }\n.styles-toolbar span { flex: 1; font-weight: 600; }\n.styles-toolbar button, .styles-toolbar a { padding: 3px 7px; border: 1px solid #c5cdda; border-radius: 3px; background: white; }\n.styles-toolbar a { color: inherit; text-decoration: none; }\n.styles-toolbar a[aria-disabled=true] { cursor: default; opacity: .65; }\n.styles-toolbar a:focus-visible { outline: 1px solid #3574cb; outline-offset: -1px; }\n.styles-help, .styles-empty, .styles-status { margin: 0; padding: 7px 9px; color: #697687; }\n.styles-status { color: #a12121; }\n.styles-status:empty { display: none; }\n.styles-group h3 { margin: 0; padding: 6px 9px; font-size: 11px; background: #edf2fa; border-block: 1px solid #d1d7df; }\n.style-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 25px 23px; align-items: center; gap: 8px; min-width: 0; padding: 3px 9px; border-bottom: 1px solid #e8ebef; }\n.style-row label { min-width: 0; font: 11px/1.4 ui-monospace, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.style-row input { min-width: 0; width: 100%; height: 23px; padding: 2px 5px; border: 1px solid #c5cdda; border-radius: 3px; background: white; }\n.style-row input[type=color] { width: 25px; padding: 1px; cursor: pointer; }\n.style-row button { grid-column: 4; width: 23px; height: 23px; padding: 0; border: 1px solid #c5cdda; border-radius: 3px; background: #f5f7f9; font-size: 15px; }\n\n:host([embedded]) { display: block; height: 100%; min-height: 0; }\n:host([embedded]) > div { height: 100%; }\n.embedded-panel { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }\n\n.property-value select.enum-value { flex: 1; min-width: 0; max-width: none; width: 100%; border: 0; background: white; font: inherit; padding: 1px 5px; }\n\n/* Presentation changes preserve the inspector runtime and selection. */\n:host([hidden]) { display: none; }\n:host([docked]) { display: block; height: 100%; min-height: 0; }\n:host([docked][hidden]) { display: none; }\n:host([docked]) > div { height: 100%; }\n:host([docked]) .targetbar select { min-height: 40px; font-size: 16px; }\n:host([docked]) .property-row { min-height: 36px; font-size: 13px; }\n:host([docked]) .property-row input { min-height: 35px; font-size: 16px; }\n:host([docked]) .remove { opacity: 1; min-height: 35px; width: 32px; }\n\n:host([docked]) { --tree-action-size: 28px; --tree-font-size: 14px; --tree-row-height: 40px; --separator-size: 14px; }\n:host([docked]) .targetbar { height: 44px; }\n:host([docked]) gramlot-poc-tabs > button { height: 44px; font-size: 14px; }\n:host([docked]) .properties-heading { height: 40px; font-size: 13px; }\n:host([docked]) .properties-heading button { width: 36px; height: 36px; }\n:host([docked]) .property-value select { height: 35px; font-size: 14px; }\n\n.history-toolbar { flex:none; display:flex; align-items:center; gap:6px; padding:4px 8px; border-bottom:1px solid #d1d7df; background:#f6f8fb; font:11px/1.4 system-ui,sans-serif; }\n.history-toolbar button { flex:none; padding:3px 6px; border:1px solid #c5cdda; border-radius:3px; background:white; cursor:pointer; }\n.history-toolbar button:disabled { opacity:.45; cursor:default; }\n.history-toolbar span { min-width:0; color:#a12121; overflow-wrap:anywhere; }\n";
709
+ var gramlot_poc_tree_default = "/* Copyright 2025-2026 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0\n * Inspector subset of the DevTools storeTree stylesheet and inspector.css variables. */\n:host { display:block; font:var(--tree-font-size,11px)/var(--tree-row-height,18px) -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; color:#38414b; }\n:host([hidden]) { display:none; }\nul { list-style:none; margin:0; padding-left:17px; }\n.tree-root { padding-left:calc(var(--tree-action-size,14px) + 2px); }\n.tree-root > ul { padding-left:2px; }\nsummary, .leaf { position:relative; }\nli { line-height:var(--tree-row-height,18px); }\nsummary { display:flex; align-items:center; gap:7px; padding:0 5px; cursor:pointer; user-select:none; list-style:none; }\nsummary::-webkit-details-marker { display:none; }\nsummary::before { content:''; width:5px; height:5px; border-right:1.5px solid #78818a; border-bottom:1.5px solid #78818a; transform:rotate(-45deg); flex:none; margin:0 3px; }\ndetails[open] > summary::before { transform:rotate(45deg); }\n.leaf { display:flex; align-items:center; gap:7px; padding:0 5px 0 23px; cursor:pointer; }\nsummary:hover,.leaf:hover { background:#f0f3f6; }\n.selected,.selected:hover { background:transparent; color:#315dbd; font-weight:700; border-radius:2px; }\n.caption { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n/* Reserve a left gutter; showing an action must not shift the node label. */\n.actions { position:absolute; left:calc(-1 * var(--tree-action-size,14px)); top:0; display:inline-flex; opacity:0; pointer-events:none; }\nsummary:hover > .actions,.leaf:hover > .actions,summary:focus-visible > .actions,.leaf:focus-visible > .actions,\n.actions:has(button:focus-visible),.actions:has(button.active) { opacity:1; pointer-events:auto; }\n.actions button { border:0; background:transparent; color:#80868b; font:inherit; font-size:11px; line-height:var(--tree-row-height,18px); width:var(--tree-action-size,14px); height:var(--tree-row-height,18px); padding:0; cursor:pointer; border-radius:3px; }\n.actions button:hover { background:#e1e5ed; }\n.actions button[data-action=breakpoint] { font-size:0; }\n.actions button[data-action=breakpoint]::before { content:''; display:inline-block; box-sizing:border-box; width:8px; height:8px; border:1.5px solid currentColor; border-radius:50%; vertical-align:middle; }\n.actions button.active { color:#b3261e; }\n.actions button.active.conditional { color:#f9ab00; }\n.actions button.active[data-action=breakpoint]::before { background:currentColor; }\n.actions button:focus-visible,summary:focus-visible,.leaf:focus-visible { outline:2px solid #356f9f; outline-offset:-2px; }\n\n@media (hover: none) { .actions { opacity:1; pointer-events:auto; } }\n\n.actions-end { position:static; margin-left:auto; flex:none; }\n[data-drop=inside] { outline:2px solid #356f9f; outline-offset:-2px; background:#e7f0fc; }\n[data-drop=before] { box-shadow:inset 0 2px #356f9f; }\n[data-drop=after] { box-shadow:inset 0 -2px #356f9f; }\n";
456
710
  var sequence = 0;
457
711
  var GramlotPocTree = class extends HTMLElement {
458
712
  constructor() {
@@ -468,6 +722,9 @@
468
722
  this._expanded = /* @__PURE__ */ new Set();
469
723
  this._nodes = /* @__PURE__ */ new Map();
470
724
  this._rowActions = [];
725
+ this.dragEnabled = false;
726
+ this.fixedPaths = [];
727
+ this._drag = null;
471
728
  this._pendingRender = null;
472
729
  this._subId = `gramlot-poc-tree-${++sequence}`;
473
730
  }
@@ -495,6 +752,12 @@
495
752
  this._selectedPath = path;
496
753
  if (this.isConnected) this._markSelection();
497
754
  }
755
+ revealPath(path) {
756
+ for (const details of this._root.querySelectorAll("details")) {
757
+ if (path.startsWith(details.dataset.path + ".")) details.open = true;
758
+ }
759
+ this.selectedPath = path;
760
+ }
498
761
  get rowActions() {
499
762
  return this._rowActions;
500
763
  }
@@ -524,12 +787,22 @@
524
787
  }
525
788
  _render() {
526
789
  this._pendingRender = null;
790
+ const focused = this.shadowRoot.activeElement;
791
+ const focusedPath = focused?.closest("[data-node-path]")?.dataset.nodePath;
792
+ const focusedAction = focused?.dataset.action;
527
793
  for (const details of this._root.querySelectorAll("details")) {
528
794
  if (details.open) this._expanded.add(details.dataset.path);
529
795
  else this._expanded.delete(details.dataset.path);
530
796
  }
531
797
  this._nodes.clear();
532
798
  this._root.replaceChildren(...this._store ? [this._buildLevel(this._store, "", /* @__PURE__ */ new Set())] : []);
799
+ if (focusedPath) {
800
+ let path = focusedPath;
801
+ while (path && !this._nodes.has(path)) path = path.includes(".") ? path.slice(0, path.lastIndexOf(".")) : "";
802
+ const row = [...this._root.querySelectorAll("[data-node-path]")].find((item) => item.dataset.nodePath === path);
803
+ const action = focusedAction && row && [...row.querySelectorAll("button")].find((button) => button.dataset.action === focusedAction);
804
+ (action || row)?.focus({ preventScroll: true });
805
+ }
533
806
  if (this._selectedPath !== null && !this._nodes.has(this._selectedPath)) {
534
807
  this._selectedPath = null;
535
808
  this._notifySelection();
@@ -572,35 +845,91 @@
572
845
  row.append(caption);
573
846
  row.addEventListener("click", () => this._select(path));
574
847
  this._actions(row, path, node);
848
+ this._dragEvents(row, path, node);
575
849
  list.append(item);
576
850
  }
577
851
  return list;
578
852
  }
579
853
  _actions(row, path, node) {
580
854
  if (!this._rowActions.length) return;
581
- const actions = this.ownerDocument.createElement("span");
582
- actions.className = "actions";
583
- for (const action of this._rowActions) {
584
- const button = this.ownerDocument.createElement("button");
585
- button.type = "button";
586
- button.textContent = action.icon ?? action.label;
587
- button.title = action.label;
588
- button.setAttribute("aria-label", action.label);
589
- button.dataset.action = action.id;
590
- button.classList.toggle("active", action.activePaths?.includes(path) ?? false);
591
- button.classList.toggle("conditional", action.conditionalPaths?.includes(path) ?? false);
592
- button.addEventListener("click", (event) => {
593
- event.preventDefault();
594
- event.stopPropagation();
595
- this.dispatchEvent(new this.ownerDocument.defaultView.CustomEvent("tree-action", {
596
- bubbles: true,
597
- composed: true,
598
- detail: { action: action.id, path, node }
599
- }));
600
- });
601
- actions.append(button);
855
+ for (const position of ["start", "end"]) {
856
+ const actions = this.ownerDocument.createElement("span");
857
+ actions.className = `actions actions-${position}`;
858
+ for (const action of this._rowActions) {
859
+ if ((action.position ?? "start") !== position || action.hiddenPaths?.includes(path)) continue;
860
+ const button = this.ownerDocument.createElement("button");
861
+ button.type = "button";
862
+ button.textContent = action.icon ?? action.label;
863
+ button.title = action.label;
864
+ button.setAttribute("aria-label", action.label);
865
+ button.dataset.action = action.id;
866
+ button.classList.toggle("active", action.activePaths?.includes(path) ?? false);
867
+ button.classList.toggle("conditional", action.conditionalPaths?.includes(path) ?? false);
868
+ button.addEventListener("click", (event) => {
869
+ event.preventDefault();
870
+ event.stopPropagation();
871
+ this.dispatchEvent(new this.ownerDocument.defaultView.CustomEvent("tree-action", {
872
+ bubbles: true,
873
+ composed: true,
874
+ detail: { action: action.id, path, node }
875
+ }));
876
+ });
877
+ actions.append(button);
878
+ }
879
+ if (actions.childElementCount) row.append(actions);
602
880
  }
603
- row.append(actions);
881
+ }
882
+ _clearDrop() {
883
+ for (const row of this._root.querySelectorAll("[data-drop]")) delete row.dataset.drop;
884
+ }
885
+ _dragEvents(row, path, node) {
886
+ if (!this.dragEnabled) return;
887
+ row.draggable = !this.fixedPaths.includes(path);
888
+ row.addEventListener("dragstart", (event) => {
889
+ if (!row.draggable || event.target.closest("button")) {
890
+ event.preventDefault();
891
+ return;
892
+ }
893
+ event.stopPropagation();
894
+ this._drag = { path, node };
895
+ event.dataTransfer.effectAllowed = "move";
896
+ event.dataTransfer.setData("application/x-gramlot-source", this._subId);
897
+ });
898
+ const placement = (event) => {
899
+ const rect = row.getBoundingClientRect();
900
+ const fraction = (event.clientY - rect.top) / rect.height;
901
+ return this.fixedPaths.includes(path) ? "inside" : fraction < 0.25 ? "before" : fraction > 0.75 ? "after" : "inside";
902
+ };
903
+ const valid = () => this._drag && this._drag.path !== path && !path.startsWith(this._drag.path + ".");
904
+ row.addEventListener("dragover", (event) => {
905
+ if (!valid()) return;
906
+ event.preventDefault();
907
+ event.stopPropagation();
908
+ event.dataTransfer.dropEffect = "move";
909
+ this._clearDrop();
910
+ row.dataset.drop = placement(event);
911
+ });
912
+ row.addEventListener("dragleave", () => {
913
+ delete row.dataset.drop;
914
+ });
915
+ row.addEventListener("drop", (event) => {
916
+ event.preventDefault();
917
+ event.stopPropagation();
918
+ this._clearDrop();
919
+ if (!valid() || event.dataTransfer.getData("application/x-gramlot-source") !== this._subId) return;
920
+ const source = this._drag;
921
+ this._drag = null;
922
+ if (this._store.getNode(source.path) !== source.node) return;
923
+ this.dispatchEvent(new this.ownerDocument.defaultView.CustomEvent("tree-drop", {
924
+ bubbles: true,
925
+ composed: true,
926
+ detail: { path: source.path, destination: path, placement: placement(event) }
927
+ }));
928
+ });
929
+ row.addEventListener("dragend", () => {
930
+ this._drag = null;
931
+ this._clearDrop();
932
+ });
604
933
  }
605
934
  _markSelection() {
606
935
  for (const row of this._root.querySelectorAll("[data-node-path]")) {
@@ -777,6 +1106,9 @@
777
1106
  }
778
1107
  }
779
1108
  #target = null;
1109
+ #history = null;
1110
+ #insertTarget = null;
1111
+ #catalog = [];
780
1112
  #connection = null;
781
1113
  #connectionRelease = null;
782
1114
  #targets = [];
@@ -1020,6 +1352,67 @@
1020
1352
  inspector.#applyEdit(choose);
1021
1353
  } else choose();
1022
1354
  }
1355
+ sourceAction(node, event) {
1356
+ if (event.detail.action === "breakpoint") {
1357
+ this.openBreakpoint(node, event);
1358
+ return;
1359
+ }
1360
+ const run = () => {
1361
+ if (event.detail.action === "insert") inspector.#openInsert(event.detail.path);
1362
+ if (event.detail.action === "remove") {
1363
+ try {
1364
+ inspector.#runCommand("remove", inspector.#sourceCommand(event.detail.path));
1365
+ } catch (error) {
1366
+ ui.data.setItem("status", error.message);
1367
+ }
1368
+ }
1369
+ };
1370
+ if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(run);
1371
+ else run();
1372
+ }
1373
+ sourceDrop(node, event) {
1374
+ const run = () => {
1375
+ try {
1376
+ const { path, destination, placement } = event.detail;
1377
+ const command = inspector.#sourceCommand(path);
1378
+ const target = inspector.#sourceCommand(destination);
1379
+ inspector.#runCommand("move", { ...command, destination, placement, targetId: target.nodeId, targetRevision: target.revision });
1380
+ } catch (error) {
1381
+ ui.data.setItem("status", error.message);
1382
+ }
1383
+ };
1384
+ if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(run);
1385
+ else run();
1386
+ }
1387
+ insertKind() {
1388
+ inspector.#insertChoices();
1389
+ }
1390
+ addElement() {
1391
+ inspector.#runCommand("insert", { ...inspector.#insertTarget, tag: ui.data.getItem("insert.tag"), attrs: Object.fromEntries((inspector.#catalog[0]?.fields ?? []).map(({ name }) => [name, ui.data.getItem(`insert.attrs.${name}`)])) });
1392
+ }
1393
+ closeInsert() {
1394
+ inspector.#dialog("insert").close();
1395
+ inspector.#insertTarget = null;
1396
+ }
1397
+ undo() {
1398
+ if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(() => inspector.#runCommand("undo"));
1399
+ else inspector.#runCommand("undo");
1400
+ }
1401
+ redo() {
1402
+ if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(() => inspector.#runCommand("redo"));
1403
+ else inspector.#runCommand("redo");
1404
+ }
1405
+ historyKey(node, event) {
1406
+ if (!(event.metaKey || event.ctrlKey) || event.altKey || event.target.closest("input,textarea,select,[contenteditable]")) return;
1407
+ if (event.key.toLowerCase() === "z") {
1408
+ event.preventDefault();
1409
+ event.shiftKey ? this.redo() : this.undo();
1410
+ }
1411
+ if (event.key.toLowerCase() === "y") {
1412
+ event.preventDefault();
1413
+ this.redo();
1414
+ }
1415
+ }
1023
1416
  openBreakpoint(node, event) {
1024
1417
  if (event.detail.action !== "breakpoint") return;
1025
1418
  ui.data.setItem("breakpointDirty", false);
@@ -1089,6 +1482,8 @@
1089
1482
  breakpointCount: "\u25CF 0",
1090
1483
  noBreakpoints: true
1091
1484
  })) ui.data.setItem(key, value);
1485
+ ui.data.setItem("history", new Bag({ noUndo: true, noRedo: true, undo: "", redo: "" }));
1486
+ ui.data.setItem("insert", new Bag({ kind: "html", tag: "", html: true, component: false, status: "" }));
1092
1487
  ui.data.setItem("editor", new Bag({ dirty: false, disabled: true, status: "", mode: "", condition: "" }));
1093
1488
  ui.data.setItem("breakpoints", new Bag());
1094
1489
  ui.data.setItem("breakpointDirty", false);
@@ -1096,7 +1491,7 @@
1096
1491
  const builder = new GramlotBuilder();
1097
1492
  const root = builder.root;
1098
1493
  root.style(gramlot_tools_inspector_default);
1099
- const palette = this.hasAttribute("embedded") ? root.section({ node_label: "palette", class: "embedded-panel", "aria-label": "Gramlot inspector" }) : root.webcomponent({ code: "gramlot-poc-palette", node_label: "palette", title: "Gramlot inspector", docked: this.hasAttribute("docked") ? "" : null, connect_onclose: "inspector.close" });
1494
+ const palette = this.hasAttribute("embedded") ? root.section({ node_label: "palette", class: "embedded-panel", "aria-label": "Gramlot inspector", connect_onkeydown: "inspector.historyKey" }) : root.webcomponent({ code: "gramlot-poc-palette", node_label: "palette", title: "Gramlot inspector", docked: this.hasAttribute("docked") ? "" : null, connect_onclose: "inspector.close", connect_onkeydown: "inspector.historyKey" });
1100
1495
  const targetbar = palette.div({ node_label: "targetbar", class: "targetbar", hidden: "^singleTarget" });
1101
1496
  targetbar.select({ node_label: "targets", value: "^targetId", disabled: "^busy", "aria-label": "Inspected page", connect_onchange: "inspector.selectTarget" });
1102
1497
  const toolbar = palette.webcomponent({ code: "gramlot-poc-tabs", node_label: "tabs", role: "tablist", "aria-label": "Inspected root" });
@@ -1108,6 +1503,10 @@
1108
1503
  "aria-selected": "false",
1109
1504
  connect_onclick: "inspector.selectRoot"
1110
1505
  });
1506
+ const historybar = palette.div({ class: "history-toolbar", hidden: "^stylesActive" });
1507
+ historybar.html_button("\u21B6 Undo", { type: "button", disabled: "^history.noUndo", title: "^history.undo", connect_onclick: "inspector.undo" });
1508
+ historybar.html_button("\u21B7 Redo", { type: "button", disabled: "^history.noRedo", title: "^history.redo", connect_onclick: "inspector.redo" });
1509
+ historybar.span("^status", { role: "status" });
1111
1510
  palette.p("^error", { role: "alert", class: "error" });
1112
1511
  const content = palette.webcomponent({ code: "gramlot-poc-splitter", node_label: "content", hidden: "^stylesActive" });
1113
1512
  const trees = content.div({ node_label: "tree", slot: "top", class: "tree" });
@@ -1117,7 +1516,8 @@
1117
1516
  "data-root": name,
1118
1517
  hidden: name === "data" ? "^sourceActive" : "^dataActive",
1119
1518
  connect_onchange: "inspector.selectNode",
1120
- "connect_ontree-action": "inspector.openBreakpoint"
1519
+ "connect_ontree-action": "inspector.sourceAction",
1520
+ "connect_ontree-drop": "inspector.sourceDrop"
1121
1521
  });
1122
1522
  const properties = content.section({ node_label: "properties", slot: "bottom", class: "properties", "aria-label": "Selected node properties" });
1123
1523
  const heading = properties.div({ class: "properties-heading" });
@@ -1169,6 +1569,19 @@
1169
1569
  list.h2("Active breakpoints");
1170
1570
  list.div({ node_label: "entries" });
1171
1571
  list.div({ class: "dialog-actions" }).html_button("Close", { type: "button", connect_onclick: "inspector.closeBreakpoints" });
1572
+ const insert = palette.dialog({ node_label: "insert", "aria-label": "Add Source element", class: "breakpoint-dialog", connect_oncancel: "inspector.closeInsert" });
1573
+ insert.h2("Add a child element");
1574
+ const kind = insert.select({ value: "^insert.kind", "aria-label": "Element kind", connect_onchange: "inspector.insertKind" });
1575
+ kind.option("HTML tag", { value: "html" });
1576
+ kind.option("Widget or container", { value: "component" });
1577
+ insert.select({ node_label: "components", value: "^insert.tag", hidden: "^insert.html", "aria-label": "Widget or container" });
1578
+ insert.input({ value: "^insert.tag", live: true, hidden: "^insert.component", list: `${this.#subscriber}-tags`, "aria-label": "HTML tag", placeholder: "div, span\u2026" });
1579
+ insert.datalist({ node_label: "tags", id: `${this.#subscriber}-tags` });
1580
+ insert.div({ node_label: "fields" });
1581
+ insert.p("^insert.status", { role: "status" });
1582
+ const insertActions = insert.div({ class: "dialog-actions" });
1583
+ insertActions.html_button("Cancel", { type: "button", connect_onclick: "inspector.closeInsert" });
1584
+ insertActions.html_button("Add", { type: "button", disabled: "^busy", connect_onclick: "inspector.addElement" });
1172
1585
  ui.src.startSource(builder.source);
1173
1586
  for (const name of ["data", "source"]) this.#trees[name] = ui.dom.getDomNode(ui.src.source.getNode(`main.palette.content.tree.${name}`));
1174
1587
  this.#renderTargets();
@@ -1194,6 +1607,7 @@
1194
1607
  return;
1195
1608
  }
1196
1609
  if (!this.#target) return;
1610
+ this.#history = new InspectorHistory(this.#target);
1197
1611
  this.#subscriptions = [this.#target.data, this.#target.src.source];
1198
1612
  for (const [index, bag] of this.#subscriptions.entries()) bag.subscribe(this.#subscriber, { any: (event) => {
1199
1613
  this.#checkBreakpoints(index === 0 ? "data" : "source", event);
@@ -1201,6 +1615,10 @@
1201
1615
  } });
1202
1616
  }
1203
1617
  #unsubscribe() {
1618
+ this.#history?.clear();
1619
+ this.#history = null;
1620
+ this.#insertTarget = null;
1621
+ if (this.#ui) this.#dialog("insert").close();
1204
1622
  ++this.#styleRequest;
1205
1623
  this.#styles?.dispose();
1206
1624
  this.#styles = null;
@@ -1247,6 +1665,11 @@
1247
1665
  ui.data.setItem("breakpoints", configurations);
1248
1666
  ui.data.setItem("breakpointError", this.#connection.breakpoints.filter((record) => record.error).map((record) => `${record.root}: ${record.path}: ${record.error}`).join("; "));
1249
1667
  }
1668
+ const history = this.#connection ? this.#connection.history : this.#history?.state;
1669
+ for (const action of ["undo", "redo"]) {
1670
+ ui.data.setItem(`history.${action}`, history?.[action] ?? "");
1671
+ ui.data.setItem(`history.no${action === "undo" ? "Undo" : "Redo"}`, !history?.[action] || ui.data.getItem("busy") || this.#state() !== "started");
1672
+ }
1250
1673
  const count = ui.data.getItem("breakpoints").getNodes().length;
1251
1674
  ui.data.setItem("breakpointCount", `\u25CF ${count}`);
1252
1675
  ui.data.setItem("noBreakpoints", count === 0);
@@ -1256,13 +1679,18 @@
1256
1679
  const tree = this.#trees[name];
1257
1680
  tree.storeBag = this.#connection || this.#target ? this.#targetBag(name) : null;
1258
1681
  const breakpoints = ui.data.getItem("breakpoints").getNodes().filter((node) => node.value.getItem("root") === name);
1682
+ tree.dragEnabled = name === "source";
1683
+ tree.fixedPaths = tree.storeBag?.getNodes().map((node) => node.label) ?? [];
1259
1684
  tree.rowActions = [{
1260
1685
  id: "breakpoint",
1261
1686
  icon: "\u25CB",
1262
1687
  label: "Breakpoint",
1263
1688
  activePaths: breakpoints.map((node) => node.value.getItem("path")),
1264
1689
  conditionalPaths: breakpoints.filter((node) => node.value.getItem("condition").trim()).map((node) => node.value.getItem("path"))
1265
- }];
1690
+ }, ...name === "source" ? [
1691
+ { id: "insert", icon: "+", label: "Add child element", position: "end" },
1692
+ { id: "remove", icon: "\u2212", label: "Remove branch", position: "end", hiddenPaths: tree.fixedPaths }
1693
+ ] : []];
1266
1694
  }
1267
1695
  if (this.#target || this.#connection) this.#index(this.#bag(), []);
1268
1696
  this.#trees[root].selectedPath = this.#selected()?.path.join(".") ?? null;
@@ -1632,8 +2060,7 @@ ${declarations}
1632
2060
  });
1633
2061
  return false;
1634
2062
  }
1635
- if (changed) target.setAttr(attrs, true, false, false);
1636
- if (!Object.is(value, this.#snapshot.value)) target.setValue(value);
2063
+ this.#history.edit(target, attrs, value, changed, !Object.is(value, this.#snapshot.value));
1637
2064
  this.#acceptEdit(target);
1638
2065
  this.#refresh();
1639
2066
  this.#ui.data.setItem("editor.status", "");
@@ -1644,6 +2071,91 @@ ${declarations}
1644
2071
  return false;
1645
2072
  }
1646
2073
  }
2074
+ #sourceCommand(path) {
2075
+ const node = this.#targetBag("source").getNode(path);
2076
+ if (!node) throw new Error("Source node no longer exists");
2077
+ const info = this.#connection?.nodeInfo(node);
2078
+ return { path, nodeId: info?.id, revision: info?.revision, ...this.#connection ? {} : { node } };
2079
+ }
2080
+ async #openInsert(path) {
2081
+ if (this.#ui.data.getItem("busy")) return;
2082
+ const generation = this.#generation;
2083
+ try {
2084
+ this.#insertTarget = this.#sourceCommand(path);
2085
+ this.#catalog = this.#connection ? (await this.#connection.request("catalog", { path })).catalog : this.#history.catalog(path);
2086
+ if (!this.#ui || generation !== this.#generation) return;
2087
+ this.#ui.data.setItem("insert.kind", "html");
2088
+ this.#ui.data.setItem("insert.status", "");
2089
+ this.#insertChoices();
2090
+ const builder = new GramlotBuilder();
2091
+ this.#ui.data.setItem("insert.attrs", new Bag());
2092
+ for (const { name, choices } of this.#catalog[0]?.fields ?? []) {
2093
+ builder.root.html_label(name);
2094
+ const field = builder.root.select({ value: `^insert.attrs.${name}`, "aria-label": name });
2095
+ field.option(`Choose ${name}\u2026`, { value: "", disabled: true });
2096
+ for (const value of choices) field.option(value, { value });
2097
+ this.#ui.data.setItem(`insert.attrs.${name}`, "");
2098
+ }
2099
+ this.#ui.src.source.getNode("main.palette.insert.fields").setValue(this.#ui.src.prepareSource(builder.source));
2100
+ this.#dialog("insert").showModal();
2101
+ } catch (error) {
2102
+ if (this.#ui && generation === this.#generation) this.#ui.data.setItem("status", error.message);
2103
+ }
2104
+ }
2105
+ #insertChoices() {
2106
+ const html = this.#ui.data.getItem("insert.kind") === "html";
2107
+ this.#ui.data.setItem("insert.html", html);
2108
+ this.#ui.data.setItem("insert.component", !html);
2109
+ const choices = this.#catalog.filter((item) => item.kind === (html ? "html" : "component"));
2110
+ const builder = new GramlotBuilder();
2111
+ for (const item of choices) builder.root.option(item.tag, { value: item.tag });
2112
+ this.#ui.src.source.getNode(`main.palette.insert.${html ? "tags" : "components"}`).setValue(this.#ui.src.prepareSource(builder.source));
2113
+ this.#ui.data.setItem("insert.tag", html ? "" : choices[0]?.tag ?? "");
2114
+ }
2115
+ async #runCommand(type, command = {}) {
2116
+ const ui = this.#ui;
2117
+ if (ui.data.getItem("busy")) return;
2118
+ const generation = this.#generation;
2119
+ ui.data.setItem("busy", true);
2120
+ ui.data.setItem("status", "");
2121
+ ui.data.setItem("insert.status", "");
2122
+ try {
2123
+ let selection;
2124
+ if (this.#connection) ({ selection } = await this.#connection.request(type, command));
2125
+ else {
2126
+ if (command.path && this.#targetBag("source").getNode(command.path) !== command.node) throw new Error("Source node changed. Select it again.");
2127
+ if (type === "insert") selection = this.#history.insert(command.path, command.tag, command.attrs);
2128
+ if (type === "remove") selection = this.#history.remove(command.path);
2129
+ if (type === "move") selection = this.#history.move(command.path, command.destination, command.placement);
2130
+ if (type === "undo") this.#history.undo();
2131
+ if (type === "redo") this.#history.redo();
2132
+ }
2133
+ if (!this.#ui || generation !== this.#generation) return;
2134
+ if (type === "insert") this.#dialog("insert").close();
2135
+ this.#snapshot = null;
2136
+ ui.data.setItem("editor.dirty", false);
2137
+ this.#refresh();
2138
+ if (selection) {
2139
+ ui.data.setItem("root", "source");
2140
+ ui.data.setItem("tab", "source");
2141
+ this.#refresh();
2142
+ const entry = this.#nodes.find((item) => item.path.join(".") === selection);
2143
+ ui.data.setItem("selection", entry?.id ?? null);
2144
+ this.#trees.source.revealPath(selection);
2145
+ this.#refresh();
2146
+ }
2147
+ } catch (error) {
2148
+ if (this.#ui && generation === this.#generation) {
2149
+ ui.data.setItem("status", error.message);
2150
+ ui.data.setItem("insert.status", error.message);
2151
+ }
2152
+ } finally {
2153
+ if (this.#ui && generation === this.#generation) {
2154
+ ui.data.setItem("busy", false);
2155
+ this.#refresh();
2156
+ }
2157
+ }
2158
+ }
1647
2159
  #findBreakpoint(root, path) {
1648
2160
  return this.#ui.data.getItem("breakpoints").getNodes().find((node) => {
1649
2161
  const value = node.getValue(true);
@@ -1801,7 +2313,7 @@ ${declarations}
1801
2313
  }
1802
2314
 
1803
2315
  // src/gramlot_examples/gallery/dist/theme-styles.js
1804
- var appearanceStyles2 = { "base": "", "compact": '/* Gramlot Compact: load after gramlot-base/theme.css. Colors and behavior stay in Base. */\n@layer gramlot.tokens {\n :root {\n --gramlot-field-height: 28px;\n --gramlot-field-line-height: 1.35;\n --gramlot-field-padding: .25rem .5rem;\n --gramlot-field-font-size: 1rem;\n --gramlot-space-1: .2rem;\n --gramlot-space-2: .4rem;\n --gramlot-space-3: .5rem;\n --gramlot-space-4: .6rem;\n --gramlot-space-6: .9rem;\n --gramlot-space-8: 1.2rem;\n --gramlot-space-12: 1.8rem;\n --gramlot-radius-sm: .25rem;\n --gramlot-radius: .35rem;\n --gramlot-radius-lg: .4rem;\n }\n}\n\n@layer gramlot.base {\n html { font-size: 13px; }\n body { line-height: 1.35; }\n :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, blockquote, pre, figure) { margin-block: 0 .5rem; }\n h1 { font-size: 1.35rem; }\n h2 { font-size: 1.15rem; }\n h3 { font-size: 1.05rem; }\n pre { padding: .5rem; }\n blockquote { padding-inline-start: .6rem; }\n hr { margin-block: 1rem; }\n}\n\n@layer gramlot.components {\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) {\n min-block-size: 28px;\n padding: .25rem .5rem;\n font-size: 1rem;\n line-height: 1.35;\n }\n :where(label) { font-size: .95rem; }\n :where(input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]), select, textarea) {\n min-block-size: var(--gramlot-field-height);\n padding: var(--gramlot-field-padding);\n font-size: var(--gramlot-field-font-size);\n line-height: var(--gramlot-field-line-height);\n }\n :where(textarea) { min-block-size: 5rem; }\n :where(input[type="color"]) { min-block-size: 28px; }\n :where(input[type="file"])::file-selector-button { margin-inline-end: .5rem; padding: .25rem .5rem; }\n :where(fieldset) { margin-block-end: .6rem; padding: .5rem; }\n :where(table) { font-size: 1rem; }\n :where(th, td) { padding: .4rem .5rem; }\n :where(details) { padding: .4rem .5rem; }\n :where(details[open] > summary) { margin-block-end: .5rem; }\n :where(dialog) { padding: .75rem; }\n}\n\n@layer gramlot.utilities {\n .example-page { inline-size: min(100% - 1.2rem, var(--gramlot-page-width)); padding-block: .6rem; }\n .card { padding: .6rem; box-shadow: none; }\n}\n', "mimi": '/* Gramlot Compact: load after gramlot-base/theme.css. Colors and behavior stay in Base. */\n@layer gramlot.tokens {\n :root {\n --gramlot-field-height: 28px;\n --gramlot-field-line-height: 1.35;\n --gramlot-field-padding: .25rem .5rem;\n --gramlot-field-font-size: 1rem;\n --gramlot-space-1: .2rem;\n --gramlot-space-2: .4rem;\n --gramlot-space-3: .5rem;\n --gramlot-space-4: .6rem;\n --gramlot-space-6: .9rem;\n --gramlot-space-8: 1.2rem;\n --gramlot-space-12: 1.8rem;\n --gramlot-radius-sm: .25rem;\n --gramlot-radius: .35rem;\n --gramlot-radius-lg: .4rem;\n }\n}\n\n@layer gramlot.base {\n html { font-size: 13px; }\n body { line-height: 1.35; }\n :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, blockquote, pre, figure) { margin-block: 0 .5rem; }\n h1 { font-size: 1.35rem; }\n h2 { font-size: 1.15rem; }\n h3 { font-size: 1.05rem; }\n pre { padding: .5rem; }\n blockquote { padding-inline-start: .6rem; }\n hr { margin-block: 1rem; }\n}\n\n@layer gramlot.components {\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) {\n min-block-size: 28px;\n padding: .25rem .5rem;\n font-size: 1rem;\n line-height: 1.35;\n }\n :where(label) { font-size: .95rem; }\n :where(input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]), select, textarea) {\n min-block-size: var(--gramlot-field-height);\n padding: var(--gramlot-field-padding);\n font-size: var(--gramlot-field-font-size);\n line-height: var(--gramlot-field-line-height);\n }\n :where(textarea) { min-block-size: 5rem; }\n :where(input[type="color"]) { min-block-size: 28px; }\n :where(input[type="file"])::file-selector-button { margin-inline-end: .5rem; padding: .25rem .5rem; }\n :where(fieldset) { margin-block-end: .6rem; padding: .5rem; }\n :where(table) { font-size: 1rem; }\n :where(th, td) { padding: .4rem .5rem; }\n :where(details) { padding: .4rem .5rem; }\n :where(details[open] > summary) { margin-block-end: .5rem; }\n :where(dialog) { padding: .75rem; }\n}\n\n@layer gramlot.utilities {\n .example-page { inline-size: min(100% - 1.2rem, var(--gramlot-page-width)); padding-block: .6rem; }\n .card { padding: .6rem; box-shadow: none; }\n}\n\n/* Gramlot adaptation of legacy mimi; load after Base and Compact. */\n@layer gramlot.tokens {\n :root {\n --gramlot-background: #EDEEF0;\n --gramlot-surface: #F5F5F6;\n --gramlot-surface-subtle: #E0E1E3;\n --gramlot-ink: #3C3D40;\n --gramlot-text-secondary: #6B6E72;\n --gramlot-border: #BDBFC2;\n --gramlot-border-control: #9EA0A3;\n --gramlot-navigation: #48494C;\n --gramlot-navigation-text: #FFFFFF;\n --gramlot-action: #0071E3;\n --gramlot-action-hover: #005BBB;\n --gramlot-brand-blue: #0071E3;\n --gramlot-brand-yellow: #9EA0A3;\n --gramlot-selected: #E8F1FC;\n --gramlot-focus-ring: #0071E3;\n --gramlot-field-background: #FFFFFF;\n --gramlot-field-readonly-background: #E0E1E3;\n --gramlot-error: #B3261E;\n --gramlot-error-background: #FFF1F0;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 6%);\n --gramlot-radius-sm: 4px;\n --gramlot-radius: 3px;\n --gramlot-radius-lg: 3px;\n --gramlot-field-height: 18px;\n --gramlot-field-padding: 1px 3px;\n --gramlot-field-line-height: 1.15;\n --gramlot-field-font-size: 1rem;\n --gramlot-label-size: .9em;\n --gramlot-label-gap: 2px;\n --gramlot-label-color: var(--gramlot-text-secondary);\n --gramlot-space-4: 6px;\n --gramlot-font-ui: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme="light"]) {\n --gramlot-background: #242527;\n --gramlot-surface: #303134;\n --gramlot-surface-subtle: #3C3D40;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #6B6E72;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3C3D40;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n }\n :root[data-theme="dark"] {\n --gramlot-background: #242527;\n --gramlot-surface: #303134;\n --gramlot-surface-subtle: #3C3D40;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #6B6E72;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3C3D40;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n}\n@layer gramlot.base {\n html { font-size: 12px; }\n}\n@layer gramlot.components {\n :where(label) { font-size: var(--gramlot-label-size); font-weight: normal; color: var(--gramlot-label-color); margin-block-end: var(--gramlot-label-gap); }\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) { min-block-size: 24px; padding: 2px 8px; font-weight: 500; }\n}\n@layer gramlot.utilities {\n .card { box-shadow: var(--gramlot-shadow); }\n}\n', "joanna": '/* Gramlot Compact: load after gramlot-base/theme.css. Colors and behavior stay in Base. */\n@layer gramlot.tokens {\n :root {\n --gramlot-field-height: 28px;\n --gramlot-field-line-height: 1.35;\n --gramlot-field-padding: .25rem .5rem;\n --gramlot-field-font-size: 1rem;\n --gramlot-space-1: .2rem;\n --gramlot-space-2: .4rem;\n --gramlot-space-3: .5rem;\n --gramlot-space-4: .6rem;\n --gramlot-space-6: .9rem;\n --gramlot-space-8: 1.2rem;\n --gramlot-space-12: 1.8rem;\n --gramlot-radius-sm: .25rem;\n --gramlot-radius: .35rem;\n --gramlot-radius-lg: .4rem;\n }\n}\n\n@layer gramlot.base {\n html { font-size: 13px; }\n body { line-height: 1.35; }\n :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, blockquote, pre, figure) { margin-block: 0 .5rem; }\n h1 { font-size: 1.35rem; }\n h2 { font-size: 1.15rem; }\n h3 { font-size: 1.05rem; }\n pre { padding: .5rem; }\n blockquote { padding-inline-start: .6rem; }\n hr { margin-block: 1rem; }\n}\n\n@layer gramlot.components {\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) {\n min-block-size: 28px;\n padding: .25rem .5rem;\n font-size: 1rem;\n line-height: 1.35;\n }\n :where(label) { font-size: .95rem; }\n :where(input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]), select, textarea) {\n min-block-size: var(--gramlot-field-height);\n padding: var(--gramlot-field-padding);\n font-size: var(--gramlot-field-font-size);\n line-height: var(--gramlot-field-line-height);\n }\n :where(textarea) { min-block-size: 5rem; }\n :where(input[type="color"]) { min-block-size: 28px; }\n :where(input[type="file"])::file-selector-button { margin-inline-end: .5rem; padding: .25rem .5rem; }\n :where(fieldset) { margin-block-end: .6rem; padding: .5rem; }\n :where(table) { font-size: 1rem; }\n :where(th, td) { padding: .4rem .5rem; }\n :where(details) { padding: .4rem .5rem; }\n :where(details[open] > summary) { margin-block-end: .5rem; }\n :where(dialog) { padding: .75rem; }\n}\n\n@layer gramlot.utilities {\n .example-page { inline-size: min(100% - 1.2rem, var(--gramlot-page-width)); padding-block: .6rem; }\n .card { padding: .6rem; box-shadow: none; }\n}\n\n/* Gramlot adaptation of legacy joanna; load after Base and Compact. */\n@layer gramlot.tokens {\n :root {\n --gramlot-background: #F2F2F7;\n --gramlot-surface: #FFFFFF;\n --gramlot-surface-subtle: #F2F2F7;\n --gramlot-ink: #3A3A3C;\n --gramlot-text-secondary: #636366;\n --gramlot-border: #D1D1D6;\n --gramlot-border-control: #AEAEB2;\n --gramlot-navigation: #3A3A3C;\n --gramlot-navigation-text: #FFFFFF;\n --gramlot-action: #007AFF;\n --gramlot-action-hover: #0061CC;\n --gramlot-brand-blue: #007AFF;\n --gramlot-brand-yellow: #AEAEB2;\n --gramlot-selected: #E0EFFF;\n --gramlot-focus-ring: #007AFF;\n --gramlot-field-background: #FFFFFF;\n --gramlot-field-readonly-background: #F2F2F7;\n --gramlot-error: #B3261E;\n --gramlot-error-background: #FFF1F0;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 6%);\n --gramlot-radius-sm: 6px;\n --gramlot-radius: 6px;\n --gramlot-radius-lg: 6px;\n --gramlot-field-height: 20px;\n --gramlot-field-padding: 1px 3px;\n --gramlot-field-line-height: 1.15;\n --gramlot-field-font-size: 1rem;\n --gramlot-label-size: .8em;\n --gramlot-label-gap: 2px;\n --gramlot-label-color: var(--gramlot-text-secondary);\n --gramlot-space-4: 6px;\n --gramlot-font-ui: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme="light"]) {\n --gramlot-background: #1C1C1E;\n --gramlot-surface: #2C2C2E;\n --gramlot-surface-subtle: #3A3A3C;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #636366;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3A3A3C;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n }\n :root[data-theme="dark"] {\n --gramlot-background: #1C1C1E;\n --gramlot-surface: #2C2C2E;\n --gramlot-surface-subtle: #3A3A3C;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #636366;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3A3A3C;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n}\n@layer gramlot.base {\n html { font-size: 13px; }\n}\n@layer gramlot.components {\n :where(label) { font-size: var(--gramlot-label-size); font-weight: normal; color: var(--gramlot-label-color); margin-block-end: var(--gramlot-label-gap); }\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) { min-block-size: 26px; padding: 2px 8px; font-weight: 500; }\n}\n@layer gramlot.utilities {\n .card { box-shadow: var(--gramlot-shadow); }\n}\n' };
2316
+ var appearanceStyles2 = { "base": "", "compact": '/* Gramlot Compact: load after gramlot-base/theme.css. Colors and behavior stay in Base. */\n@layer gramlot.tokens {\n :root {\n --gramlot-field-height: 28px;\n --gramlot-field-line-height: 1.35;\n --gramlot-field-padding: .25rem .5rem;\n --gramlot-field-font-size: 1rem;\n --gramlot-space-1: .2rem;\n --gramlot-space-2: .4rem;\n --gramlot-space-3: .5rem;\n --gramlot-space-4: .6rem;\n --gramlot-space-6: .9rem;\n --gramlot-space-8: 1.2rem;\n --gramlot-space-12: 1.8rem;\n --gramlot-radius-sm: .25rem;\n --gramlot-radius: .35rem;\n --gramlot-radius-lg: .4rem;\n }\n}\n\n@layer gramlot.base {\n html { font-size: 13px; }\n body { line-height: 1.35; }\n :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, blockquote, pre, figure) { margin-block: 0 .5rem; }\n h1 { font-size: 1.35rem; }\n h2 { font-size: 1.15rem; }\n h3 { font-size: 1.05rem; }\n pre { padding: .5rem; }\n blockquote { padding-inline-start: .6rem; }\n hr { margin-block: 1rem; }\n}\n\n@layer gramlot.components {\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) {\n min-block-size: 28px;\n padding: .25rem .5rem;\n font-size: 1rem;\n line-height: 1.35;\n }\n :where(label) { font-size: .95rem; }\n :where(input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]), select, textarea) {\n min-block-size: var(--gramlot-field-height);\n padding: var(--gramlot-field-padding);\n font-size: var(--gramlot-field-font-size);\n line-height: var(--gramlot-field-line-height);\n }\n :where(textarea) { min-block-size: 5rem; }\n :where(input[type="color"]) { min-block-size: 28px; }\n :where(input[type="file"])::file-selector-button { margin-inline-end: .5rem; padding: .25rem .5rem; }\n :where(fieldset) { margin-block-end: .6rem; padding: .5rem; }\n :where(table) { font-size: 1rem; }\n :where(th, td) { padding: .4rem .5rem; }\n :where(details) { padding: .4rem .5rem; }\n :where(details[open] > summary) { margin-block-end: .5rem; }\n :where(dialog) { padding: .75rem; }\n}\n\n@layer gramlot.utilities {\n .example-page { inline-size: min(100% - 1.2rem, var(--gramlot-page-width)); padding-block: .6rem; }\n .card { padding: .6rem; box-shadow: none; }\n}\n', "mimi": '/* Gramlot Compact: load after gramlot-base/theme.css. Colors and behavior stay in Base. */\n@layer gramlot.tokens {\n :root {\n --gramlot-field-height: 28px;\n --gramlot-field-line-height: 1.35;\n --gramlot-field-padding: .25rem .5rem;\n --gramlot-field-font-size: 1rem;\n --gramlot-space-1: .2rem;\n --gramlot-space-2: .4rem;\n --gramlot-space-3: .5rem;\n --gramlot-space-4: .6rem;\n --gramlot-space-6: .9rem;\n --gramlot-space-8: 1.2rem;\n --gramlot-space-12: 1.8rem;\n --gramlot-radius-sm: .25rem;\n --gramlot-radius: .35rem;\n --gramlot-radius-lg: .4rem;\n }\n}\n\n@layer gramlot.base {\n html { font-size: 13px; }\n body { line-height: 1.35; }\n :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, blockquote, pre, figure) { margin-block: 0 .5rem; }\n h1 { font-size: 1.35rem; }\n h2 { font-size: 1.15rem; }\n h3 { font-size: 1.05rem; }\n pre { padding: .5rem; }\n blockquote { padding-inline-start: .6rem; }\n hr { margin-block: 1rem; }\n}\n\n@layer gramlot.components {\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) {\n min-block-size: 28px;\n padding: .25rem .5rem;\n font-size: 1rem;\n line-height: 1.35;\n }\n :where(label) { font-size: .95rem; }\n :where(input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]), select, textarea) {\n min-block-size: var(--gramlot-field-height);\n padding: var(--gramlot-field-padding);\n font-size: var(--gramlot-field-font-size);\n line-height: var(--gramlot-field-line-height);\n }\n :where(textarea) { min-block-size: 5rem; }\n :where(input[type="color"]) { min-block-size: 28px; }\n :where(input[type="file"])::file-selector-button { margin-inline-end: .5rem; padding: .25rem .5rem; }\n :where(fieldset) { margin-block-end: .6rem; padding: .5rem; }\n :where(table) { font-size: 1rem; }\n :where(th, td) { padding: .4rem .5rem; }\n :where(details) { padding: .4rem .5rem; }\n :where(details[open] > summary) { margin-block-end: .5rem; }\n :where(dialog) { padding: .75rem; }\n}\n\n@layer gramlot.utilities {\n .example-page { inline-size: min(100% - 1.2rem, var(--gramlot-page-width)); padding-block: .6rem; }\n .card { padding: .6rem; box-shadow: none; }\n}\n\n/* Gramlot adaptation of legacy mimi; load after Base and Compact. */\n@layer gramlot.tokens {\n :root {\n --gramlot-tab-height: 24px;\n --gramlot-tab-padding: 3px 10px;\n --gramlot-tab-radius: 3px;\n --gramlot-tab-gap: 2px;\n --gramlot-tab-strip-padding: 3px;\n --gramlot-tab-background: var(--gramlot-background);\n --gramlot-tab-selected-background: var(--gramlot-surface);\n --gramlot-tab-selected-color: var(--gramlot-ink);\n --gramlot-tab-border: var(--gramlot-border);\n --gramlot-background: #EDEEF0;\n --gramlot-surface: #F5F5F6;\n --gramlot-surface-subtle: #E0E1E3;\n --gramlot-ink: #3C3D40;\n --gramlot-text-secondary: #6B6E72;\n --gramlot-border: #BDBFC2;\n --gramlot-border-control: #9EA0A3;\n --gramlot-navigation: #48494C;\n --gramlot-navigation-text: #FFFFFF;\n --gramlot-action: #0071E3;\n --gramlot-action-hover: #005BBB;\n --gramlot-brand-blue: #0071E3;\n --gramlot-brand-yellow: #9EA0A3;\n --gramlot-selected: #E8F1FC;\n --gramlot-focus-ring: #0071E3;\n --gramlot-field-background: #FFFFFF;\n --gramlot-field-readonly-background: #E0E1E3;\n --gramlot-error: #B3261E;\n --gramlot-error-background: #FFF1F0;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 6%);\n --gramlot-radius-sm: 4px;\n --gramlot-radius: 3px;\n --gramlot-radius-lg: 3px;\n --gramlot-field-height: 18px;\n --gramlot-field-padding: 1px 3px;\n --gramlot-field-line-height: 1.15;\n --gramlot-field-font-size: 1rem;\n --gramlot-label-size: .9em;\n --gramlot-label-gap: 2px;\n --gramlot-label-color: var(--gramlot-text-secondary);\n --gramlot-space-4: 6px;\n --gramlot-font-ui: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme="light"]) {\n --gramlot-background: #242527;\n --gramlot-surface: #303134;\n --gramlot-surface-subtle: #3C3D40;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #6B6E72;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3C3D40;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n }\n :root[data-theme="dark"] {\n --gramlot-background: #242527;\n --gramlot-surface: #303134;\n --gramlot-surface-subtle: #3C3D40;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #6B6E72;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3C3D40;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n}\n@layer gramlot.base {\n html { font-size: 12px; }\n}\n@layer gramlot.components {\n :where(label) { font-size: var(--gramlot-label-size); font-weight: normal; color: var(--gramlot-label-color); margin-block-end: var(--gramlot-label-gap); }\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) { min-block-size: 24px; padding: 2px 8px; font-weight: 500; }\n}\n@layer gramlot.utilities {\n .card { box-shadow: var(--gramlot-shadow); }\n}\n', "joanna": '/* Gramlot Compact: load after gramlot-base/theme.css. Colors and behavior stay in Base. */\n@layer gramlot.tokens {\n :root {\n --gramlot-field-height: 28px;\n --gramlot-field-line-height: 1.35;\n --gramlot-field-padding: .25rem .5rem;\n --gramlot-field-font-size: 1rem;\n --gramlot-space-1: .2rem;\n --gramlot-space-2: .4rem;\n --gramlot-space-3: .5rem;\n --gramlot-space-4: .6rem;\n --gramlot-space-6: .9rem;\n --gramlot-space-8: 1.2rem;\n --gramlot-space-12: 1.8rem;\n --gramlot-radius-sm: .25rem;\n --gramlot-radius: .35rem;\n --gramlot-radius-lg: .4rem;\n }\n}\n\n@layer gramlot.base {\n html { font-size: 13px; }\n body { line-height: 1.35; }\n :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, blockquote, pre, figure) { margin-block: 0 .5rem; }\n h1 { font-size: 1.35rem; }\n h2 { font-size: 1.15rem; }\n h3 { font-size: 1.05rem; }\n pre { padding: .5rem; }\n blockquote { padding-inline-start: .6rem; }\n hr { margin-block: 1rem; }\n}\n\n@layer gramlot.components {\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) {\n min-block-size: 28px;\n padding: .25rem .5rem;\n font-size: 1rem;\n line-height: 1.35;\n }\n :where(label) { font-size: .95rem; }\n :where(input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]), select, textarea) {\n min-block-size: var(--gramlot-field-height);\n padding: var(--gramlot-field-padding);\n font-size: var(--gramlot-field-font-size);\n line-height: var(--gramlot-field-line-height);\n }\n :where(textarea) { min-block-size: 5rem; }\n :where(input[type="color"]) { min-block-size: 28px; }\n :where(input[type="file"])::file-selector-button { margin-inline-end: .5rem; padding: .25rem .5rem; }\n :where(fieldset) { margin-block-end: .6rem; padding: .5rem; }\n :where(table) { font-size: 1rem; }\n :where(th, td) { padding: .4rem .5rem; }\n :where(details) { padding: .4rem .5rem; }\n :where(details[open] > summary) { margin-block-end: .5rem; }\n :where(dialog) { padding: .75rem; }\n}\n\n@layer gramlot.utilities {\n .example-page { inline-size: min(100% - 1.2rem, var(--gramlot-page-width)); padding-block: .6rem; }\n .card { padding: .6rem; box-shadow: none; }\n}\n\n/* Gramlot adaptation of legacy joanna; load after Base and Compact. */\n@layer gramlot.tokens {\n :root {\n --gramlot-tab-height: 26px;\n --gramlot-tab-padding: 3px 10px;\n --gramlot-tab-radius: 6px;\n --gramlot-tab-gap: 2px;\n --gramlot-tab-strip-padding: 3px;\n --gramlot-tab-background: var(--gramlot-background);\n --gramlot-tab-selected-background: var(--gramlot-surface);\n --gramlot-tab-selected-color: var(--gramlot-ink);\n --gramlot-tab-border: var(--gramlot-border);\n --gramlot-background: #F2F2F7;\n --gramlot-surface: #FFFFFF;\n --gramlot-surface-subtle: #F2F2F7;\n --gramlot-ink: #3A3A3C;\n --gramlot-text-secondary: #636366;\n --gramlot-border: #D1D1D6;\n --gramlot-border-control: #AEAEB2;\n --gramlot-navigation: #3A3A3C;\n --gramlot-navigation-text: #FFFFFF;\n --gramlot-action: #007AFF;\n --gramlot-action-hover: #0061CC;\n --gramlot-brand-blue: #007AFF;\n --gramlot-brand-yellow: #AEAEB2;\n --gramlot-selected: #E0EFFF;\n --gramlot-focus-ring: #007AFF;\n --gramlot-field-background: #FFFFFF;\n --gramlot-field-readonly-background: #F2F2F7;\n --gramlot-error: #B3261E;\n --gramlot-error-background: #FFF1F0;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 6%);\n --gramlot-radius-sm: 6px;\n --gramlot-radius: 6px;\n --gramlot-radius-lg: 6px;\n --gramlot-field-height: 20px;\n --gramlot-field-padding: 1px 3px;\n --gramlot-field-line-height: 1.15;\n --gramlot-field-font-size: 1rem;\n --gramlot-label-size: .8em;\n --gramlot-label-gap: 2px;\n --gramlot-label-color: var(--gramlot-text-secondary);\n --gramlot-space-4: 6px;\n --gramlot-font-ui: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme="light"]) {\n --gramlot-background: #1C1C1E;\n --gramlot-surface: #2C2C2E;\n --gramlot-surface-subtle: #3A3A3C;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #636366;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3A3A3C;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n }\n :root[data-theme="dark"] {\n --gramlot-background: #1C1C1E;\n --gramlot-surface: #2C2C2E;\n --gramlot-surface-subtle: #3A3A3C;\n --gramlot-ink: #F2F2F7;\n --gramlot-text-secondary: #C7C7CC;\n --gramlot-border: #636366;\n --gramlot-border-control: #8E8E93;\n --gramlot-navigation: #1C1C1E;\n --gramlot-action: #78B7FF;\n --gramlot-action-hover: #A6CFFF;\n --gramlot-brand-blue: #3478CA;\n --gramlot-selected: #254163;\n --gramlot-focus-ring: #78B7FF;\n --gramlot-field-background: #202124;\n --gramlot-field-readonly-background: #3A3A3C;\n --gramlot-error: #FFB4AB;\n --gramlot-error-background: #482524;\n --gramlot-shadow: 0 1px 4px rgb(0 0 0 / 20%);\n }\n}\n@layer gramlot.base {\n html { font-size: 13px; }\n}\n@layer gramlot.components {\n :where(label) { font-size: var(--gramlot-label-size); font-weight: normal; color: var(--gramlot-label-color); margin-block-end: var(--gramlot-label-gap); }\n :where(button, input[type="button"], input[type="submit"], input[type="reset"], .button) { min-block-size: 26px; padding: 2px 8px; font-weight: 500; }\n}\n@layer gramlot.utilities {\n .card { box-shadow: var(--gramlot-shadow); }\n}\n' };
1805
2317
 
1806
2318
  // src/gramlot_examples/gallery/browser/frame-theme.js
1807
2319
  function mountFrameTheme(renderer, node) {