@gramlot/gramlot-examples 0.2.12 → 0.2.15

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.
@@ -6437,30 +6437,82 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6437
6437
  }
6438
6438
  throw new Error(`Invalid ${type} value: ${text2 || "(empty)"}`);
6439
6439
  }
6440
- var sequence = 0;
6440
+ var InspectorStyles = class {
6441
+ #document;
6442
+ #element = null;
6443
+ #rule = null;
6444
+ constructor(document2) {
6445
+ this.#document = document2;
6446
+ }
6447
+ snapshot() {
6448
+ const computed = this.#document.defaultView.getComputedStyle(this.#document.documentElement);
6449
+ const names = /* @__PURE__ */ new Set();
6450
+ for (let index = 0; index < computed.length; index++) {
6451
+ const name = computed.item(index);
6452
+ if (name.startsWith("--gramlot-")) names.add(name);
6453
+ }
6454
+ for (let index = 0; index < (this.#rule?.style.length ?? 0); index++) names.add(this.#rule.style.item(index));
6455
+ return [...names].sort((left, right) => left.localeCompare(right, "en", { numeric: true })).map((name) => ({
6456
+ name,
6457
+ value: computed.getPropertyValue(name).trim(),
6458
+ override: this.#rule?.style.getPropertyValue(name) ?? ""
6459
+ }));
6460
+ }
6461
+ set(name, value) {
6462
+ if (typeof name !== "string" || !this.snapshot().some((row) => row.name === name)) {
6463
+ throw new Error("Choose an existing --gramlot-* variable on the document root");
6464
+ }
6465
+ if (typeof value !== "string" || !value.trim() || !this.#document.defaultView.CSS.supports(name, value)) {
6466
+ throw new Error("Enter a valid CSS variable value");
6467
+ }
6468
+ if (!this.#element) {
6469
+ const element = this.#document.createElement("style");
6470
+ this.#document.head.append(element);
6471
+ if (!element.sheet) {
6472
+ element.remove();
6473
+ throw new Error("The page does not allow the inspector preview stylesheet");
6474
+ }
6475
+ element.sheet.insertRule(":root {}", 0);
6476
+ this.#element = element;
6477
+ this.#rule = element.sheet.cssRules[0];
6478
+ }
6479
+ this.#rule.style.setProperty(name, value, "important");
6480
+ }
6481
+ reset(name) {
6482
+ if (typeof name !== "string" || !name.startsWith("--gramlot-")) throw new Error("Invalid CSS variable name");
6483
+ this.#rule?.style.removeProperty(name);
6484
+ if (this.#rule?.style.length === 0) this.dispose();
6485
+ }
6486
+ dispose() {
6487
+ this.#element?.remove();
6488
+ this.#element = null;
6489
+ this.#rule = null;
6490
+ }
6491
+ };
6441
6492
  var roots = ["data", "source"];
6442
6493
  var forbidden = /* @__PURE__ */ new Set(["_meta", "__proto__", "prototype", "constructor"]);
6443
- function encodeScalar(value) {
6444
- if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
6445
- const wrapper = new Bag();
6446
- wrapper.setItem("value", value);
6447
- return wrapper.toTytx("json");
6448
- }
6449
- function decodeScalar(text2) {
6450
- const value = Bag.fromTytx(text2, "json").getItem("value");
6451
- if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
6452
- return value;
6453
- }
6454
- function cell(value) {
6455
- if (scalarType(value)) return { encoded: encodeScalar(value) };
6456
- const display = value instanceof Bag ? `Bag (${value.getNodes().length} nodes)` : value === void 0 ? "undefined" : typeof value === "function" ? "[Function]" : typeof value === "bigint" || typeof value === "symbol" ? String(value) : JSON.stringify(value);
6457
- return { display, readonly: true };
6458
- }
6459
6494
  function attachInspectorAgent(gramlot, port) {
6460
- if (!(gramlot instanceof Gramlot) || gramlot.state === "disposed") throw new TypeError("Expected a live Gramlot instance");
6495
+ if (!gramlot || gramlot.state === "disposed") throw new TypeError("Expected a live Gramlot instance");
6496
+ const Bag4 = gramlot.data.constructor;
6497
+ const encodeScalar2 = (value) => {
6498
+ const wrapper = new Bag4();
6499
+ wrapper.setItem("value", value);
6500
+ return wrapper.toTytx("json");
6501
+ };
6502
+ const decodeScalar2 = (text2) => {
6503
+ const value = Bag4.fromTytx(text2, "json").getItem("value");
6504
+ if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
6505
+ return value;
6506
+ };
6507
+ function cell(value) {
6508
+ if (scalarType(value)) return { encoded: encodeScalar2(value) };
6509
+ const display = value instanceof Bag4 ? `Bag (${value.getNodes().length} nodes)` : value === void 0 ? "undefined" : typeof value === "function" ? "[Function]" : typeof value === "bigint" || typeof value === "symbol" ? String(value) : JSON.stringify(value);
6510
+ return { display, readonly: true };
6511
+ }
6461
6512
  const document2 = gramlot.src.renderer.destination.ownerDocument;
6462
6513
  const window2 = document2.defaultView;
6463
- const subscriber = `inspector-bridge-${++sequence}`;
6514
+ const styles = new InspectorStyles(document2);
6515
+ const subscriber = `inspector-bridge-${window2.crypto.getRandomValues(new Uint32Array(4)).join("-")}`;
6464
6516
  const identities = /* @__PURE__ */ new WeakMap();
6465
6517
  const versions = /* @__PURE__ */ new WeakMap();
6466
6518
  let nextNode = 0, stopped = false, scheduled = false;
@@ -6478,7 +6530,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6478
6530
  const next = /* @__PURE__ */ new Set([...ancestors, store]);
6479
6531
  return store.getNodes().map((node) => {
6480
6532
  const value = node.getValue(true);
6481
- const branch = !node.resolver && value instanceof Bag;
6533
+ const branch = !node.resolver && value instanceof Bag4;
6482
6534
  return {
6483
6535
  id: identity(node),
6484
6536
  label: node.label,
@@ -6559,12 +6611,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6559
6611
  }
6560
6612
  for (const [name, encoded] of command.set) {
6561
6613
  if (!name.trim() || forbidden.has(name) || Object.hasOwn(attrs, name) && !scalarType(attrs[name])) throw new Error(`Cannot edit attribute: ${name}`);
6562
- attrs[name] = decodeScalar(encoded);
6614
+ attrs[name] = decodeScalar2(encoded);
6563
6615
  }
6564
6616
  let value;
6565
6617
  if (Object.hasOwn(command, "value")) {
6566
6618
  if (node.resolver || !scalarType(node.getValue(true))) throw new Error("This value is read-only");
6567
- value = decodeScalar(command.value);
6619
+ value = decodeScalar2(command.value);
6568
6620
  }
6569
6621
  if (command.set.length || command.remove.length) node.setAttr(attrs, true, false, false);
6570
6622
  if (Object.hasOwn(command, "value")) node.setValue(value);
@@ -6585,6 +6637,14 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6585
6637
  return;
6586
6638
  }
6587
6639
  try {
6640
+ if (["styles", "setStyle", "resetStyle", "resetStyles"].includes(message.type)) {
6641
+ if (gramlot.state === "disposed") throw new Error("Inspected page disconnected");
6642
+ if (message.type === "setStyle") styles.set(message.command.name, message.command.value);
6643
+ if (message.type === "resetStyle") styles.reset(message.command.name);
6644
+ if (message.type === "resetStyles") styles.dispose();
6645
+ send({ type: "result", id: message.id, ok: true, styles: styles.snapshot() });
6646
+ return;
6647
+ }
6588
6648
  if (message.type === "edit") edit(message.command);
6589
6649
  else if (message.type === "breakpoint") setBreakpoint(message.command);
6590
6650
  else if (message.type !== "refresh") throw new Error(`Unknown inspector command: ${message.type}`);
@@ -6599,6 +6659,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6599
6659
  stopped = true;
6600
6660
  for (const root of roots) bag(root).unsubscribe(subscriber, { any: true });
6601
6661
  breakpoints.clear();
6662
+ styles.dispose();
6602
6663
  port.removeEventListener("message", receive);
6603
6664
  window2.removeEventListener("pagehide", dispose);
6604
6665
  port.close();
@@ -6610,12 +6671,25 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6610
6671
  flush();
6611
6672
  return { dispose };
6612
6673
  }
6674
+ var roots2 = ["data", "source"];
6675
+ function encodeScalar(value) {
6676
+ if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
6677
+ const wrapper = new Bag();
6678
+ wrapper.setItem("value", value);
6679
+ return wrapper.toTytx("json");
6680
+ }
6681
+ function decodeScalar(text2) {
6682
+ const value = Bag.fromTytx(text2, "json").getItem("value");
6683
+ if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
6684
+ return value;
6685
+ }
6613
6686
  var InspectorConnection = class {
6614
6687
  data = new Bag();
6615
6688
  source = new Bag();
6616
6689
  state = "connecting";
6617
6690
  error = "";
6618
6691
  breakpoints = [];
6692
+ styles = null;
6619
6693
  #info = /* @__PURE__ */ new WeakMap();
6620
6694
  #listeners = /* @__PURE__ */ new Set();
6621
6695
  #pending = /* @__PURE__ */ new Map();
@@ -6643,8 +6717,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6643
6717
  return;
6644
6718
  }
6645
6719
  try {
6720
+ if (message.styles) this.styles = message.styles;
6646
6721
  if (message.snapshot) {
6647
- for (const root of roots) this.#reconcile(this[root], message.snapshot[root]);
6722
+ for (const root of roots2) this.#reconcile(this[root], message.snapshot[root]);
6648
6723
  this.breakpoints = message.snapshot.breakpoints;
6649
6724
  this.state = "started";
6650
6725
  this.error = "";
@@ -6665,7 +6740,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6665
6740
  #reconcile(store, rows) {
6666
6741
  const labels = new Set(rows.map((row) => row.label));
6667
6742
  for (const node of store.getNodes()) if (!labels.has(node.label)) store.popNode(node.label);
6668
- for (const row of rows) {
6743
+ for (const [index, row] of rows.entries()) {
6669
6744
  let node = store.getNode(row.label);
6670
6745
  let previous = node ? this.#info.get(node) : null;
6671
6746
  if (node && previous?.id !== row.id) {
@@ -6695,6 +6770,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6695
6770
  attrsWire: new Map(row.attrs.map(([name, descriptor]) => [name, JSON.stringify(descriptor)]))
6696
6771
  });
6697
6772
  if (branch) this.#reconcile(value, row.children);
6773
+ const position = store.getNodes().indexOf(node);
6774
+ if (position !== index) store.move(position, index);
6698
6775
  }
6699
6776
  }
6700
6777
  request(type, command = {}) {
@@ -6716,9 +6793,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6716
6793
  this.#notify();
6717
6794
  }
6718
6795
  };
6719
- 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 { 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";
6796
+ 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 { 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";
6720
6797
  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:11px/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:16px; }\n.tree-root > ul { padding-left:2px; }\nsummary, .leaf { position:relative; }\nli { line-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:-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:18px; width:14px; 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";
6721
- var sequence2 = 0;
6798
+ var sequence = 0;
6722
6799
  var GramlotPocTree = class extends HTMLElement {
6723
6800
  constructor() {
6724
6801
  super();
@@ -6734,7 +6811,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6734
6811
  this._nodes = /* @__PURE__ */ new Map();
6735
6812
  this._rowActions = [];
6736
6813
  this._pendingRender = null;
6737
- this._subId = `gramlot-poc-tree-${++sequence2}`;
6814
+ this._subId = `gramlot-poc-tree-${++sequence}`;
6738
6815
  }
6739
6816
  get storeBag() {
6740
6817
  return this._store;
@@ -7052,6 +7129,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7052
7129
  #breakpointTests = /* @__PURE__ */ new Map();
7053
7130
  #trees = {};
7054
7131
  #breakpointTarget = null;
7132
+ #styles = null;
7133
+ #styleNames = "";
7134
+ #styleRequest = 0;
7135
+ #styleDownloadUrl = null;
7055
7136
  constructor() {
7056
7137
  super();
7057
7138
  this.#mount = this.ownerDocument.createElement("div");
@@ -7085,6 +7166,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7085
7166
  this.#ui.data.setItem("breakpointStatus", "");
7086
7167
  this.#subscribe();
7087
7168
  this.#refresh();
7169
+ if (this.#ui.data.getItem("tab") === "styles") this.#loadStyles();
7088
7170
  }
7089
7171
  }
7090
7172
  get targets() {
@@ -7127,6 +7209,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7127
7209
  this.#ui.data.setItem("breakpointDirty", false);
7128
7210
  this.#subscribe();
7129
7211
  this.#refresh();
7212
+ if (this.#ui.data.getItem("tab") === "styles") this.#loadStyles();
7130
7213
  }
7131
7214
  }
7132
7215
  #renderTargets() {
@@ -7213,14 +7296,47 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7213
7296
  }
7214
7297
  selectRoot(node) {
7215
7298
  const choose = () => {
7216
- ui.data.setItem("root", node.getAttr("data-root"));
7217
- ui.data.setItem("selection", null);
7218
- inspector.#snapshot = null;
7299
+ const tab = node.getAttr("data-root");
7300
+ ui.data.setItem("tab", tab);
7301
+ if (tab !== "styles") {
7302
+ ui.data.setItem("root", tab);
7303
+ ui.data.setItem("selection", null);
7304
+ inspector.#snapshot = null;
7305
+ }
7219
7306
  inspector.#refresh();
7307
+ if (tab === "styles") inspector.#loadStyles();
7220
7308
  };
7221
7309
  if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(choose);
7222
7310
  else choose();
7223
7311
  }
7312
+ refreshStyles() {
7313
+ inspector.#loadStyles();
7314
+ }
7315
+ styleChanged(node) {
7316
+ ui.data.setItem(`styles.rows.${node.getAttr("data-row")}.dirty`, true);
7317
+ }
7318
+ setStyle(node) {
7319
+ const row = ui.data.getItem(`styles.rows.${node.getAttr("data-row")}`);
7320
+ if (node.getAttr("data-field") === "color") row.setItem("dirty", false);
7321
+ else if (!row.getItem("dirty")) return;
7322
+ inspector.#loadStyles("setStyle", { name: row.getItem("name"), value: row.getItem(node.getAttr("data-field")) });
7323
+ }
7324
+ styleKey(node, event) {
7325
+ if (event.key === "Enter" && !event.isComposing) {
7326
+ event.preventDefault();
7327
+ this.setStyle(node);
7328
+ }
7329
+ if (event.key === "Escape") {
7330
+ event.preventDefault();
7331
+ inspector.#loadStyles();
7332
+ }
7333
+ }
7334
+ resetStyle(node) {
7335
+ inspector.#loadStyles("resetStyle", { name: ui.data.getItem(`styles.rows.${node.getAttr("data-row")}.name`) });
7336
+ }
7337
+ resetStyles() {
7338
+ inspector.#loadStyles("resetStyles");
7339
+ }
7224
7340
  selectNode(node, event) {
7225
7341
  const choose = () => {
7226
7342
  const entry = inspector.#nodes.find((item) => item.node === event.detail.node);
@@ -7283,6 +7399,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7283
7399
  ui.src.logicRegistry.register(Logic, { group: "inspector", resource: "gramlot-tools-inspector" });
7284
7400
  for (const [key, value] of Object.entries({
7285
7401
  root: "data",
7402
+ tab: "data",
7403
+ stylesActive: false,
7404
+ treeActive: true,
7286
7405
  dataActive: true,
7287
7406
  sourceActive: false,
7288
7407
  targetId: "",
@@ -7302,14 +7421,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7302
7421
  ui.data.setItem("editor", new Bag({ dirty: false, disabled: true, status: "", mode: "", condition: "" }));
7303
7422
  ui.data.setItem("breakpoints", new Bag());
7304
7423
  ui.data.setItem("breakpointDirty", false);
7424
+ ui.data.setItem("styles", new Bag({ status: "", busy: false, noChanges: true, noDownload: true, downloadUrl: null, rows: new Bag() }));
7305
7425
  const builder = new GramlotBuilder();
7306
7426
  const root = builder.root;
7307
7427
  root.style(gramlot_tools_inspector_default);
7308
- const palette = root.webcomponent({ code: "gramlot-poc-palette", node_label: "palette", title: "Gramlot inspector", connect_onclose: "inspector.close" });
7428
+ 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", connect_onclose: "inspector.close" });
7309
7429
  const targetbar = palette.div({ node_label: "targetbar", class: "targetbar", hidden: "^singleTarget" });
7310
7430
  targetbar.select({ node_label: "targets", value: "^targetId", disabled: "^busy", "aria-label": "Inspected page", connect_onchange: "inspector.selectTarget" });
7311
7431
  const toolbar = palette.webcomponent({ code: "gramlot-poc-tabs", node_label: "tabs", role: "tablist", "aria-label": "Inspected root" });
7312
- for (const name of ["Data", "Source"]) toolbar.button(name, {
7432
+ for (const name of ["Data", "Source", "Styles"]) toolbar.button(name, {
7313
7433
  node_label: name.toLowerCase(),
7314
7434
  type: "button",
7315
7435
  role: "tab",
@@ -7318,7 +7438,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7318
7438
  connect_onclick: "inspector.selectRoot"
7319
7439
  });
7320
7440
  palette.p("^error", { role: "alert", class: "error" });
7321
- const content = palette.webcomponent({ code: "gramlot-poc-splitter", node_label: "content" });
7441
+ const content = palette.webcomponent({ code: "gramlot-poc-splitter", node_label: "content", hidden: "^stylesActive" });
7322
7442
  const trees = content.div({ node_label: "tree", slot: "top", class: "tree" });
7323
7443
  for (const name of ["data", "source"]) trees.webcomponent({
7324
7444
  code: "gramlot-poc-tree",
@@ -7352,7 +7472,17 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7352
7472
  const editor = properties.fieldset({ node_label: "editor", disabled: "^editor.disabled" });
7353
7473
  editor.div({ node_label: "rows", class: "rows" });
7354
7474
  properties.p("^editor.status", { role: "status", class: "edit-status" });
7355
- const footer = palette.footer({ class: "path" });
7475
+ const styles = palette.section({ node_label: "styles", class: "styles-panel", hidden: "^treeActive", "aria-label": "CSS variables" });
7476
+ const stylebar = styles.div({ class: "styles-toolbar" });
7477
+ stylebar.span("Document variables");
7478
+ stylebar.button("Refresh", { type: "button", disabled: "^styles.busy", connect_onclick: "inspector.refreshStyles" });
7479
+ stylebar.button("Reset all", { type: "button", disabled: "^styles.noChanges", connect_onclick: "inspector.resetStyles" });
7480
+ stylebar.a("Download CSS", { href: "^styles.downloadUrl", download: "theme-overrides.css", "aria-disabled": "^styles.noDownload" });
7481
+ styles.p("Temporary preview \xB7 Enter or leave a field to apply. Closing the connection clears previews.", { class: "styles-help" });
7482
+ styles.p("^styles.status", { role: "status", class: "styles-status" });
7483
+ styles.p("No --gramlot-* variables on this document root. Load a Gramlot theme, then Refresh.", { class: "styles-empty", node_label: "empty" });
7484
+ styles.div({ node_label: "rows", class: "styles-rows" });
7485
+ const footer = palette.footer({ class: "path", hidden: "^stylesActive" });
7356
7486
  footer.span("Path: ");
7357
7487
  footer.em("^path");
7358
7488
  const breakpoint = palette.dialog({ node_label: "breakpoint", "aria-label": "Breakpoint", class: "breakpoint-dialog", connect_oncancel: "inspector.closeBreakpoint" });
@@ -7400,6 +7530,14 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7400
7530
  } });
7401
7531
  }
7402
7532
  #unsubscribe() {
7533
+ ++this.#styleRequest;
7534
+ this.#styles?.dispose();
7535
+ this.#styles = null;
7536
+ if (this.#ui) {
7537
+ this.#renderStyles([]);
7538
+ this.#ui.data.setItem("styles.busy", false);
7539
+ this.#ui.data.setItem("styles.status", "");
7540
+ }
7403
7541
  this.#connectionRelease?.();
7404
7542
  this.#connectionRelease = null;
7405
7543
  ++this.#generation;
@@ -7426,6 +7564,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7426
7564
  try {
7427
7565
  this.#nodes = [];
7428
7566
  const root = ui.data.getItem("root");
7567
+ const tab = ui.data.getItem("tab");
7568
+ ui.data.setItem("stylesActive", tab === "styles");
7569
+ ui.data.setItem("treeActive", tab !== "styles");
7570
+ for (const name of ["data", "source", "styles"]) {
7571
+ ui.src.source.getNode(`main.palette.tabs.${name}`).setAttr({ "aria-selected": String(tab === name) });
7572
+ }
7429
7573
  if (this.#connection) {
7430
7574
  const configurations = new Bag();
7431
7575
  for (const [index, record] of this.#connection.breakpoints.entries()) configurations.setItem(`b${index}`, new Bag(record));
@@ -7438,7 +7582,6 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7438
7582
  ui.data.setItem("dataActive", root === "data");
7439
7583
  ui.data.setItem("sourceActive", root === "source");
7440
7584
  for (const name of ["data", "source"]) {
7441
- ui.src.source.getNode(`main.palette.tabs.${name}`).setAttr({ "aria-selected": String(root === name) });
7442
7585
  const tree = this.#trees[name];
7443
7586
  tree.storeBag = this.#connection || this.#target ? this.#targetBag(name) : null;
7444
7587
  const breakpoints = ui.data.getItem("breakpoints").getNodes().filter((node) => node.value.getItem("root") === name);
@@ -7458,6 +7601,132 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7458
7601
  ui.data.setItem("error", error.message);
7459
7602
  }
7460
7603
  }
7604
+ async #loadStyles(type = "styles", command = {}) {
7605
+ const ui = this.#ui;
7606
+ const request = ++this.#styleRequest;
7607
+ ui.data.setItem("styles.busy", true);
7608
+ ui.data.setItem("styles.downloadUrl", null);
7609
+ ui.data.setItem("styles.noDownload", true);
7610
+ ui.data.setItem("styles.status", "");
7611
+ try {
7612
+ if (!this.#state() || this.#state() === "disposed" || this.#connection && this.#state() !== "started") {
7613
+ throw new Error("Select a connected page before editing CSS variables");
7614
+ }
7615
+ let rows;
7616
+ if (this.#connection) {
7617
+ const connection = this.#connection;
7618
+ await connection.request(type, command);
7619
+ rows = connection.styles;
7620
+ } else {
7621
+ this.#styles ??= new InspectorStyles(this.#target.src.renderer.destination.ownerDocument);
7622
+ if (type === "setStyle") this.#styles.set(command.name, command.value);
7623
+ if (type === "resetStyle") this.#styles.reset(command.name);
7624
+ if (type === "resetStyles") this.#styles.dispose();
7625
+ rows = this.#styles.snapshot();
7626
+ }
7627
+ if (this.#ui !== ui || request !== this.#styleRequest) return;
7628
+ this.#renderStyles(rows, type, command);
7629
+ } catch (error) {
7630
+ if (this.#ui === ui && request === this.#styleRequest) ui.data.setItem("styles.status", error.message);
7631
+ } finally {
7632
+ if (this.#ui === ui && request === this.#styleRequest) {
7633
+ ui.data.setItem("styles.busy", false);
7634
+ ui.data.setItem("styles.downloadUrl", this.#styleDownloadUrl);
7635
+ ui.data.setItem("styles.noDownload", !this.#styleDownloadUrl);
7636
+ }
7637
+ }
7638
+ }
7639
+ #renderStyles(rows, type = "styles", command = {}) {
7640
+ const ui = this.#ui;
7641
+ const browser = this.ownerDocument.defaultView;
7642
+ if (this.#styleDownloadUrl) browser.URL.revokeObjectURL(this.#styleDownloadUrl);
7643
+ this.#styleDownloadUrl = null;
7644
+ const overrides = rows.filter((row) => row.override);
7645
+ if (overrides.length) {
7646
+ const declarations = overrides.map((row) => ` ${browser.CSS.escape(row.name)}: ${row.override} !important;`).join("\n");
7647
+ const css = `/* Gramlot overrides. Load after the theme stylesheet. */
7648
+ :root {
7649
+ ${declarations}
7650
+ }
7651
+ `;
7652
+ this.#styleDownloadUrl = browser.URL.createObjectURL(new browser.Blob([css], { type: "text/css;charset=utf-8" }));
7653
+ }
7654
+ ui.data.setItem("styles.downloadUrl", ui.data.getItem("styles.busy") ? null : this.#styleDownloadUrl);
7655
+ ui.data.setItem("styles.noDownload", ui.data.getItem("styles.busy") || !this.#styleDownloadUrl);
7656
+ const names = JSON.stringify(rows.map((row) => row.name));
7657
+ const rebuild = names !== this.#styleNames;
7658
+ this.#styleNames = names;
7659
+ const builder = rebuild ? new GramlotBuilder() : null;
7660
+ const groups = /* @__PURE__ */ new Map();
7661
+ if (rebuild) {
7662
+ ui.src.source.getNode("main.palette.styles.rows").setValue(ui.src.prepareSource(new GramlotBuilder().source));
7663
+ ui.data.setItem("styles.rows", new Bag());
7664
+ }
7665
+ for (const [index, record] of rows.entries()) {
7666
+ const id = `v${index}`, path = `styles.rows.${id}`;
7667
+ const color = /^#[\da-f]{6}$/i.test(record.value) ? record.value : /^#[\da-f]{3}$/i.test(record.value) ? "#" + [...record.value.slice(1)].map((char) => char + char).join("") : null;
7668
+ const fields = {
7669
+ name: record.name,
7670
+ text: record.override || record.value,
7671
+ computed: record.value,
7672
+ dirty: false,
7673
+ color: color ?? "#000000",
7674
+ noColor: color === null,
7675
+ unchanged: !record.override
7676
+ };
7677
+ if (!rebuild && ui.data.getItem(`${path}.dirty`) && !["styles", "resetStyles"].includes(type)) {
7678
+ const acknowledged = record.name === command.name && (type === "resetStyle" || ui.data.getItem(`${path}.text`) === command.value);
7679
+ if (!acknowledged) {
7680
+ delete fields.text;
7681
+ delete fields.dirty;
7682
+ }
7683
+ }
7684
+ if (rebuild) ui.data.setItem(path, new Bag(fields));
7685
+ else for (const [key, value] of Object.entries(fields)) ui.data.setItem(`${path}.${key}`, value);
7686
+ if (!rebuild) continue;
7687
+ const group = record.name.includes("-font-") ? "Typography" : record.name.includes("-space-") ? "Spacing" : /-(radius|page-width)/.test(record.name) ? "Dimensions" : record.name.endsWith("-shadow") ? "Effects" : color ? "Colors" : "Other";
7688
+ if (!groups.has(group)) {
7689
+ const section = builder.root.section({ class: "styles-group" });
7690
+ section.h3(group);
7691
+ groups.set(group, section);
7692
+ }
7693
+ const row = groups.get(group).div({ class: "style-row" });
7694
+ row.html_label(record.name, { for: `${this.#subscriber}-${id}`, title: record.name });
7695
+ row.input({
7696
+ id: `${this.#subscriber}-${id}`,
7697
+ value: `^${path}.text`,
7698
+ live: true,
7699
+ "aria-label": `${record.name} value`,
7700
+ title: `^${path}.computed`,
7701
+ "data-row": id,
7702
+ "data-field": "text",
7703
+ connect_oninput: "inspector.styleChanged",
7704
+ connect_onchange: "inspector.setStyle",
7705
+ connect_onkeydown: "inspector.styleKey",
7706
+ spellcheck: "false"
7707
+ });
7708
+ row.input({
7709
+ type: "color",
7710
+ value: `^${path}.color`,
7711
+ hidden: `^${path}.noColor`,
7712
+ "aria-label": `${record.name} color`,
7713
+ "data-row": id,
7714
+ "data-field": "color",
7715
+ connect_onchange: "inspector.setStyle"
7716
+ });
7717
+ row.button("\u21B6", {
7718
+ type: "button",
7719
+ "aria-label": `Reset ${record.name}`,
7720
+ title: "Reset variable",
7721
+ disabled: `^${path}.unchanged`,
7722
+ "data-row": id,
7723
+ connect_onclick: "inspector.resetStyle"
7724
+ });
7725
+ }
7726
+ if (rebuild) ui.src.source.getNode("main.palette.styles.rows").setValue(ui.src.prepareSource(builder.source));
7727
+ ui.data.setItem("styles.noChanges", !rows.some((row) => row.override));
7728
+ ui.src.source.getNode("main.palette.styles.empty").setAttr({ hidden: rows.length !== 0 });
7729
+ }
7461
7730
  #index(bag, labels) {
7462
7731
  for (const node of bag.getNodes()) {
7463
7732
  const path = [...labels, node.label];
@@ -7551,8 +7820,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7551
7820
  const label = line.div({ class: "property-name" });
7552
7821
  if (fresh) label.input({ value: "^.name", live: true, "aria-label": "New attribute name", "data-row": row.label, connect_oninput: "inspector.editChanged" });
7553
7822
  else label.span(name);
7554
- const cell2 = line.div({ class: "property-value" });
7555
- cell2.input({
7823
+ const cell = line.div({ class: "property-value" });
7824
+ cell.input({
7556
7825
  value: "^.text",
7557
7826
  type: "^.inputType",
7558
7827
  live: true,
@@ -7565,11 +7834,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7565
7834
  connect_onkeydown: "inspector.editKey"
7566
7835
  });
7567
7836
  if (fresh) {
7568
- const type = cell2.select({ value: "^.type", "aria-label": "New attribute type", "data-row": row.label, connect_onchange: "inspector.typeChanged" });
7837
+ const type = cell.select({ value: "^.type", "aria-label": "New attribute type", "data-row": row.label, connect_onchange: "inspector.typeChanged" });
7569
7838
  for (const value of editorTypes) type.option(value, { value });
7570
7839
  }
7571
7840
  if (!primary) {
7572
- const button = cell2.button({
7841
+ const button = cell.button({
7573
7842
  type: "button",
7574
7843
  disabled: locked,
7575
7844
  "aria-label": `Remove ${name || "new attribute"}`,