@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
@@ -0,0 +1,541 @@
1
+ // node_modules/@gramlot/gramlot/src/webcomponent.js
2
+ var NAME = /^[a-z][.0-9_a-z\-\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]*$/u;
3
+ var RESERVED = /* @__PURE__ */ new Set([
4
+ "annotation-xml",
5
+ "color-profile",
6
+ "font-face",
7
+ "font-face-src",
8
+ "font-face-uri",
9
+ "font-face-format",
10
+ "font-face-name",
11
+ "missing-glyph"
12
+ ]);
13
+ function webcomponentCode(attrs) {
14
+ const code = attrs.code;
15
+ if (typeof code !== "string" || NAME.exec(code)?.[0] !== code || !code.includes("-") || RESERVED.has(code)) {
16
+ throw new TypeError("webcomponent: code must be a literal custom-element name");
17
+ }
18
+ return code;
19
+ }
20
+
21
+ // node_modules/@gramlot/gramlot/src/components/registry.js
22
+ function freeze(value) {
23
+ for (const child of Object.values(value)) if (child && typeof child === "object") freeze(child);
24
+ return Object.freeze(value);
25
+ }
26
+ function annotation(parameter) {
27
+ if (typeof parameter.dtype !== "string" && !Array.isArray(parameter.dtype)) throw new TypeError("Component parameter requires dtype");
28
+ const alternatives = (Array.isArray(parameter.dtype) ? parameter.dtype : [parameter.dtype]).map((dtype) => ({ kind: "dtype", dtype }));
29
+ if (parameter.nullable) alternatives.push({ kind: "dtype", dtype: "NN" });
30
+ if (parameter.binding && parameter.dtype !== "T") alternatives.push({
31
+ kind: "annotated",
32
+ base: { kind: "dtype", dtype: "T" },
33
+ metadata: [{ kind: "regex", pattern: "^[\\^=].+", flags: 0 }]
34
+ });
35
+ return alternatives.length === 1 ? alternatives[0] : { kind: "union", items: alternatives };
36
+ }
37
+ function defineComponent({ name, tag, base, mixins = [], parameters = {}, subTags = "", presentationDefaults = [], dataScope = false, command = false, implementation }) {
38
+ webcomponentCode({ code: tag });
39
+ if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) throw new TypeError("Invalid component authoring name");
40
+ const effective = { ...base.parameters };
41
+ for (const mixin of mixins) {
42
+ for (const [key, spec] of Object.entries(mixin.parameters)) {
43
+ if (Object.hasOwn(effective, key)) throw new Error(`Duplicate mixin parameter: ${key}`);
44
+ effective[key] = spec;
45
+ }
46
+ }
47
+ Object.assign(effective, parameters);
48
+ for (const spec of Object.values(effective)) annotation(spec);
49
+ return Object.freeze({
50
+ name,
51
+ tag,
52
+ base,
53
+ subTags,
54
+ presentationDefaults: Object.freeze([...presentationDefaults]),
55
+ dataScope,
56
+ command,
57
+ mixins: Object.freeze([...mixins]),
58
+ parameters: freeze(structuredClone(effective)),
59
+ implementation
60
+ });
61
+ }
62
+ var ComponentCollection = class {
63
+ #classes = /* @__PURE__ */ new WeakMap();
64
+ constructor({ name, components }) {
65
+ this.name = name;
66
+ this.components = Object.freeze([...components]);
67
+ const names = /* @__PURE__ */ new Set(), tags = /* @__PURE__ */ new Set();
68
+ for (const component of components) {
69
+ if (names.has(component.name.toLowerCase()) || tags.has(component.tag)) {
70
+ throw new Error(`Duplicate component: ${component.name}`);
71
+ }
72
+ names.add(component.name.toLowerCase());
73
+ tags.add(component.tag);
74
+ }
75
+ }
76
+ /** Same JSON consumed by Python and JavaScript Builders. */
77
+ toDocument() {
78
+ return {
79
+ document_format: { name: "builder_grammar", version: "1.1" },
80
+ grammar: { name: this.name, version: null, title: this.name, description: null },
81
+ abstracts: {},
82
+ elements: Object.fromEntries(this.components.map((component) => [component.name, {
83
+ doc: null,
84
+ sub_tags: component.subTags,
85
+ parent_tags: null,
86
+ inherits_from: null,
87
+ ns: null,
88
+ node_label: null,
89
+ collection_key: null,
90
+ attributes: {
91
+ parameters: [
92
+ ...Object.entries(component.parameters).map(([name, spec]) => ({
93
+ name,
94
+ kind: "keyword_only",
95
+ role: "attribute",
96
+ annotation: annotation(spec),
97
+ has_default: true,
98
+ default: spec.default ?? null
99
+ })),
100
+ {
101
+ name: "attr",
102
+ kind: "var_keyword",
103
+ role: "attribute",
104
+ annotation: { kind: "any" },
105
+ has_default: false
106
+ }
107
+ ],
108
+ accepts_var_keyword: true,
109
+ accepts_var_positional: false
110
+ },
111
+ _meta: { render_tag: component.tag, gramlot_component: {
112
+ parameters: structuredClone(component.parameters),
113
+ presentationDefaults: [...component.presentationDefaults],
114
+ dataScope: component.dataScope,
115
+ command: component.command
116
+ } }
117
+ }]))
118
+ };
119
+ }
120
+ /** Register once per document realm, only when the collection is activated. */
121
+ register(window) {
122
+ if (this.#classes.has(window)) return;
123
+ const classes = [];
124
+ for (const component of this.components) {
125
+ if (window.customElements.get(component.tag)) throw new Error(`Custom element already registered: ${component.tag}`);
126
+ let Base = component.base(window.HTMLElement);
127
+ for (const mixin of component.mixins) Base = mixin(Base);
128
+ const Class = component.implementation(Base);
129
+ Object.defineProperty(Class, "gramlotComponent", { value: component });
130
+ classes.push([component.tag, Class]);
131
+ }
132
+ for (const [tag, Class] of classes) window.customElements.define(tag, Class);
133
+ this.#classes.set(window, classes);
134
+ }
135
+ };
136
+ function defineCollection(options) {
137
+ return new ComponentCollection(options);
138
+ }
139
+
140
+ // node_modules/@gramlot/gramlot/src/components/labelbox.js
141
+ var POSITIONS = ["TL", "TC", "TR", "L", "R", "BL", "BC", "BR"];
142
+ function project(element, attrs, adapt, previous) {
143
+ const next = adapt(attrs);
144
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(next)])) {
145
+ if (next[key] == null) element.removeAttribute(key);
146
+ else element.setAttribute(key, String(next[key]));
147
+ }
148
+ return next;
149
+ }
150
+ function Labelbox(Base) {
151
+ return class extends Base {
152
+ #previous = { box: {}, label: {}, labelBox: {}, controlBox: {} };
153
+ constructor() {
154
+ super();
155
+ this.box = this.ownerDocument.createElement("div");
156
+ this.box.className = "labelbox";
157
+ this.labelBox = this.ownerDocument.createElement("div");
158
+ this.labelBox.className = "label-box";
159
+ this.fieldLabel = this.ownerDocument.createElement("label");
160
+ this.fieldLabel.id = "field-label";
161
+ this.fieldLabel.htmlFor = "control";
162
+ this.labelBox.append(this.fieldLabel);
163
+ this.box.append(this.labelBox, this.controlBox);
164
+ const style = this.ownerDocument.createElement("style");
165
+ style.textContent = `.labelbox{--gramlot-label-width:initial;--gramlot-formlet-label-width:initial;display:grid;gap:var(--gramlot-label-gap,.25em);grid-template-areas:'label' 'control'}
166
+ .label-box{grid-area:label;color:var(--gramlot-label-color,var(--gramlot-text-secondary,inherit));font-size:var(--gramlot-label-size,1em)} .control-box{grid-area:control}
167
+ .labelbox[data-position='L']{grid-template-areas:'label control';align-items:center}
168
+ .labelbox[data-position='R']{grid-template-areas:'control label';align-items:center}
169
+ .labelbox[data-position^='B']{grid-template-areas:'control' 'label'}
170
+ .labelbox[data-position$='C']>.label-box{text-align:center}
171
+ .labelbox[data-position$='R']>.label-box{text-align:right}
172
+ .label-box[hidden]{display:none} .label-box label{display:block}
173
+ .labelbox[data-position='L']>.label-box{text-align:right;padding-right:2px}
174
+ .labelbox[data-formlet-aligned]>.label-box>label{width:max-content;margin-left:auto}
175
+ .labelbox[data-position='L']{grid-template-columns:var(--gramlot-label-width,var(--gramlot-formlet-label-width,max-content)) minmax(0,1fr)}`;
176
+ this.shadowRoot.append(style, this.box);
177
+ }
178
+ projectAttributes(attrs, adapt) {
179
+ const rest = super.projectAttributes(attrs, adapt);
180
+ const { lbl, lbl_position = "TL" } = this.parameters;
181
+ if (!POSITIONS.includes(lbl_position)) throw new TypeError(`Invalid lbl_position: ${lbl_position}`);
182
+ const groups = { box: {}, label: {}, labelBox: {}, controlBox: {} };
183
+ const prefixes = [["box_l_", "labelBox"], ["box_c_", "controlBox"], ["lbl_", "label"], ["box_", "box"]];
184
+ for (const [name, value] of Object.entries(rest)) {
185
+ const match = prefixes.find(([prefix]) => name.startsWith(prefix));
186
+ if (match) {
187
+ groups[match[1]][name.slice(match[0].length)] = value;
188
+ delete rest[name];
189
+ }
190
+ }
191
+ const elements = { box: this.box, label: this.fieldLabel, labelBox: this.labelBox, controlBox: this.controlBox };
192
+ for (const [name, element] of Object.entries(elements)) {
193
+ this.#previous[name] = project(element, groups[name], adapt, this.#previous[name]);
194
+ }
195
+ if (attrs.lbl_width == null) this.box.style.removeProperty("--gramlot-label-width");
196
+ else this.box.style.setProperty("--gramlot-label-width", attrs.lbl_width);
197
+ this.box.classList.add("labelbox");
198
+ this.labelBox.classList.add("label-box");
199
+ this.controlBox.classList.add("control-box");
200
+ this.box.dataset.position = lbl_position;
201
+ this.fieldLabel.textContent = lbl ?? "";
202
+ this.labelBox.hidden = lbl == null || lbl === "";
203
+ if (this.control.localName !== "input") {
204
+ if (this.labelBox.hidden) this.control.removeAttribute("aria-labelledby");
205
+ else this.control.setAttribute("aria-labelledby", "field-label");
206
+ }
207
+ this.dispatchEvent(new this.ownerDocument.defaultView.Event("gramlot-label-layout", { bubbles: true }));
208
+ return rest;
209
+ }
210
+ };
211
+ }
212
+ Labelbox.parameters = {
213
+ lbl: { dtype: "T", qualification: "intl_str", nullable: true, binding: "read" },
214
+ lbl_position: { dtype: "T", default: "TL", binding: "read", enum: POSITIONS }
215
+ };
216
+
217
+ // node_modules/@gramlot/gramlot/src/components/parameters.js
218
+ function projectParameters(element, attrs) {
219
+ element.parameters = Object.fromEntries(Object.entries(element.constructor.gramlotComponent.parameters).map(([name, spec]) => [name, Object.hasOwn(attrs, name) ? attrs[name] : spec.default]));
220
+ return Object.fromEntries(Object.entries(attrs).filter(([name]) => !Object.hasOwn(element.parameters, name)));
221
+ }
222
+
223
+ // node_modules/@gramlot/gramlot/src/components/popup.js
224
+ var VIEWPORT_MARGIN = 8;
225
+ var ANCHOR_GAP = 3;
226
+ function positionPopup(popup, anchor, { align = "start", matchWidth = false } = {}) {
227
+ const view = anchor.ownerDocument.defaultView;
228
+ const field = anchor.getBoundingClientRect();
229
+ const availableWidth = Math.max(0, view.innerWidth - 2 * VIEWPORT_MARGIN);
230
+ popup.style.setProperty("--gramlot-popup-available-width", `${availableWidth}px`);
231
+ if (matchWidth) popup.style.width = `${Math.min(field.width, availableWidth)}px`;
232
+ popup.style.removeProperty("--gramlot-popup-available-height");
233
+ const natural = popup.getBoundingClientRect();
234
+ const below = Math.max(0, view.innerHeight - VIEWPORT_MARGIN - field.bottom - ANCHOR_GAP);
235
+ const above = Math.max(0, field.top - VIEWPORT_MARGIN - ANCHOR_GAP);
236
+ const upwards = natural.height > below && above > below;
237
+ popup.style.setProperty("--gramlot-popup-available-height", `${upwards ? above : below}px`);
238
+ const size = popup.getBoundingClientRect();
239
+ const left = align === "end" ? field.right - size.width : field.left;
240
+ const top = upwards ? field.top - size.height - ANCHOR_GAP : field.bottom + ANCHOR_GAP;
241
+ popup.style.left = `${Math.max(VIEWPORT_MARGIN, Math.min(left, view.innerWidth - size.width - VIEWPORT_MARGIN))}px`;
242
+ popup.style.top = `${Math.max(VIEWPORT_MARGIN, Math.min(top, view.innerHeight - size.height - VIEWPORT_MARGIN))}px`;
243
+ }
244
+
245
+ // node_modules/@gramlot/gramlot/src/components/formlet-labels.js
246
+ var FormletLabels = class {
247
+ #fields = /* @__PURE__ */ new Set();
248
+ #observer;
249
+ #frame;
250
+ constructor(formlet) {
251
+ this.formlet = formlet;
252
+ this.window = formlet.ownerDocument.defaultView;
253
+ this.slot = formlet.control.querySelector("slot");
254
+ }
255
+ connect() {
256
+ this.#observer = new this.window.ResizeObserver(this.schedule);
257
+ this.#observer.observe(this.formlet.control);
258
+ this.slot.addEventListener("slotchange", this.schedule);
259
+ this.formlet.addEventListener("gramlot-label-layout", this.schedule);
260
+ this.formlet.ownerDocument.fonts.addEventListener("loadingdone", this.schedule);
261
+ this.schedule();
262
+ }
263
+ disconnect() {
264
+ this.#observer.disconnect();
265
+ this.window.cancelAnimationFrame(this.#frame);
266
+ this.#frame = void 0;
267
+ this.slot.removeEventListener("slotchange", this.schedule);
268
+ this.formlet.removeEventListener("gramlot-label-layout", this.schedule);
269
+ this.formlet.ownerDocument.fonts.removeEventListener("loadingdone", this.schedule);
270
+ for (const field of this.#fields) this.#clear(field);
271
+ this.#fields.clear();
272
+ }
273
+ schedule = () => {
274
+ if (!this.formlet.isConnected || this.#frame !== void 0) return;
275
+ this.#frame = this.window.requestAnimationFrame(() => {
276
+ this.#frame = void 0;
277
+ this.#align();
278
+ });
279
+ };
280
+ #clear(field) {
281
+ delete field.box.dataset.formletAligned;
282
+ field.box.style.removeProperty("--gramlot-formlet-label-width");
283
+ }
284
+ #align() {
285
+ const fields = new Set(this.slot.assignedElements().filter((field) => "fieldLabel" in field && field.box.dataset.position === "L"));
286
+ for (const field of this.#fields) if (!fields.has(field)) {
287
+ this.#observer.unobserve(field.fieldLabel);
288
+ this.#clear(field);
289
+ }
290
+ for (const field of fields) {
291
+ field.box.dataset.formletAligned = "";
292
+ if (!this.#fields.has(field)) this.#observer.observe(field.fieldLabel);
293
+ }
294
+ this.#fields = fields;
295
+ const columns = /* @__PURE__ */ new Map();
296
+ for (const field of fields) {
297
+ if (!field.getClientRects().length) continue;
298
+ const host = field.getBoundingClientRect();
299
+ const label = field.labelBox.getBoundingClientRect();
300
+ const style = this.window.getComputedStyle(field.labelBox);
301
+ const boxStyle = this.window.getComputedStyle(field.box);
302
+ const gap = parseFloat(boxStyle.columnGap) || 0;
303
+ const labelLeft = field.labelBox.hidden ? field.box.getBoundingClientRect().left + parseFloat(boxStyle.paddingLeft) + parseFloat(boxStyle.borderLeftWidth) : label.left;
304
+ const inset = labelLeft - host.left + gap;
305
+ const width = field.box.style.getPropertyValue("--gramlot-label-width") ? label.width : field.fieldLabel.getBoundingClientRect().width + parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
306
+ const column = columns.get(host.left) ?? [];
307
+ column.push({ field, inset, width });
308
+ columns.set(host.left, column);
309
+ }
310
+ for (const column of columns.values()) {
311
+ const edge = Math.max(...column.map(({ inset, width }) => inset + width));
312
+ for (const { field, inset } of column) {
313
+ field.box.style.setProperty("--gramlot-formlet-label-width", `${edge - inset}px`);
314
+ }
315
+ }
316
+ }
317
+ };
318
+
319
+ // node_modules/@gramlot/gramlot/src/components/layout.js
320
+ var COPY_FEEDBACK_DURATION = 1600;
321
+ function Container(HTMLElement) {
322
+ return class extends HTMLElement {
323
+ constructor() {
324
+ super();
325
+ const shadow = this.attachShadow({ mode: "open" });
326
+ const style = this.ownerDocument.createElement("style");
327
+ style.textContent = `:host{display:block;min-width:0;font:inherit;color:var(--gramlot-ink,CanvasText)}
328
+ *,*::before,*::after{box-sizing:border-box}.control-box{min-width:0}slot{display:contents}
329
+ ::slotted(*){min-width:0;max-width:100%}`;
330
+ this.controlBox = this.ownerDocument.createElement("div");
331
+ this.controlBox.className = "control-box";
332
+ this.control = this.controlBox;
333
+ this.control.id = "control";
334
+ this.control.append(this.ownerDocument.createElement("slot"));
335
+ shadow.append(style, this.controlBox);
336
+ }
337
+ projectAttributes(attrs) {
338
+ return projectParameters(this, attrs);
339
+ }
340
+ };
341
+ }
342
+ Container.parameters = {};
343
+ var Formlet = defineComponent({
344
+ name: "formlet",
345
+ tag: "gramlot-formlet",
346
+ base: Container,
347
+ subTags: "*",
348
+ presentationDefaults: ["lbl_", "box_"],
349
+ parameters: {
350
+ columns: { dtype: ["L", "T"], default: 1, binding: "read" },
351
+ col_min_width: { dtype: "T", nullable: true, binding: "read" }
352
+ },
353
+ implementation: (Base) => class extends Base {
354
+ #labels;
355
+ constructor() {
356
+ super();
357
+ const style = this.ownerDocument.createElement("style");
358
+ style.textContent = `.control-box{display:grid;gap:inherit;align-items:start}
359
+ :host{gap:var(--gramlot-formlet-gap,8px 16px)}`;
360
+ this.shadowRoot.append(style);
361
+ this.#labels = new FormletLabels(this);
362
+ }
363
+ connectedCallback() {
364
+ this.#labels.connect();
365
+ }
366
+ disconnectedCallback() {
367
+ this.#labels.disconnect();
368
+ }
369
+ projectAttributes(attrs, adapt) {
370
+ const rest = super.projectAttributes(attrs, adapt);
371
+ const { columns, col_min_width } = this.parameters;
372
+ if (typeof columns === "number" && (!Number.isInteger(columns) || columns < 1)) {
373
+ throw new TypeError("formlet columns must be a positive integer or CSS track list");
374
+ }
375
+ this.control.style.gridTemplateColumns = col_min_width ? `repeat(auto-fit, minmax(min(100%, ${col_min_width}), 1fr))` : typeof columns === "number" ? `repeat(${columns}, minmax(0, 1fr))` : columns;
376
+ this.#labels.schedule();
377
+ return Object.fromEntries(Object.entries(rest).filter(([name]) => !/^(lbl_|box_)/.test(name)));
378
+ }
379
+ }
380
+ });
381
+ var GroupBox = defineComponent({
382
+ name: "groupBox",
383
+ tag: "gramlot-group-box",
384
+ base: Container,
385
+ subTags: "*",
386
+ dataScope: true,
387
+ mixins: [Labelbox],
388
+ parameters: {
389
+ copy: { dtype: "B", default: false, binding: "read" },
390
+ lbl_variant: { dtype: "T", default: "bar", binding: "read", enum: ["bar", "underline"] }
391
+ },
392
+ implementation: (Base) => class extends Base {
393
+ #generation = 0;
394
+ #timer;
395
+ #busy = false;
396
+ #menuEvents;
397
+ #menuOpen = false;
398
+ constructor() {
399
+ super();
400
+ this.control.setAttribute("role", "group");
401
+ this.fieldLabel.removeAttribute("for");
402
+ const doc = this.ownerDocument;
403
+ const style = doc.createElement("style");
404
+ style.textContent = `.labelbox{gap:0;border:1px solid var(--gramlot-border-control,#aaa);border-radius:var(--gramlot-radius-sm,4px)}
405
+ .label-box{display:flex;align-items:center;gap:.4em;padding:var(--gramlot-group-header-padding,.35em .65em);background:var(--gramlot-surface-subtle,#eef1f5)}
406
+ .label-box[hidden]{display:none}.label-box label{flex:1;min-width:0}
407
+ .control-box{padding:var(--gramlot-group-padding,.65em)}.labelbox[data-variant=underline]{border:0}
408
+ [data-variant=underline]>.label-box{background:transparent;border-bottom:1px solid var(--gramlot-border-control,#aaa)}
409
+ .actions{display:flex;align-items:center}.actions[hidden]{display:none}
410
+ button{font:inherit;color:inherit;cursor:pointer;border:0;border-radius:var(--gramlot-radius-sm,4px);background:transparent;
411
+ padding:var(--gramlot-group-action-padding,.2em .4em);min-width:var(--gramlot-touch-target,var(--gramlot-group-action-size,24px));min-height:var(--gramlot-touch-target,var(--gramlot-group-action-size,24px))}
412
+ button:hover{background:var(--gramlot-field-readonly-background,#ddd)}button:disabled{opacity:.5}
413
+ button:focus-visible{outline:2px solid var(--gramlot-focus-ring,Highlight)}
414
+ .copy-menu{max-width:var(--gramlot-popup-available-width,100vw);position:fixed;inset:auto;margin:0;padding:var(--gramlot-popup-padding,4px);max-height:var(--gramlot-popup-available-height,100vh);overflow:auto;border:1px solid var(--gramlot-border-control,#aaa);
415
+ background:var(--gramlot-field-background,Canvas);color:var(--gramlot-ink,CanvasText);border-radius:var(--gramlot-radius-sm,4px)}
416
+ .copy-menu button{display:block;width:100%;text-align:left}
417
+ .status{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}`;
418
+ this.actions = doc.createElement("span");
419
+ this.actions.className = "actions";
420
+ this.copyButton = doc.createElement("button");
421
+ this.copyButton.type = "button";
422
+ this.copyButton.textContent = "\u29C9";
423
+ this.copyButton.title = "Copy TYTX";
424
+ this.copyButton.setAttribute("aria-label", "Copy TYTX");
425
+ this.menuButton = doc.createElement("button");
426
+ this.menuButton.type = "button";
427
+ this.menuButton.textContent = "\u25BE";
428
+ this.menuButton.setAttribute("aria-label", "Choose copy format");
429
+ this.menuButton.setAttribute("aria-expanded", "false");
430
+ this.copyMenu = doc.createElement("div");
431
+ this.copyMenu.className = "copy-menu";
432
+ this.copyMenu.id = "copy-menu";
433
+ this.copyMenu.setAttribute("popover", "auto");
434
+ this.menuButton.setAttribute("aria-controls", "copy-menu");
435
+ for (const format of ["TYTX", "JSON"]) {
436
+ const button = doc.createElement("button");
437
+ button.type = "button";
438
+ button.textContent = `Copy ${format}`;
439
+ button.addEventListener("click", () => {
440
+ this.closeCopyMenu();
441
+ this.copyData(format);
442
+ });
443
+ this.copyMenu.append(button);
444
+ }
445
+ this.status = doc.createElement("span");
446
+ this.status.className = "status";
447
+ this.status.setAttribute("role", "status");
448
+ this.copyButton.addEventListener("click", () => this.copyData("TYTX"));
449
+ this.menuButton.addEventListener("click", () => {
450
+ if (this.#menuOpen) {
451
+ this.closeCopyMenu();
452
+ return;
453
+ }
454
+ this.copyMenu.showPopover();
455
+ this.#menuOpen = true;
456
+ positionPopup(this.copyMenu, this.menuButton, { align: "end" });
457
+ const view = doc.defaultView;
458
+ this.#menuEvents = new view.AbortController();
459
+ const options = { capture: true, signal: this.#menuEvents.signal };
460
+ view.addEventListener("scroll", (event) => {
461
+ if (!event.composedPath().includes(this.copyMenu)) this.closeCopyMenu();
462
+ }, options);
463
+ view.addEventListener("resize", () => this.closeCopyMenu(), options);
464
+ this.copyMenu.firstElementChild.focus();
465
+ });
466
+ this.copyMenu.addEventListener("toggle", (event) => {
467
+ this.menuButton.setAttribute("aria-expanded", String(event.newState === "open"));
468
+ if (event.newState === "closed") {
469
+ this.#menuOpen = false;
470
+ this.#menuEvents?.abort();
471
+ this.#menuEvents = null;
472
+ }
473
+ });
474
+ this.actions.append(this.copyButton, this.menuButton, this.status);
475
+ this.labelBox.append(this.actions);
476
+ this.shadowRoot.append(style, this.copyMenu);
477
+ }
478
+ closeCopyMenu() {
479
+ if (this.#menuOpen) this.copyMenu.hidePopover();
480
+ this.#menuOpen = false;
481
+ this.#menuEvents?.abort();
482
+ this.#menuEvents = null;
483
+ this.menuButton.setAttribute("aria-expanded", "false");
484
+ }
485
+ projectAttributes(attrs, adapt) {
486
+ const rest = super.projectAttributes(attrs, adapt);
487
+ if (!this.constructor.gramlotComponent.parameters.lbl_variant.enum.includes(this.parameters.lbl_variant)) throw new TypeError("groupBox lbl_variant must be bar or underline");
488
+ this.box.dataset.variant = this.parameters.lbl_variant;
489
+ this.actions.hidden = !this.parameters.copy;
490
+ if (!this.parameters.copy) this.closeCopyMenu();
491
+ this.labelBox.hidden = this.labelBox.hidden && !this.parameters.copy;
492
+ this.copyButton.disabled = this.#busy;
493
+ this.menuButton.disabled = this.#busy;
494
+ return rest;
495
+ }
496
+ async copyData(format = "TYTX") {
497
+ if (!["TYTX", "JSON"].includes(format)) throw new TypeError("Copy format must be TYTX or JSON");
498
+ if (!this.parameters.copy || this.#busy || !this.isConnected) return;
499
+ const generation = ++this.#generation;
500
+ this.copyButton.title = "Copy TYTX";
501
+ this.copyButton.textContent = "\u29C9";
502
+ this.status.textContent = "";
503
+ this.#busy = true;
504
+ this.copyButton.disabled = this.menuButton.disabled = true;
505
+ this.ownerDocument.defaultView.clearTimeout(this.#timer);
506
+ try {
507
+ const bag = this.readDataScope();
508
+ const text = format === "TYTX" ? bag.toTytx() : JSON.stringify(bag.asDict(false, false, true), null, 2);
509
+ await this.ownerDocument.defaultView.navigator.clipboard.writeText(text);
510
+ if (generation !== this.#generation || !this.isConnected) return;
511
+ this.copyButton.textContent = "\u2713";
512
+ this.status.textContent = `Copied ${format}`;
513
+ this.#timer = this.ownerDocument.defaultView.setTimeout(() => {
514
+ this.copyButton.textContent = "\u29C9";
515
+ this.status.textContent = "";
516
+ }, COPY_FEEDBACK_DURATION);
517
+ } catch (error) {
518
+ if (generation !== this.#generation || !this.isConnected) return;
519
+ this.copyButton.textContent = "!";
520
+ this.copyButton.title = this.status.textContent = `Copy failed: ${error.message}`;
521
+ } finally {
522
+ if (generation === this.#generation) {
523
+ this.#busy = false;
524
+ this.copyButton.disabled = this.menuButton.disabled = false;
525
+ }
526
+ }
527
+ }
528
+ disconnectedCallback() {
529
+ this.closeCopyMenu();
530
+ this.#generation++;
531
+ this.#busy = false;
532
+ this.ownerDocument.defaultView.clearTimeout(this.#timer);
533
+ }
534
+ }
535
+ });
536
+ var layout_default = defineCollection({ name: "layout", components: [Formlet, GroupBox] });
537
+ export {
538
+ Formlet,
539
+ GroupBox,
540
+ layout_default as default
541
+ };
@@ -1,2 +1,2 @@
1
- // Generated from Gramlot Compact; edit the core theme, not this file.
2
- export const compactStyle = "/* 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";
1
+ // Generated from canonical Gramlot themes.
2
+ export const appearanceStyles = {"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"};