@gramlot/gramlot-examples 0.2.12 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/package.json +2 -2
- package/src/gramlot_examples/catalog.json +13 -1
- package/src/gramlot_examples/gallery/dist/frame.js +308 -39
- package/src/gramlot_examples/gallery/dist/gallery.js +308 -39
- package/src/gramlot_examples/gallery/dist/inspector.js +1801 -0
- package/src/gramlot_examples/gallery/gallery-page.js +2 -5
- package/src/gramlot_examples/gallery/gallery.css +3 -2
- package/src/gramlot_examples/pages/README.md +3 -2
- package/src/gramlot_examples/pages/controllers/13_cached_remote.md +1 -1
- package/src/gramlot_examples/pages/controllers/18_publish_subscribe.js +36 -0
- package/src/gramlot_examples/pages/controllers/18_publish_subscribe.md +20 -0
- package/src/gramlot_examples/pages/controllers/19_frame_messages.js +39 -0
- package/src/gramlot_examples/pages/controllers/19_frame_messages.md +20 -0
- package/src/gramlot_examples/pages/controllers/README.md +4 -2
package/README.md
CHANGED
|
@@ -12,7 +12,13 @@ npm install @gramlot/gramlot-examples
|
|
|
12
12
|
[](https://pypi.org/project/gramlot-examples/)
|
|
13
13
|
[](https://www.npmjs.com/package/@gramlot/gramlot-examples)
|
|
14
14
|
|
|
15
|
-
Current release: 0.2.
|
|
15
|
+
Current release: 0.2.14.
|
|
16
|
+
|
|
17
|
+
[Documentation and learning sequence](https://gramlot-examples.readthedocs.io/en/latest/)
|
|
18
|
+
include every lesson, its Python and JavaScript source, and the gallery guide.
|
|
19
|
+
To run the examples, choose the [Python server](https://gramlot-py-server.readthedocs.io/en/latest/),
|
|
20
|
+
[Node.js/Bun server](https://gramlot-js-server.readthedocs.io/en/latest/010-tutorial.html)
|
|
21
|
+
or [serverless exporter](https://gramlot-js-server.readthedocs.io/en/latest/135-serverless-tutorial.html).
|
|
16
22
|
|
|
17
23
|
The repositories of the family are described in
|
|
18
24
|
[The Gramlot family](https://github.com/gramlot-org/gramlot/blob/main/docs/public/055-family.md)
|
|
@@ -26,9 +32,10 @@ same-name README:
|
|
|
26
32
|
|
|
27
33
|
- `html_svg/`: thirteen HTML and SVG pages, without binding (`e01`–`e13`);
|
|
28
34
|
- `binding/`: eleven pages whose DOM follows the Data (`b01`–`b11`);
|
|
29
|
-
- `controllers/`:
|
|
35
|
+
- `controllers/`: nineteen pages with formulas, controllers, named logic, events, the
|
|
30
36
|
end-to-end story, `dataRpc` calls to the endpoints of the page, `remote` fragments and
|
|
31
|
-
`dataRemote` values (`c01`–`
|
|
37
|
+
`dataRemote` values (`c01`–`c19`). The last two introduce: topics,
|
|
38
|
+
authored references, native ask and parent/child messages. They require core 0.2.18 or later.
|
|
32
39
|
|
|
33
40
|
Each `NN_name.js` exports `Page` and `Logic`; the Python page `NN_name.py` takes its
|
|
34
41
|
`Logic` from the module beside it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gramlot/gramlot-examples",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "Gramlot example pages, the gallery page and its catalogue.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"prepack": "node scripts/build-browser.mjs"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@gramlot/gramlot": ">=0.2.
|
|
17
|
+
"@gramlot/gramlot": ">=0.2.18"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@genrojs/bag": ">=0.11.0",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"title": "Cards with icons",
|
|
73
73
|
"folder": "10_cards_with_icons",
|
|
74
74
|
"description": "HTML cards with SVG icons and live Source removal.",
|
|
75
|
-
"explanation": "Helpers and loops build the cards. Each
|
|
75
|
+
"explanation": "Helpers and loops build the cards. Each \u00d7 button removes its labelled Source node through the browser Builder; Gramlot updates the DOM without reloading. Reload restores the initial cards. No Data binding or server synchronization is involved."
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"key": "e11",
|
|
@@ -299,6 +299,18 @@
|
|
|
299
299
|
"folder": "14_data_remote",
|
|
300
300
|
"description": "Revisit endpoint values with lazy Data; compare a bound read, an explicit snapshot and cache expiry.",
|
|
301
301
|
"explanation": "The menu path holds a resolver of the endpoint load_menu with cacheTime 5. The ^.menu paragraph reads it: the answer arrives with the Data event. A change of the table loads the menu again."
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"key": "c18",
|
|
305
|
+
"title": "Publish and subscribe",
|
|
306
|
+
"folder": "18_publish_subscribe",
|
|
307
|
+
"description": "Development core 0.2.18: revisit button actions with page topics, self receivers, references and native ask."
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"key": "c19",
|
|
311
|
+
"title": "Parent and child messages",
|
|
312
|
+
"folder": "19_frame_messages",
|
|
313
|
+
"description": "Development core 0.2.18: local-first publish and direct parent/child postMessage exchange."
|
|
302
314
|
}
|
|
303
315
|
]
|
|
304
316
|
}
|
|
@@ -49,30 +49,82 @@
|
|
|
49
49
|
}
|
|
50
50
|
throw new Error(`Invalid ${type} value: ${text || "(empty)"}`);
|
|
51
51
|
}
|
|
52
|
-
var
|
|
52
|
+
var InspectorStyles = class {
|
|
53
|
+
#document;
|
|
54
|
+
#element = null;
|
|
55
|
+
#rule = null;
|
|
56
|
+
constructor(document2) {
|
|
57
|
+
this.#document = document2;
|
|
58
|
+
}
|
|
59
|
+
snapshot() {
|
|
60
|
+
const computed = this.#document.defaultView.getComputedStyle(this.#document.documentElement);
|
|
61
|
+
const names = /* @__PURE__ */ new Set();
|
|
62
|
+
for (let index = 0; index < computed.length; index++) {
|
|
63
|
+
const name = computed.item(index);
|
|
64
|
+
if (name.startsWith("--gramlot-")) names.add(name);
|
|
65
|
+
}
|
|
66
|
+
for (let index = 0; index < (this.#rule?.style.length ?? 0); index++) names.add(this.#rule.style.item(index));
|
|
67
|
+
return [...names].sort((left, right) => left.localeCompare(right, "en", { numeric: true })).map((name) => ({
|
|
68
|
+
name,
|
|
69
|
+
value: computed.getPropertyValue(name).trim(),
|
|
70
|
+
override: this.#rule?.style.getPropertyValue(name) ?? ""
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
set(name, value) {
|
|
74
|
+
if (typeof name !== "string" || !this.snapshot().some((row) => row.name === name)) {
|
|
75
|
+
throw new Error("Choose an existing --gramlot-* variable on the document root");
|
|
76
|
+
}
|
|
77
|
+
if (typeof value !== "string" || !value.trim() || !this.#document.defaultView.CSS.supports(name, value)) {
|
|
78
|
+
throw new Error("Enter a valid CSS variable value");
|
|
79
|
+
}
|
|
80
|
+
if (!this.#element) {
|
|
81
|
+
const element = this.#document.createElement("style");
|
|
82
|
+
this.#document.head.append(element);
|
|
83
|
+
if (!element.sheet) {
|
|
84
|
+
element.remove();
|
|
85
|
+
throw new Error("The page does not allow the inspector preview stylesheet");
|
|
86
|
+
}
|
|
87
|
+
element.sheet.insertRule(":root {}", 0);
|
|
88
|
+
this.#element = element;
|
|
89
|
+
this.#rule = element.sheet.cssRules[0];
|
|
90
|
+
}
|
|
91
|
+
this.#rule.style.setProperty(name, value, "important");
|
|
92
|
+
}
|
|
93
|
+
reset(name) {
|
|
94
|
+
if (typeof name !== "string" || !name.startsWith("--gramlot-")) throw new Error("Invalid CSS variable name");
|
|
95
|
+
this.#rule?.style.removeProperty(name);
|
|
96
|
+
if (this.#rule?.style.length === 0) this.dispose();
|
|
97
|
+
}
|
|
98
|
+
dispose() {
|
|
99
|
+
this.#element?.remove();
|
|
100
|
+
this.#element = null;
|
|
101
|
+
this.#rule = null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
53
104
|
var roots = ["data", "source"];
|
|
54
105
|
var forbidden = /* @__PURE__ */ new Set(["_meta", "__proto__", "prototype", "constructor"]);
|
|
55
|
-
function encodeScalar(value) {
|
|
56
|
-
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
57
|
-
const wrapper = new Bag();
|
|
58
|
-
wrapper.setItem("value", value);
|
|
59
|
-
return wrapper.toTytx("json");
|
|
60
|
-
}
|
|
61
|
-
function decodeScalar(text) {
|
|
62
|
-
const value = Bag.fromTytx(text, "json").getItem("value");
|
|
63
|
-
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
function cell(value) {
|
|
67
|
-
if (scalarType(value)) return { encoded: encodeScalar(value) };
|
|
68
|
-
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);
|
|
69
|
-
return { display, readonly: true };
|
|
70
|
-
}
|
|
71
106
|
function attachInspectorAgent(gramlot, port) {
|
|
72
|
-
if (!
|
|
107
|
+
if (!gramlot || gramlot.state === "disposed") throw new TypeError("Expected a live Gramlot instance");
|
|
108
|
+
const Bag4 = gramlot.data.constructor;
|
|
109
|
+
const encodeScalar2 = (value) => {
|
|
110
|
+
const wrapper = new Bag4();
|
|
111
|
+
wrapper.setItem("value", value);
|
|
112
|
+
return wrapper.toTytx("json");
|
|
113
|
+
};
|
|
114
|
+
const decodeScalar2 = (text) => {
|
|
115
|
+
const value = Bag4.fromTytx(text, "json").getItem("value");
|
|
116
|
+
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
117
|
+
return value;
|
|
118
|
+
};
|
|
119
|
+
function cell(value) {
|
|
120
|
+
if (scalarType(value)) return { encoded: encodeScalar2(value) };
|
|
121
|
+
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);
|
|
122
|
+
return { display, readonly: true };
|
|
123
|
+
}
|
|
73
124
|
const document2 = gramlot.src.renderer.destination.ownerDocument;
|
|
74
125
|
const window2 = document2.defaultView;
|
|
75
|
-
const
|
|
126
|
+
const styles = new InspectorStyles(document2);
|
|
127
|
+
const subscriber = `inspector-bridge-${window2.crypto.getRandomValues(new Uint32Array(4)).join("-")}`;
|
|
76
128
|
const identities = /* @__PURE__ */ new WeakMap();
|
|
77
129
|
const versions = /* @__PURE__ */ new WeakMap();
|
|
78
130
|
let nextNode = 0, stopped = false, scheduled = false;
|
|
@@ -90,7 +142,7 @@
|
|
|
90
142
|
const next = /* @__PURE__ */ new Set([...ancestors, store]);
|
|
91
143
|
return store.getNodes().map((node) => {
|
|
92
144
|
const value = node.getValue(true);
|
|
93
|
-
const branch = !node.resolver && value instanceof
|
|
145
|
+
const branch = !node.resolver && value instanceof Bag4;
|
|
94
146
|
return {
|
|
95
147
|
id: identity(node),
|
|
96
148
|
label: node.label,
|
|
@@ -171,12 +223,12 @@
|
|
|
171
223
|
}
|
|
172
224
|
for (const [name, encoded] of command.set) {
|
|
173
225
|
if (!name.trim() || forbidden.has(name) || Object.hasOwn(attrs, name) && !scalarType(attrs[name])) throw new Error(`Cannot edit attribute: ${name}`);
|
|
174
|
-
attrs[name] =
|
|
226
|
+
attrs[name] = decodeScalar2(encoded);
|
|
175
227
|
}
|
|
176
228
|
let value;
|
|
177
229
|
if (Object.hasOwn(command, "value")) {
|
|
178
230
|
if (node.resolver || !scalarType(node.getValue(true))) throw new Error("This value is read-only");
|
|
179
|
-
value =
|
|
231
|
+
value = decodeScalar2(command.value);
|
|
180
232
|
}
|
|
181
233
|
if (command.set.length || command.remove.length) node.setAttr(attrs, true, false, false);
|
|
182
234
|
if (Object.hasOwn(command, "value")) node.setValue(value);
|
|
@@ -197,6 +249,14 @@
|
|
|
197
249
|
return;
|
|
198
250
|
}
|
|
199
251
|
try {
|
|
252
|
+
if (["styles", "setStyle", "resetStyle", "resetStyles"].includes(message.type)) {
|
|
253
|
+
if (gramlot.state === "disposed") throw new Error("Inspected page disconnected");
|
|
254
|
+
if (message.type === "setStyle") styles.set(message.command.name, message.command.value);
|
|
255
|
+
if (message.type === "resetStyle") styles.reset(message.command.name);
|
|
256
|
+
if (message.type === "resetStyles") styles.dispose();
|
|
257
|
+
send({ type: "result", id: message.id, ok: true, styles: styles.snapshot() });
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
200
260
|
if (message.type === "edit") edit(message.command);
|
|
201
261
|
else if (message.type === "breakpoint") setBreakpoint(message.command);
|
|
202
262
|
else if (message.type !== "refresh") throw new Error(`Unknown inspector command: ${message.type}`);
|
|
@@ -211,6 +271,7 @@
|
|
|
211
271
|
stopped = true;
|
|
212
272
|
for (const root of roots) bag(root).unsubscribe(subscriber, { any: true });
|
|
213
273
|
breakpoints.clear();
|
|
274
|
+
styles.dispose();
|
|
214
275
|
port.removeEventListener("message", receive);
|
|
215
276
|
window2.removeEventListener("pagehide", dispose);
|
|
216
277
|
port.close();
|
|
@@ -222,12 +283,25 @@
|
|
|
222
283
|
flush();
|
|
223
284
|
return { dispose };
|
|
224
285
|
}
|
|
286
|
+
var roots2 = ["data", "source"];
|
|
287
|
+
function encodeScalar(value) {
|
|
288
|
+
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
289
|
+
const wrapper = new Bag();
|
|
290
|
+
wrapper.setItem("value", value);
|
|
291
|
+
return wrapper.toTytx("json");
|
|
292
|
+
}
|
|
293
|
+
function decodeScalar(text) {
|
|
294
|
+
const value = Bag.fromTytx(text, "json").getItem("value");
|
|
295
|
+
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
296
|
+
return value;
|
|
297
|
+
}
|
|
225
298
|
var InspectorConnection = class {
|
|
226
299
|
data = new Bag();
|
|
227
300
|
source = new Bag();
|
|
228
301
|
state = "connecting";
|
|
229
302
|
error = "";
|
|
230
303
|
breakpoints = [];
|
|
304
|
+
styles = null;
|
|
231
305
|
#info = /* @__PURE__ */ new WeakMap();
|
|
232
306
|
#listeners = /* @__PURE__ */ new Set();
|
|
233
307
|
#pending = /* @__PURE__ */ new Map();
|
|
@@ -255,8 +329,9 @@
|
|
|
255
329
|
return;
|
|
256
330
|
}
|
|
257
331
|
try {
|
|
332
|
+
if (message.styles) this.styles = message.styles;
|
|
258
333
|
if (message.snapshot) {
|
|
259
|
-
for (const root of
|
|
334
|
+
for (const root of roots2) this.#reconcile(this[root], message.snapshot[root]);
|
|
260
335
|
this.breakpoints = message.snapshot.breakpoints;
|
|
261
336
|
this.state = "started";
|
|
262
337
|
this.error = "";
|
|
@@ -277,7 +352,7 @@
|
|
|
277
352
|
#reconcile(store, rows) {
|
|
278
353
|
const labels = new Set(rows.map((row) => row.label));
|
|
279
354
|
for (const node of store.getNodes()) if (!labels.has(node.label)) store.popNode(node.label);
|
|
280
|
-
for (const row of rows) {
|
|
355
|
+
for (const [index, row] of rows.entries()) {
|
|
281
356
|
let node = store.getNode(row.label);
|
|
282
357
|
let previous = node ? this.#info.get(node) : null;
|
|
283
358
|
if (node && previous?.id !== row.id) {
|
|
@@ -307,6 +382,8 @@
|
|
|
307
382
|
attrsWire: new Map(row.attrs.map(([name, descriptor]) => [name, JSON.stringify(descriptor)]))
|
|
308
383
|
});
|
|
309
384
|
if (branch) this.#reconcile(value, row.children);
|
|
385
|
+
const position = store.getNodes().indexOf(node);
|
|
386
|
+
if (position !== index) store.move(position, index);
|
|
310
387
|
}
|
|
311
388
|
}
|
|
312
389
|
request(type, command = {}) {
|
|
@@ -328,9 +405,9 @@
|
|
|
328
405
|
this.#notify();
|
|
329
406
|
}
|
|
330
407
|
};
|
|
331
|
-
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";
|
|
408
|
+
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";
|
|
332
409
|
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";
|
|
333
|
-
var
|
|
410
|
+
var sequence = 0;
|
|
334
411
|
var GramlotPocTree = class extends HTMLElement {
|
|
335
412
|
constructor() {
|
|
336
413
|
super();
|
|
@@ -346,7 +423,7 @@
|
|
|
346
423
|
this._nodes = /* @__PURE__ */ new Map();
|
|
347
424
|
this._rowActions = [];
|
|
348
425
|
this._pendingRender = null;
|
|
349
|
-
this._subId = `gramlot-poc-tree-${++
|
|
426
|
+
this._subId = `gramlot-poc-tree-${++sequence}`;
|
|
350
427
|
}
|
|
351
428
|
get storeBag() {
|
|
352
429
|
return this._store;
|
|
@@ -664,6 +741,10 @@
|
|
|
664
741
|
#breakpointTests = /* @__PURE__ */ new Map();
|
|
665
742
|
#trees = {};
|
|
666
743
|
#breakpointTarget = null;
|
|
744
|
+
#styles = null;
|
|
745
|
+
#styleNames = "";
|
|
746
|
+
#styleRequest = 0;
|
|
747
|
+
#styleDownloadUrl = null;
|
|
667
748
|
constructor() {
|
|
668
749
|
super();
|
|
669
750
|
this.#mount = this.ownerDocument.createElement("div");
|
|
@@ -697,6 +778,7 @@
|
|
|
697
778
|
this.#ui.data.setItem("breakpointStatus", "");
|
|
698
779
|
this.#subscribe();
|
|
699
780
|
this.#refresh();
|
|
781
|
+
if (this.#ui.data.getItem("tab") === "styles") this.#loadStyles();
|
|
700
782
|
}
|
|
701
783
|
}
|
|
702
784
|
get targets() {
|
|
@@ -739,6 +821,7 @@
|
|
|
739
821
|
this.#ui.data.setItem("breakpointDirty", false);
|
|
740
822
|
this.#subscribe();
|
|
741
823
|
this.#refresh();
|
|
824
|
+
if (this.#ui.data.getItem("tab") === "styles") this.#loadStyles();
|
|
742
825
|
}
|
|
743
826
|
}
|
|
744
827
|
#renderTargets() {
|
|
@@ -825,14 +908,47 @@
|
|
|
825
908
|
}
|
|
826
909
|
selectRoot(node) {
|
|
827
910
|
const choose = () => {
|
|
828
|
-
|
|
829
|
-
ui.data.setItem("
|
|
830
|
-
|
|
911
|
+
const tab = node.getAttr("data-root");
|
|
912
|
+
ui.data.setItem("tab", tab);
|
|
913
|
+
if (tab !== "styles") {
|
|
914
|
+
ui.data.setItem("root", tab);
|
|
915
|
+
ui.data.setItem("selection", null);
|
|
916
|
+
inspector.#snapshot = null;
|
|
917
|
+
}
|
|
831
918
|
inspector.#refresh();
|
|
919
|
+
if (tab === "styles") inspector.#loadStyles();
|
|
832
920
|
};
|
|
833
921
|
if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(choose);
|
|
834
922
|
else choose();
|
|
835
923
|
}
|
|
924
|
+
refreshStyles() {
|
|
925
|
+
inspector.#loadStyles();
|
|
926
|
+
}
|
|
927
|
+
styleChanged(node) {
|
|
928
|
+
ui.data.setItem(`styles.rows.${node.getAttr("data-row")}.dirty`, true);
|
|
929
|
+
}
|
|
930
|
+
setStyle(node) {
|
|
931
|
+
const row = ui.data.getItem(`styles.rows.${node.getAttr("data-row")}`);
|
|
932
|
+
if (node.getAttr("data-field") === "color") row.setItem("dirty", false);
|
|
933
|
+
else if (!row.getItem("dirty")) return;
|
|
934
|
+
inspector.#loadStyles("setStyle", { name: row.getItem("name"), value: row.getItem(node.getAttr("data-field")) });
|
|
935
|
+
}
|
|
936
|
+
styleKey(node, event) {
|
|
937
|
+
if (event.key === "Enter" && !event.isComposing) {
|
|
938
|
+
event.preventDefault();
|
|
939
|
+
this.setStyle(node);
|
|
940
|
+
}
|
|
941
|
+
if (event.key === "Escape") {
|
|
942
|
+
event.preventDefault();
|
|
943
|
+
inspector.#loadStyles();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
resetStyle(node) {
|
|
947
|
+
inspector.#loadStyles("resetStyle", { name: ui.data.getItem(`styles.rows.${node.getAttr("data-row")}.name`) });
|
|
948
|
+
}
|
|
949
|
+
resetStyles() {
|
|
950
|
+
inspector.#loadStyles("resetStyles");
|
|
951
|
+
}
|
|
836
952
|
selectNode(node, event) {
|
|
837
953
|
const choose = () => {
|
|
838
954
|
const entry = inspector.#nodes.find((item) => item.node === event.detail.node);
|
|
@@ -895,6 +1011,9 @@
|
|
|
895
1011
|
ui.src.logicRegistry.register(Logic, { group: "inspector", resource: "gramlot-tools-inspector" });
|
|
896
1012
|
for (const [key, value] of Object.entries({
|
|
897
1013
|
root: "data",
|
|
1014
|
+
tab: "data",
|
|
1015
|
+
stylesActive: false,
|
|
1016
|
+
treeActive: true,
|
|
898
1017
|
dataActive: true,
|
|
899
1018
|
sourceActive: false,
|
|
900
1019
|
targetId: "",
|
|
@@ -914,14 +1033,15 @@
|
|
|
914
1033
|
ui.data.setItem("editor", new Bag({ dirty: false, disabled: true, status: "", mode: "", condition: "" }));
|
|
915
1034
|
ui.data.setItem("breakpoints", new Bag());
|
|
916
1035
|
ui.data.setItem("breakpointDirty", false);
|
|
1036
|
+
ui.data.setItem("styles", new Bag({ status: "", busy: false, noChanges: true, noDownload: true, downloadUrl: null, rows: new Bag() }));
|
|
917
1037
|
const builder = new GramlotBuilder();
|
|
918
1038
|
const root = builder.root;
|
|
919
1039
|
root.style(gramlot_tools_inspector_default);
|
|
920
|
-
const palette = root.webcomponent({ code: "gramlot-poc-palette", node_label: "palette", title: "Gramlot inspector", connect_onclose: "inspector.close" });
|
|
1040
|
+
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" });
|
|
921
1041
|
const targetbar = palette.div({ node_label: "targetbar", class: "targetbar", hidden: "^singleTarget" });
|
|
922
1042
|
targetbar.select({ node_label: "targets", value: "^targetId", disabled: "^busy", "aria-label": "Inspected page", connect_onchange: "inspector.selectTarget" });
|
|
923
1043
|
const toolbar = palette.webcomponent({ code: "gramlot-poc-tabs", node_label: "tabs", role: "tablist", "aria-label": "Inspected root" });
|
|
924
|
-
for (const name of ["Data", "Source"]) toolbar.button(name, {
|
|
1044
|
+
for (const name of ["Data", "Source", "Styles"]) toolbar.button(name, {
|
|
925
1045
|
node_label: name.toLowerCase(),
|
|
926
1046
|
type: "button",
|
|
927
1047
|
role: "tab",
|
|
@@ -930,7 +1050,7 @@
|
|
|
930
1050
|
connect_onclick: "inspector.selectRoot"
|
|
931
1051
|
});
|
|
932
1052
|
palette.p("^error", { role: "alert", class: "error" });
|
|
933
|
-
const content = palette.webcomponent({ code: "gramlot-poc-splitter", node_label: "content" });
|
|
1053
|
+
const content = palette.webcomponent({ code: "gramlot-poc-splitter", node_label: "content", hidden: "^stylesActive" });
|
|
934
1054
|
const trees = content.div({ node_label: "tree", slot: "top", class: "tree" });
|
|
935
1055
|
for (const name of ["data", "source"]) trees.webcomponent({
|
|
936
1056
|
code: "gramlot-poc-tree",
|
|
@@ -964,7 +1084,17 @@
|
|
|
964
1084
|
const editor = properties.fieldset({ node_label: "editor", disabled: "^editor.disabled" });
|
|
965
1085
|
editor.div({ node_label: "rows", class: "rows" });
|
|
966
1086
|
properties.p("^editor.status", { role: "status", class: "edit-status" });
|
|
967
|
-
const
|
|
1087
|
+
const styles = palette.section({ node_label: "styles", class: "styles-panel", hidden: "^treeActive", "aria-label": "CSS variables" });
|
|
1088
|
+
const stylebar = styles.div({ class: "styles-toolbar" });
|
|
1089
|
+
stylebar.span("Document variables");
|
|
1090
|
+
stylebar.button("Refresh", { type: "button", disabled: "^styles.busy", connect_onclick: "inspector.refreshStyles" });
|
|
1091
|
+
stylebar.button("Reset all", { type: "button", disabled: "^styles.noChanges", connect_onclick: "inspector.resetStyles" });
|
|
1092
|
+
stylebar.a("Download CSS", { href: "^styles.downloadUrl", download: "theme-overrides.css", "aria-disabled": "^styles.noDownload" });
|
|
1093
|
+
styles.p("Temporary preview \xB7 Enter or leave a field to apply. Closing the connection clears previews.", { class: "styles-help" });
|
|
1094
|
+
styles.p("^styles.status", { role: "status", class: "styles-status" });
|
|
1095
|
+
styles.p("No --gramlot-* variables on this document root. Load a Gramlot theme, then Refresh.", { class: "styles-empty", node_label: "empty" });
|
|
1096
|
+
styles.div({ node_label: "rows", class: "styles-rows" });
|
|
1097
|
+
const footer = palette.footer({ class: "path", hidden: "^stylesActive" });
|
|
968
1098
|
footer.span("Path: ");
|
|
969
1099
|
footer.em("^path");
|
|
970
1100
|
const breakpoint = palette.dialog({ node_label: "breakpoint", "aria-label": "Breakpoint", class: "breakpoint-dialog", connect_oncancel: "inspector.closeBreakpoint" });
|
|
@@ -1012,6 +1142,14 @@
|
|
|
1012
1142
|
} });
|
|
1013
1143
|
}
|
|
1014
1144
|
#unsubscribe() {
|
|
1145
|
+
++this.#styleRequest;
|
|
1146
|
+
this.#styles?.dispose();
|
|
1147
|
+
this.#styles = null;
|
|
1148
|
+
if (this.#ui) {
|
|
1149
|
+
this.#renderStyles([]);
|
|
1150
|
+
this.#ui.data.setItem("styles.busy", false);
|
|
1151
|
+
this.#ui.data.setItem("styles.status", "");
|
|
1152
|
+
}
|
|
1015
1153
|
this.#connectionRelease?.();
|
|
1016
1154
|
this.#connectionRelease = null;
|
|
1017
1155
|
++this.#generation;
|
|
@@ -1038,6 +1176,12 @@
|
|
|
1038
1176
|
try {
|
|
1039
1177
|
this.#nodes = [];
|
|
1040
1178
|
const root = ui.data.getItem("root");
|
|
1179
|
+
const tab = ui.data.getItem("tab");
|
|
1180
|
+
ui.data.setItem("stylesActive", tab === "styles");
|
|
1181
|
+
ui.data.setItem("treeActive", tab !== "styles");
|
|
1182
|
+
for (const name of ["data", "source", "styles"]) {
|
|
1183
|
+
ui.src.source.getNode(`main.palette.tabs.${name}`).setAttr({ "aria-selected": String(tab === name) });
|
|
1184
|
+
}
|
|
1041
1185
|
if (this.#connection) {
|
|
1042
1186
|
const configurations = new Bag();
|
|
1043
1187
|
for (const [index, record] of this.#connection.breakpoints.entries()) configurations.setItem(`b${index}`, new Bag(record));
|
|
@@ -1050,7 +1194,6 @@
|
|
|
1050
1194
|
ui.data.setItem("dataActive", root === "data");
|
|
1051
1195
|
ui.data.setItem("sourceActive", root === "source");
|
|
1052
1196
|
for (const name of ["data", "source"]) {
|
|
1053
|
-
ui.src.source.getNode(`main.palette.tabs.${name}`).setAttr({ "aria-selected": String(root === name) });
|
|
1054
1197
|
const tree = this.#trees[name];
|
|
1055
1198
|
tree.storeBag = this.#connection || this.#target ? this.#targetBag(name) : null;
|
|
1056
1199
|
const breakpoints = ui.data.getItem("breakpoints").getNodes().filter((node) => node.value.getItem("root") === name);
|
|
@@ -1070,6 +1213,132 @@
|
|
|
1070
1213
|
ui.data.setItem("error", error.message);
|
|
1071
1214
|
}
|
|
1072
1215
|
}
|
|
1216
|
+
async #loadStyles(type = "styles", command = {}) {
|
|
1217
|
+
const ui = this.#ui;
|
|
1218
|
+
const request = ++this.#styleRequest;
|
|
1219
|
+
ui.data.setItem("styles.busy", true);
|
|
1220
|
+
ui.data.setItem("styles.downloadUrl", null);
|
|
1221
|
+
ui.data.setItem("styles.noDownload", true);
|
|
1222
|
+
ui.data.setItem("styles.status", "");
|
|
1223
|
+
try {
|
|
1224
|
+
if (!this.#state() || this.#state() === "disposed" || this.#connection && this.#state() !== "started") {
|
|
1225
|
+
throw new Error("Select a connected page before editing CSS variables");
|
|
1226
|
+
}
|
|
1227
|
+
let rows;
|
|
1228
|
+
if (this.#connection) {
|
|
1229
|
+
const connection = this.#connection;
|
|
1230
|
+
await connection.request(type, command);
|
|
1231
|
+
rows = connection.styles;
|
|
1232
|
+
} else {
|
|
1233
|
+
this.#styles ??= new InspectorStyles(this.#target.src.renderer.destination.ownerDocument);
|
|
1234
|
+
if (type === "setStyle") this.#styles.set(command.name, command.value);
|
|
1235
|
+
if (type === "resetStyle") this.#styles.reset(command.name);
|
|
1236
|
+
if (type === "resetStyles") this.#styles.dispose();
|
|
1237
|
+
rows = this.#styles.snapshot();
|
|
1238
|
+
}
|
|
1239
|
+
if (this.#ui !== ui || request !== this.#styleRequest) return;
|
|
1240
|
+
this.#renderStyles(rows, type, command);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (this.#ui === ui && request === this.#styleRequest) ui.data.setItem("styles.status", error.message);
|
|
1243
|
+
} finally {
|
|
1244
|
+
if (this.#ui === ui && request === this.#styleRequest) {
|
|
1245
|
+
ui.data.setItem("styles.busy", false);
|
|
1246
|
+
ui.data.setItem("styles.downloadUrl", this.#styleDownloadUrl);
|
|
1247
|
+
ui.data.setItem("styles.noDownload", !this.#styleDownloadUrl);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
#renderStyles(rows, type = "styles", command = {}) {
|
|
1252
|
+
const ui = this.#ui;
|
|
1253
|
+
const browser = this.ownerDocument.defaultView;
|
|
1254
|
+
if (this.#styleDownloadUrl) browser.URL.revokeObjectURL(this.#styleDownloadUrl);
|
|
1255
|
+
this.#styleDownloadUrl = null;
|
|
1256
|
+
const overrides = rows.filter((row) => row.override);
|
|
1257
|
+
if (overrides.length) {
|
|
1258
|
+
const declarations = overrides.map((row) => ` ${browser.CSS.escape(row.name)}: ${row.override} !important;`).join("\n");
|
|
1259
|
+
const css = `/* Gramlot overrides. Load after the theme stylesheet. */
|
|
1260
|
+
:root {
|
|
1261
|
+
${declarations}
|
|
1262
|
+
}
|
|
1263
|
+
`;
|
|
1264
|
+
this.#styleDownloadUrl = browser.URL.createObjectURL(new browser.Blob([css], { type: "text/css;charset=utf-8" }));
|
|
1265
|
+
}
|
|
1266
|
+
ui.data.setItem("styles.downloadUrl", ui.data.getItem("styles.busy") ? null : this.#styleDownloadUrl);
|
|
1267
|
+
ui.data.setItem("styles.noDownload", ui.data.getItem("styles.busy") || !this.#styleDownloadUrl);
|
|
1268
|
+
const names = JSON.stringify(rows.map((row) => row.name));
|
|
1269
|
+
const rebuild = names !== this.#styleNames;
|
|
1270
|
+
this.#styleNames = names;
|
|
1271
|
+
const builder = rebuild ? new GramlotBuilder() : null;
|
|
1272
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1273
|
+
if (rebuild) {
|
|
1274
|
+
ui.src.source.getNode("main.palette.styles.rows").setValue(ui.src.prepareSource(new GramlotBuilder().source));
|
|
1275
|
+
ui.data.setItem("styles.rows", new Bag());
|
|
1276
|
+
}
|
|
1277
|
+
for (const [index, record] of rows.entries()) {
|
|
1278
|
+
const id = `v${index}`, path = `styles.rows.${id}`;
|
|
1279
|
+
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;
|
|
1280
|
+
const fields = {
|
|
1281
|
+
name: record.name,
|
|
1282
|
+
text: record.override || record.value,
|
|
1283
|
+
computed: record.value,
|
|
1284
|
+
dirty: false,
|
|
1285
|
+
color: color ?? "#000000",
|
|
1286
|
+
noColor: color === null,
|
|
1287
|
+
unchanged: !record.override
|
|
1288
|
+
};
|
|
1289
|
+
if (!rebuild && ui.data.getItem(`${path}.dirty`) && !["styles", "resetStyles"].includes(type)) {
|
|
1290
|
+
const acknowledged = record.name === command.name && (type === "resetStyle" || ui.data.getItem(`${path}.text`) === command.value);
|
|
1291
|
+
if (!acknowledged) {
|
|
1292
|
+
delete fields.text;
|
|
1293
|
+
delete fields.dirty;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (rebuild) ui.data.setItem(path, new Bag(fields));
|
|
1297
|
+
else for (const [key, value] of Object.entries(fields)) ui.data.setItem(`${path}.${key}`, value);
|
|
1298
|
+
if (!rebuild) continue;
|
|
1299
|
+
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";
|
|
1300
|
+
if (!groups.has(group)) {
|
|
1301
|
+
const section = builder.root.section({ class: "styles-group" });
|
|
1302
|
+
section.h3(group);
|
|
1303
|
+
groups.set(group, section);
|
|
1304
|
+
}
|
|
1305
|
+
const row = groups.get(group).div({ class: "style-row" });
|
|
1306
|
+
row.html_label(record.name, { for: `${this.#subscriber}-${id}`, title: record.name });
|
|
1307
|
+
row.input({
|
|
1308
|
+
id: `${this.#subscriber}-${id}`,
|
|
1309
|
+
value: `^${path}.text`,
|
|
1310
|
+
live: true,
|
|
1311
|
+
"aria-label": `${record.name} value`,
|
|
1312
|
+
title: `^${path}.computed`,
|
|
1313
|
+
"data-row": id,
|
|
1314
|
+
"data-field": "text",
|
|
1315
|
+
connect_oninput: "inspector.styleChanged",
|
|
1316
|
+
connect_onchange: "inspector.setStyle",
|
|
1317
|
+
connect_onkeydown: "inspector.styleKey",
|
|
1318
|
+
spellcheck: "false"
|
|
1319
|
+
});
|
|
1320
|
+
row.input({
|
|
1321
|
+
type: "color",
|
|
1322
|
+
value: `^${path}.color`,
|
|
1323
|
+
hidden: `^${path}.noColor`,
|
|
1324
|
+
"aria-label": `${record.name} color`,
|
|
1325
|
+
"data-row": id,
|
|
1326
|
+
"data-field": "color",
|
|
1327
|
+
connect_onchange: "inspector.setStyle"
|
|
1328
|
+
});
|
|
1329
|
+
row.button("\u21B6", {
|
|
1330
|
+
type: "button",
|
|
1331
|
+
"aria-label": `Reset ${record.name}`,
|
|
1332
|
+
title: "Reset variable",
|
|
1333
|
+
disabled: `^${path}.unchanged`,
|
|
1334
|
+
"data-row": id,
|
|
1335
|
+
connect_onclick: "inspector.resetStyle"
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
if (rebuild) ui.src.source.getNode("main.palette.styles.rows").setValue(ui.src.prepareSource(builder.source));
|
|
1339
|
+
ui.data.setItem("styles.noChanges", !rows.some((row) => row.override));
|
|
1340
|
+
ui.src.source.getNode("main.palette.styles.empty").setAttr({ hidden: rows.length !== 0 });
|
|
1341
|
+
}
|
|
1073
1342
|
#index(bag, labels) {
|
|
1074
1343
|
for (const node of bag.getNodes()) {
|
|
1075
1344
|
const path = [...labels, node.label];
|
|
@@ -1163,8 +1432,8 @@
|
|
|
1163
1432
|
const label = line.div({ class: "property-name" });
|
|
1164
1433
|
if (fresh) label.input({ value: "^.name", live: true, "aria-label": "New attribute name", "data-row": row.label, connect_oninput: "inspector.editChanged" });
|
|
1165
1434
|
else label.span(name);
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1435
|
+
const cell = line.div({ class: "property-value" });
|
|
1436
|
+
cell.input({
|
|
1168
1437
|
value: "^.text",
|
|
1169
1438
|
type: "^.inputType",
|
|
1170
1439
|
live: true,
|
|
@@ -1177,11 +1446,11 @@
|
|
|
1177
1446
|
connect_onkeydown: "inspector.editKey"
|
|
1178
1447
|
});
|
|
1179
1448
|
if (fresh) {
|
|
1180
|
-
const type =
|
|
1449
|
+
const type = cell.select({ value: "^.type", "aria-label": "New attribute type", "data-row": row.label, connect_onchange: "inspector.typeChanged" });
|
|
1181
1450
|
for (const value of editorTypes) type.option(value, { value });
|
|
1182
1451
|
}
|
|
1183
1452
|
if (!primary) {
|
|
1184
|
-
const button =
|
|
1453
|
+
const button = cell.button({
|
|
1185
1454
|
type: "button",
|
|
1186
1455
|
disabled: locked,
|
|
1187
1456
|
"aria-label": `Remove ${name || "new attribute"}`,
|