@gramlot/gramlot-examples 0.2.15 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +12 -5
  2. package/package.json +4 -3
  3. package/src/gramlot_examples/catalog.json +163 -1
  4. package/src/gramlot_examples/gallery/collections.js +4 -0
  5. package/src/gramlot_examples/gallery/dist/frame.js +88 -28
  6. package/src/gramlot_examples/gallery/dist/gallery.js +211 -52
  7. package/src/gramlot_examples/gallery/dist/inputs.js +24456 -0
  8. package/src/gramlot_examples/gallery/dist/inspector.js +42 -18
  9. package/src/gramlot_examples/gallery/dist/layout.js +541 -0
  10. package/src/gramlot_examples/gallery/dist/theme-styles.js +2 -2
  11. package/src/gramlot_examples/gallery/gallery-page.js +49 -17
  12. package/src/gramlot_examples/gallery/gallery.css +50 -4
  13. package/src/gramlot_examples/index.js +3 -2
  14. package/src/gramlot_examples/pages/README.md +5 -2
  15. package/src/gramlot_examples/pages/binding/06_style_and_visibility.md +77 -12
  16. package/src/gramlot_examples/pages/binding/09_forms.js +1 -1
  17. package/src/gramlot_examples/pages/components/01_textbox.js +32 -0
  18. package/src/gramlot_examples/pages/components/01_textbox.md +14 -0
  19. package/src/gramlot_examples/pages/components/02_checkbox.js +25 -0
  20. package/src/gramlot_examples/pages/components/02_checkbox.md +14 -0
  21. package/src/gramlot_examples/pages/components/03_radio_button_text.js +30 -0
  22. package/src/gramlot_examples/pages/components/03_radio_button_text.md +16 -0
  23. package/src/gramlot_examples/pages/components/04_text_box_area.js +23 -0
  24. package/src/gramlot_examples/pages/components/04_text_box_area.md +14 -0
  25. package/src/gramlot_examples/pages/components/05_passwordbox.js +23 -0
  26. package/src/gramlot_examples/pages/components/05_passwordbox.md +14 -0
  27. package/src/gramlot_examples/pages/components/06_radio_button.js +28 -0
  28. package/src/gramlot_examples/pages/components/06_radio_button.md +14 -0
  29. package/src/gramlot_examples/pages/components/07_colorpicker.js +24 -0
  30. package/src/gramlot_examples/pages/components/07_colorpicker.md +14 -0
  31. package/src/gramlot_examples/pages/components/08_horizontal_slider.js +24 -0
  32. package/src/gramlot_examples/pages/components/08_horizontal_slider.md +14 -0
  33. package/src/gramlot_examples/pages/components/09_vertical_slider.js +24 -0
  34. package/src/gramlot_examples/pages/components/09_vertical_slider.md +14 -0
  35. package/src/gramlot_examples/pages/components/10_number_textbox.js +30 -0
  36. package/src/gramlot_examples/pages/components/10_number_textbox.md +16 -0
  37. package/src/gramlot_examples/pages/components/11_date_textbox.js +23 -0
  38. package/src/gramlot_examples/pages/components/11_date_textbox.md +18 -0
  39. package/src/gramlot_examples/pages/components/12_time_textbox.js +23 -0
  40. package/src/gramlot_examples/pages/components/12_time_textbox.md +16 -0
  41. package/src/gramlot_examples/pages/components/13_filtering_select.js +23 -0
  42. package/src/gramlot_examples/pages/components/13_filtering_select.md +20 -0
  43. package/src/gramlot_examples/pages/components/14_callback_select.js +45 -0
  44. package/src/gramlot_examples/pages/components/14_callback_select.md +24 -0
  45. package/src/gramlot_examples/pages/components/15_combo_box.js +23 -0
  46. package/src/gramlot_examples/pages/components/15_combo_box.md +22 -0
  47. package/src/gramlot_examples/pages/components/16_checkbox_text.js +23 -0
  48. package/src/gramlot_examples/pages/components/16_checkbox_text.md +23 -0
  49. package/src/gramlot_examples/pages/components/17_date_calendar.js +25 -0
  50. package/src/gramlot_examples/pages/components/17_date_calendar.md +22 -0
  51. package/src/gramlot_examples/pages/components/18_validation.js +31 -0
  52. package/src/gramlot_examples/pages/components/18_validation.md +29 -0
  53. package/src/gramlot_examples/pages/components/19_formlet.js +26 -0
  54. package/src/gramlot_examples/pages/components/19_formlet.md +12 -0
  55. package/src/gramlot_examples/pages/components/20_group_box.js +26 -0
  56. package/src/gramlot_examples/pages/components/20_group_box.md +12 -0
  57. package/src/gramlot_examples/pages/components/21_switch.js +19 -0
  58. package/src/gramlot_examples/pages/components/21_switch.md +12 -0
  59. package/src/gramlot_examples/pages/components/22_button.js +18 -0
  60. package/src/gramlot_examples/pages/components/22_button.md +12 -0
  61. package/src/gramlot_examples/pages/components/23_toggle_button.js +18 -0
  62. package/src/gramlot_examples/pages/components/23_toggle_button.md +12 -0
  63. package/src/gramlot_examples/pages/components/24_dropdown_button.js +21 -0
  64. package/src/gramlot_examples/pages/components/24_dropdown_button.md +12 -0
  65. package/src/gramlot_examples/pages/components/25_validation_engine.js +51 -0
  66. package/src/gramlot_examples/pages/components/25_validation_engine.md +26 -0
  67. package/src/gramlot_examples/pages/components/README.md +40 -0
  68. package/src/gramlot_examples/pages/controllers/02_controller.js +1 -1
  69. package/src/gramlot_examples/pages/controllers/16_attribute_projection.js +3 -3
  70. package/src/gramlot_examples/pages/controllers/16_attribute_projection.md +8 -8
  71. package/src/gramlot_examples/pages/controllers/22_pointer_transforms.js +39 -0
  72. package/src/gramlot_examples/pages/controllers/22_pointer_transforms.md +64 -0
  73. package/src/gramlot_examples/pages/controllers/README.md +3 -0
  74. package/src/gramlot_examples/pages/html_svg/06_forms.js +3 -3
package/README.md CHANGED
@@ -12,7 +12,7 @@ npm install @gramlot/gramlot-examples
12
12
  [![PyPI](https://img.shields.io/pypi/v/gramlot-examples)](https://pypi.org/project/gramlot-examples/)
13
13
  [![npm](https://img.shields.io/npm/v/@gramlot/gramlot-examples)](https://www.npmjs.com/package/@gramlot/gramlot-examples)
14
14
 
15
- Current release: 0.2.15.
15
+ Current release: 0.3.0.
16
16
 
17
17
  [Documentation and learning sequence](https://gramlot-examples.readthedocs.io/en/latest/)
18
18
  include every lesson, its Python and JavaScript source, and the gallery guide.
@@ -24,19 +24,26 @@ The repositories of the family are described in
24
24
  [The Gramlot family](https://github.com/gramlot-org/gramlot/blob/main/docs/public/055-family.md)
25
25
  (GC-055).
26
26
 
27
+ ## Updating
28
+
29
+ Choose [packaged or source-checkout updates](docs/010-gallery.md#ge-010-050). A Python gallery installed from source also needs its JavaScript bundles rebuilt; updating Python files alone does not update the footer inspector.
30
+
27
31
  ## Pages
28
32
 
29
- [`src/gramlot_examples/pages/`](https://github.com/gramlot-org/gramlot-examples/blob/main/src/gramlot_examples/pages/README.md) holds three
33
+ [`src/gramlot_examples/pages/`](https://github.com/gramlot-org/gramlot-examples/blob/main/src/gramlot_examples/pages/README.md) holds four
30
34
  families. Each example is a Python page and its JavaScript equivalent, with a
31
35
  same-name README:
32
36
 
33
37
  - `html_svg/`: thirteen HTML and SVG pages, without binding (`e01`–`e13`);
34
38
  - `binding/`: eleven pages whose DOM follows the Data (`b01`–`b11`);
35
- - `controllers/`: twenty-one pages with formulas, controllers, named logic, events, the
39
+ - `controllers/`: twenty-two pages with formulas, controllers, named logic, events, the
36
40
  end-to-end story, `dataRpc` calls to the endpoints of the page, `remote` fragments and
37
- `dataRemote` values (`c01`–`c21`). Later lessons introduce topics, authored references, native ask,
41
+ `dataRemote` values (`c01`–`c22`). Later lessons introduce topics, authored references, native ask,
38
42
  parent/child messages, plain HTML buttons and delayed Gramlot clicks. The package
39
- requires core 0.2.19 or later; each lesson states which features it uses.
43
+ requires the current core release; each lesson states which features it uses.
44
+
45
+ - `components/`: twenty-five lessons for inputs, labelbox, formlet, group copy,
46
+ command buttons and field validation (`w01`–`w25`), with compact touch previews.
40
47
 
41
48
  Each `NN_name.js` exports `Page` and `Logic`; the Python page `NN_name.py` takes its
42
49
  `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.15",
3
+ "version": "0.3.0",
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.19"
17
+ "@gramlot/gramlot": ">=0.3.0"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@genrojs/bag": ">=0.11.0",
@@ -41,7 +41,8 @@
41
41
  "src/gramlot_examples/gallery/page.js",
42
42
  "src/gramlot_examples/gallery/gallery-page.js",
43
43
  "src/gramlot_examples/gallery/gallery.css",
44
- "src/gramlot_examples/gallery/dist"
44
+ "src/gramlot_examples/gallery/dist",
45
+ "src/gramlot_examples/gallery/collections.js"
45
46
  ],
46
47
  "engines": {
47
48
  "node": ">=22"
@@ -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 \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."
75
+ "explanation": "Helpers and loops build the cards. Each × 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",
@@ -323,6 +323,168 @@
323
323
  "title": "Plain and delayed buttons",
324
324
  "folder": "21_plain_and_delayed_buttons",
325
325
  "description": "Core 0.2.19: native html_button and grouped clicks with _delay."
326
+ },
327
+ {
328
+ "key": "c22",
329
+ "title": "Pointer templates and expressions",
330
+ "folder": "22_pointer_transforms",
331
+ "description": "Core 0.2.20: revisit CSS lengths, boolean state and formatted text with attribute pointer transforms."
332
+ }
333
+ ]
334
+ },
335
+ {
336
+ "key": "components",
337
+ "title": "Input components",
338
+ "examples": [
339
+ {
340
+ "key": "w01",
341
+ "title": "Textbox and labelbox",
342
+ "folder": "01_textbox",
343
+ "description": "Two textboxes share a customer name. Type in either one, switch live editing off and on, and move the label without rebuilding the field."
344
+ },
345
+ {
346
+ "key": "w02",
347
+ "title": "Checkbox and labelbox",
348
+ "folder": "02_checkbox",
349
+ "description": "Two checkboxes share one boolean preference. Click either caption, then disable both using the State checkbox."
350
+ },
351
+ {
352
+ "key": "w03",
353
+ "title": "RadioButtonText and labelbox",
354
+ "folder": "03_radio_button_text",
355
+ "description": "Choose one delivery option in each group. Change the column count and label position, and try the arrow keys inside a group."
356
+ },
357
+ {
358
+ "key": "w04",
359
+ "title": "Multiline text and labelbox",
360
+ "folder": "04_text_box_area",
361
+ "description": "Two multiline fields share one note. Switch live editing and move the label while keeping the text and selection."
362
+ },
363
+ {
364
+ "key": "w05",
365
+ "title": "Password and labelbox",
366
+ "folder": "05_passwordbox",
367
+ "description": "Two password fields share a sample value. The browser masks the characters; changing either field updates the other."
368
+ },
369
+ {
370
+ "key": "w06",
371
+ "title": "Single radio buttons",
372
+ "folder": "06_radio_button",
373
+ "description": "Each radio binds its own boolean. A shared group makes the choices exclusive; a separate group stays independent."
374
+ },
375
+ {
376
+ "key": "w07",
377
+ "title": "Color and labelbox",
378
+ "folder": "07_colorpicker",
379
+ "description": "Choose a color in either picker. Both controls and the swatch follow the same Data value."
380
+ },
381
+ {
382
+ "key": "w08",
383
+ "title": "Horizontal slider",
384
+ "folder": "08_horizontal_slider",
385
+ "description": "Two horizontal sliders share a numeric level. Drag or use the arrow keys; switch live editing to compare immediate and committed updates."
386
+ },
387
+ {
388
+ "key": "w09",
389
+ "title": "Vertical slider",
390
+ "folder": "09_vertical_slider",
391
+ "description": "Vertical and horizontal sliders share the same level. The vertical minimum is at the bottom and the maximum at the top."
392
+ },
393
+ {
394
+ "key": "w10",
395
+ "title": "Number editing",
396
+ "folder": "10_number_textbox",
397
+ "description": "Try a comma decimal, an integer and an exact Decimal. Invalid text stays in the field without changing Data."
398
+ },
399
+ {
400
+ "key": "w11",
401
+ "title": "Date editing",
402
+ "folder": "11_date_textbox",
403
+ "description": "Enter a calendar date or a word such as oggi or domani. Two fields share one civil date."
404
+ },
405
+ {
406
+ "key": "w12",
407
+ "title": "Time editing",
408
+ "folder": "12_time_textbox",
409
+ "description": "Edit a civil time with hours, minutes and optional seconds. The two fields keep the same typed value."
410
+ },
411
+ {
412
+ "key": "w13",
413
+ "title": "Filtering select",
414
+ "folder": "13_filtering_select",
415
+ "description": "Search a caption, store its code. Two fields share the same choice and the same list."
416
+ },
417
+ {
418
+ "key": "w14",
419
+ "title": "Callback select",
420
+ "folder": "14_callback_select",
421
+ "description": "Search 100 random samples generated once in this page. The callback reads a Data Bag, without a server request."
422
+ },
423
+ {
424
+ "key": "w15",
425
+ "title": "Combo box",
426
+ "folder": "15_combo_box",
427
+ "description": "Choose a suggested city or enter a different one. Two fields share the same text; suggestions do not restrict what you can write."
428
+ },
429
+ {
430
+ "key": "w16",
431
+ "title": "Checkbox group",
432
+ "folder": "16_checkbox_text",
433
+ "description": "Choose several notification channels. Two groups share the same comma-separated codes; clearing all choices writes null."
434
+ },
435
+ {
436
+ "key": "w17",
437
+ "title": "Date calendar",
438
+ "folder": "17_date_calendar",
439
+ "description": "Pick a day or type a date. The inline calendar, popup and text-only field share the same Data value."
440
+ },
441
+ {
442
+ "key": "w18",
443
+ "title": "Required values and length",
444
+ "folder": "18_validation",
445
+ "description": "Invalid drafts remain visible with a message while Data keeps the last accepted value."
446
+ },
447
+ {
448
+ "key": "w19",
449
+ "folder": "19_formlet",
450
+ "title": "Formlet and shared labels",
451
+ "description": "Arrange fields in columns and share label settings. Changing the layout preserves each field and its draft."
452
+ },
453
+ {
454
+ "key": "w20",
455
+ "folder": "20_group_box",
456
+ "title": "GroupBox and typed copy",
457
+ "description": "Group customer data under a heading. Copy TYTX with its types or choose plain JSON from the copy menu."
458
+ },
459
+ {
460
+ "key": "w21",
461
+ "folder": "21_switch",
462
+ "title": "Checkbox as a switch",
463
+ "description": "A switch and a checkbox share one boolean. Compare their appearance, captions and read-only state."
464
+ },
465
+ {
466
+ "key": "w22",
467
+ "folder": "22_button",
468
+ "title": "Button commands",
469
+ "description": "Compare the Gramlot command button with plain HTML. Try confirmation and a delayed command."
470
+ },
471
+ {
472
+ "key": "w23",
473
+ "folder": "23_toggle_button",
474
+ "title": "Toggle button with LED",
475
+ "description": "A command button and a checkbox share a boolean. The LED and aria-pressed follow Data."
476
+ },
477
+ {
478
+ "key": "w24",
479
+ "folder": "24_dropdown_button",
480
+ "title": "Dropdown commands and persistence",
481
+ "description": "The main surface executes; the arrow opens the menu. Shift-select a command to run it and make it the default."
482
+ },
483
+ {
484
+ "key": "w25",
485
+ "folder": "25_validation_engine",
486
+ "title": "Validation rules and callbacks",
487
+ "description": "Transformations, conditions, warnings, local callbacks and remote validation."
326
488
  }
327
489
  ]
328
490
  }
@@ -0,0 +1,4 @@
1
+ // Collections selected by the gallery application.
2
+ import inputs from '@gramlot/gramlot/components/inputs';
3
+ import layout from '@gramlot/gramlot/components/layout';
4
+ export const collections = [inputs, layout].map(collection => ({grammar: collection.toDocument(), module: `/gallery/dist/${collection.name}.js`}));
@@ -1,4 +1,45 @@
1
1
  (() => {
2
+ // node_modules/@gramlot/gramlot/src/index-theme.js
3
+ var THEME_READY = "index:theme-ready";
4
+ var THEME_SET = "index:theme-set";
5
+ var THEMES = /* @__PURE__ */ new Set(["light", "dark"]);
6
+ var APPEARANCES = /* @__PURE__ */ new Set(["base", "compact", "mimi", "joanna"]);
7
+
8
+ // src/gramlot_examples/gallery/browser/gallery-settings.js
9
+ var SETTINGS_SET = "gallery:settings-set";
10
+ var LABEL_POSITIONS = /* @__PURE__ */ new Set(["example", "TL", "TC", "TR", "L", "R", "BL", "BC", "BR"]);
11
+ function mountFrameSettings(renderer, owner) {
12
+ const window2 = renderer.destination.ownerDocument.defaultView;
13
+ if (window2.parent === window2) return;
14
+ const originals = /* @__PURE__ */ new Map();
15
+ const message = (event) => {
16
+ if (event.source !== window2.parent || event.data?.type !== SETTINGS_SET || !LABEL_POSITIONS.has(event.data.labelPosition)) return;
17
+ if (["auto", "on", "off"].includes(event.data.touchMode)) renderer.binding.gramlot.dom.setTouchMode(event.data.touchMode);
18
+ const position = event.data.labelPosition;
19
+ if (position === "example") {
20
+ for (const [node, attrs] of originals) if (renderer.records.has(node)) node.setAttr(attrs);
21
+ originals.clear();
22
+ return;
23
+ }
24
+ for (const [node] of renderer.records) {
25
+ if (node.getAttr("lbl") == null) continue;
26
+ const component = node._getMeta("gramlot_component");
27
+ if (component && !component.parameters.value) continue;
28
+ if (!originals.has(node)) {
29
+ originals.set(node, Object.fromEntries(["lbl_position"].map((name) => [name, node.getAttr(name)])));
30
+ }
31
+ const attrs = originals.get(node);
32
+ node.setAttr({ ...attrs, lbl_position: position });
33
+ }
34
+ };
35
+ window2.addEventListener("message", message);
36
+ renderer.onDispose(owner, () => {
37
+ window2.removeEventListener("message", message);
38
+ for (const [node, attrs] of originals) if (renderer.records.has(node)) node.setAttr(attrs);
39
+ originals.clear();
40
+ });
41
+ }
42
+
2
43
  // inspector-runtime:runtime
3
44
  var Gramlot = window.gramlot.constructor;
4
45
  var Bag = window.gramlot.data.constructor;
@@ -49,6 +90,9 @@
49
90
  }
50
91
  throw new Error(`Invalid ${type} value: ${text || "(empty)"}`);
51
92
  }
93
+ function readParameterEnums(node) {
94
+ return Object.fromEntries(Object.entries(node._getMeta("gramlot_component")?.parameters ?? {}).filter(([, spec]) => spec.enum).map(([name, spec]) => [name, spec.enum]));
95
+ }
52
96
  var InspectorStyles = class {
53
97
  #document;
54
98
  #element = null;
@@ -137,7 +181,7 @@
137
181
  if (!identities.has(node)) identities.set(node, ++nextNode);
138
182
  return identities.get(node);
139
183
  };
140
- const describe = (store, ancestors = /* @__PURE__ */ new Set()) => {
184
+ const describe = (store, root, ancestors = /* @__PURE__ */ new Set()) => {
141
185
  if (ancestors.has(store)) throw new Error("Inspector snapshots require an acyclic Bag");
142
186
  const next = /* @__PURE__ */ new Set([...ancestors, store]);
143
187
  return store.getNodes().map((node) => {
@@ -148,15 +192,16 @@
148
192
  label: node.label,
149
193
  revision: versions.get(node) ?? 0,
150
194
  value: branch ? null : cell(value),
151
- children: branch ? describe(value, next) : null,
195
+ children: branch ? describe(value, root, next) : null,
152
196
  readonly: !!node.resolver,
197
+ parameterEnums: root === "source" ? readParameterEnums(node) : {},
153
198
  attrs: Object.entries(node.getAttr()).filter(([name]) => name !== "_meta").map(([name, value2]) => [name, cell(value2)])
154
199
  };
155
200
  });
156
201
  };
157
202
  const snapshot = () => ({
158
- data: describe(bag("data")),
159
- source: describe(bag("source")),
203
+ data: describe(bag("data"), "data"),
204
+ source: describe(bag("source"), "source"),
160
205
  breakpoints: [...breakpoints.values()].map(({ test, ...record }) => record)
161
206
  });
162
207
  const send = (data) => {
@@ -376,6 +421,7 @@
376
421
  id: row.id,
377
422
  revision: row.revision,
378
423
  branch,
424
+ parameterEnums: row.parameterEnums,
379
425
  readonly: row.readonly || !!row.value?.readonly,
380
426
  readonlyAttrs: new Set(row.attrs.filter(([, descriptor]) => descriptor.readonly).map(([name]) => name)),
381
427
  valueWire: JSON.stringify(row.value),
@@ -405,7 +451,7 @@
405
451
  this.#notify();
406
452
  }
407
453
  };
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";
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";
409
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: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";
410
456
  var sequence = 0;
411
457
  var GramlotPocTree = class extends HTMLElement {
@@ -855,6 +901,10 @@
855
901
  ui.data.setItem("editor.dirty", true);
856
902
  ui.data.setItem("editor.status", "Unsaved changes");
857
903
  }
904
+ enumChanged(node) {
905
+ this.editChanged(node);
906
+ inspector.#applyEdit();
907
+ }
858
908
  typeChanged(node) {
859
909
  const row = ui.data.getItem(`editor.rows.${node.getAttr("data-row")}`);
860
910
  row.setItem("inputType", inspector.#inputType(row.getItem("type")));
@@ -1041,7 +1091,7 @@
1041
1091
  const targetbar = palette.div({ node_label: "targetbar", class: "targetbar", hidden: "^singleTarget" });
1042
1092
  targetbar.select({ node_label: "targets", value: "^targetId", disabled: "^busy", "aria-label": "Inspected page", connect_onchange: "inspector.selectTarget" });
1043
1093
  const toolbar = palette.webcomponent({ code: "gramlot-poc-tabs", node_label: "tabs", role: "tablist", "aria-label": "Inspected root" });
1044
- for (const name of ["Data", "Source", "Styles"]) toolbar.button(name, {
1094
+ for (const name of ["Data", "Source", "Styles"]) toolbar.html_button(name, {
1045
1095
  node_label: name.toLowerCase(),
1046
1096
  type: "button",
1047
1097
  role: "tab",
@@ -1063,7 +1113,7 @@
1063
1113
  const properties = content.section({ node_label: "properties", slot: "bottom", class: "properties", "aria-label": "Selected node properties" });
1064
1114
  const heading = properties.div({ class: "properties-heading" });
1065
1115
  heading.span("Properties", { class: "heading-label" });
1066
- heading.button("^breakpointCount", {
1116
+ heading.html_button("^breakpointCount", {
1067
1117
  type: "button",
1068
1118
  class: "breakpoint-count",
1069
1119
  hidden: "^noBreakpoints",
@@ -1071,7 +1121,7 @@
1071
1121
  title: "Active breakpoints",
1072
1122
  connect_onclick: "inspector.showBreakpoints"
1073
1123
  });
1074
- heading.button("+", {
1124
+ heading.html_button("+", {
1075
1125
  type: "button",
1076
1126
  "aria-label": "Add attribute",
1077
1127
  title: "Add attribute",
@@ -1087,8 +1137,8 @@
1087
1137
  const styles = palette.section({ node_label: "styles", class: "styles-panel", hidden: "^treeActive", "aria-label": "CSS variables" });
1088
1138
  const stylebar = styles.div({ class: "styles-toolbar" });
1089
1139
  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" });
1140
+ stylebar.html_button("Refresh", { type: "button", disabled: "^styles.busy", connect_onclick: "inspector.refreshStyles" });
1141
+ stylebar.html_button("Reset all", { type: "button", disabled: "^styles.noChanges", connect_onclick: "inspector.resetStyles" });
1092
1142
  stylebar.a("Download CSS", { href: "^styles.downloadUrl", download: "theme-overrides.css", "aria-disabled": "^styles.noDownload" });
1093
1143
  styles.p("Temporary preview \xB7 Enter or leave a field to apply. Closing the connection clears previews.", { class: "styles-help" });
1094
1144
  styles.p("^styles.status", { role: "status", class: "styles-status" });
@@ -1104,12 +1154,12 @@
1104
1154
  breakpoint.input({ value: "^editor.condition", live: true, connect_oninput: "inspector.breakpointChanged", "aria-label": "Breakpoint condition", placeholder: "Condition (optional)" });
1105
1155
  breakpoint.p("^breakpointStatus", { role: "status", class: "breakpoint-status" });
1106
1156
  const actions = breakpoint.div({ class: "dialog-actions" });
1107
- actions.button("Cancel", { type: "button", connect_onclick: "inspector.closeBreakpoint" });
1108
- actions.button("Save", { type: "button", connect_onclick: "inspector.setBreakpoint" });
1157
+ actions.html_button("Cancel", { type: "button", connect_onclick: "inspector.closeBreakpoint" });
1158
+ actions.html_button("Save", { type: "button", connect_onclick: "inspector.setBreakpoint" });
1109
1159
  const list = palette.dialog({ node_label: "breakpoints", "aria-label": "Active breakpoints", class: "breakpoint-dialog" });
1110
1160
  list.h2("Active breakpoints");
1111
1161
  list.div({ node_label: "entries" });
1112
- list.div({ class: "dialog-actions" }).button("Close", { type: "button", connect_onclick: "inspector.closeBreakpoints" });
1162
+ list.div({ class: "dialog-actions" }).html_button("Close", { type: "button", connect_onclick: "inspector.closeBreakpoints" });
1113
1163
  ui.src.startSource(builder.source);
1114
1164
  for (const name of ["data", "source"]) this.#trees[name] = ui.dom.getDomNode(ui.src.source.getNode(`main.palette.content.tree.${name}`));
1115
1165
  this.#renderTargets();
@@ -1326,7 +1376,7 @@ ${declarations}
1326
1376
  "data-field": "color",
1327
1377
  connect_onchange: "inspector.setStyle"
1328
1378
  });
1329
- row.button("\u21B6", {
1379
+ row.html_button("\u21B6", {
1330
1380
  type: "button",
1331
1381
  "aria-label": `Reset ${record.name}`,
1332
1382
  title: "Reset variable",
@@ -1396,6 +1446,7 @@ ${declarations}
1396
1446
  this.#snapshot = entry ? {
1397
1447
  node: entry.node,
1398
1448
  value: entry.node.getValue(true),
1449
+ parameterEnums: ui.data.getItem("root") !== "source" ? {} : this.#connection ? this.#connection.nodeInfo(entry.node).parameterEnums : readParameterEnums(entry.node),
1399
1450
  attrs: { ...entry.node.getAttr() },
1400
1451
  resolver: entry.node.resolver,
1401
1452
  revision: this.#connection?.nodeInfo(entry.node)?.revision
@@ -1433,7 +1484,20 @@ ${declarations}
1433
1484
  if (fresh) label.input({ value: "^.name", live: true, "aria-label": "New attribute name", "data-row": row.label, connect_oninput: "inspector.editChanged" });
1434
1485
  else label.span(name);
1435
1486
  const cell = line.div({ class: "property-value" });
1436
- cell.input({
1487
+ const choices = !primary && this.#snapshot.parameterEnums[name];
1488
+ const value = data.getItem("text");
1489
+ if (choices && choices.includes(value)) {
1490
+ const select = cell.select({
1491
+ value: "^.text",
1492
+ class: "enum-value",
1493
+ disabled: locked,
1494
+ "aria-label": `${name} value`,
1495
+ "data-row": row.label,
1496
+ connect_onchange: "inspector.enumChanged",
1497
+ connect_onkeydown: "inspector.editKey"
1498
+ });
1499
+ for (const choice of choices) select.option(choice, { value: choice });
1500
+ } else cell.input({
1437
1501
  value: "^.text",
1438
1502
  type: "^.inputType",
1439
1503
  live: true,
@@ -1447,10 +1511,10 @@ ${declarations}
1447
1511
  });
1448
1512
  if (fresh) {
1449
1513
  const type = cell.select({ value: "^.type", "aria-label": "New attribute type", "data-row": row.label, connect_onchange: "inspector.typeChanged" });
1450
- for (const value of editorTypes) type.option(value, { value });
1514
+ for (const value2 of editorTypes) type.option(value2, { value: value2 });
1451
1515
  }
1452
1516
  if (!primary) {
1453
- const button = cell.button({
1517
+ const button = cell.html_button({
1454
1518
  type: "button",
1455
1519
  disabled: locked,
1456
1520
  "aria-label": `Remove ${name || "new attribute"}`,
@@ -1486,6 +1550,7 @@ ${declarations}
1486
1550
  this.#loadEditor(this.#selected());
1487
1551
  } else {
1488
1552
  this.#snapshot = {
1553
+ ...this.#snapshot,
1489
1554
  node: target,
1490
1555
  value: target.getValue(true),
1491
1556
  attrs: { ...target.getAttr() },
@@ -1645,7 +1710,7 @@ ${declarations}
1645
1710
  const config = node.value;
1646
1711
  const row = builder.root.div({ class: "breakpoint-entry" });
1647
1712
  row.span(`${config.getItem("root")}: ${config.getItem("path")} \xB7 ${config.getItem("mode")} \xB7 ${config.getItem("hits")} hits`);
1648
- row.button("\xD7", {
1713
+ row.html_button("\xD7", {
1649
1714
  type: "button",
1650
1715
  "aria-label": `Remove breakpoint ${config.getItem("root")}: ${config.getItem("path")}`,
1651
1716
  "data-breakpoint": node.label,
@@ -1726,31 +1791,25 @@ ${declarations}
1726
1791
  });
1727
1792
  }
1728
1793
 
1729
- // node_modules/@gramlot/gramlot/src/index-theme.js
1730
- var THEME_READY = "index:theme-ready";
1731
- var THEME_SET = "index:theme-set";
1732
- var THEMES = /* @__PURE__ */ new Set(["light", "dark"]);
1733
- var APPEARANCES = /* @__PURE__ */ new Set(["base", "compact"]);
1734
-
1735
1794
  // src/gramlot_examples/gallery/dist/theme-styles.js
1736
- var compactStyle2 = '/* Gramlot Compact: load after gramlot-base/theme.css. Colors and behavior stay in Base. */\n@layer gramlot.tokens {\n :root {\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: 28px;\n padding: .25rem .5rem;\n font-size: 1rem;\n line-height: 1.35;\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';
1795
+ 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' };
1737
1796
 
1738
1797
  // src/gramlot_examples/gallery/browser/frame-theme.js
1739
1798
  function mountFrameTheme(renderer, node) {
1740
1799
  const window2 = renderer.destination.ownerDocument.defaultView;
1741
1800
  if (window2.parent === window2) return;
1742
- const style = node.builder.wrapSource(node.parentBag).style(compactStyle2, { media: "all" });
1801
+ const style = node.builder.wrapSource(node.parentBag).style(appearanceStyles2.compact);
1743
1802
  const message = (event) => {
1744
1803
  if (event.source !== window2.parent || event.data?.type !== THEME_SET || !THEMES.has(event.data.theme) || !APPEARANCES.has(event.data.appearance)) return;
1745
1804
  window2.document.documentElement.setAttribute("data-theme", event.data.theme);
1746
- style.setAttr({ media: event.data.appearance === "compact" ? "all" : "not all" });
1805
+ style.setValue(appearanceStyles2[event.data.appearance]);
1747
1806
  };
1748
1807
  window2.addEventListener("message", message);
1749
1808
  renderer.onDispose(node, () => {
1750
1809
  window2.removeEventListener("message", message);
1751
1810
  if (style.parentBag) style.parentBag.popNode(style.label);
1752
1811
  });
1753
- window2.parent.postMessage({ type: THEME_READY }, "*");
1812
+ window2.parent.postMessage({ type: THEME_READY, devices: renderer.binding.gramlot.devices }, "*");
1754
1813
  }
1755
1814
 
1756
1815
  // src/gramlot_examples/gallery/browser/frame-entry.js
@@ -1760,6 +1819,7 @@ ${declarations}
1760
1819
  const record = renderer.elements.get(script);
1761
1820
  if (!record || renderer.disposed || renderer.records.get(record.node) !== record) return;
1762
1821
  mountFrameInspector(renderer, record.node);
1822
+ mountFrameSettings(renderer, record.node);
1763
1823
  mountFrameTheme(renderer, record.node);
1764
1824
  });
1765
1825
  })();