@gramlot/gramlot-examples 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +6 -3
  2. package/package.json +4 -3
  3. package/src/gramlot_examples/catalog.json +54 -0
  4. package/src/gramlot_examples/gallery/dist/frame.js +548 -36
  5. package/src/gramlot_examples/gallery/dist/gallery.js +2387 -16677
  6. package/src/gramlot_examples/gallery/dist/inputs.js +12 -6
  7. package/src/gramlot_examples/gallery/dist/inspector.js +547 -35
  8. package/src/gramlot_examples/gallery/dist/layout.js +847 -28
  9. package/src/gramlot_examples/gallery/dist/notices.json +0 -24
  10. package/src/gramlot_examples/gallery/dist/theme-styles.js +1 -1
  11. package/src/gramlot_examples/gallery/gallery-page.js +66 -45
  12. package/src/gramlot_examples/gallery/gallery.css +61 -38
  13. package/src/gramlot_examples/gallery/guided-demo.js +151 -0
  14. package/src/gramlot_examples/gallery/page.js +1 -1
  15. package/src/gramlot_examples/index.js +1 -0
  16. package/src/gramlot_examples/pages/layout/01_border.js +80 -0
  17. package/src/gramlot_examples/pages/layout/01_border.md +23 -0
  18. package/src/gramlot_examples/pages/layout/02_nested.js +99 -0
  19. package/src/gramlot_examples/pages/layout/02_nested.md +23 -0
  20. package/src/gramlot_examples/pages/layout/03_rounded.js +111 -0
  21. package/src/gramlot_examples/pages/layout/03_rounded.md +25 -0
  22. package/src/gramlot_examples/pages/layout/04_drawer_handles.js +140 -0
  23. package/src/gramlot_examples/pages/layout/04_drawer_handles.md +33 -0
  24. package/src/gramlot_examples/pages/layout/05_nested_lab.js +251 -0
  25. package/src/gramlot_examples/pages/layout/05_nested_lab.md +25 -0
  26. package/src/gramlot_examples/pages/layout/06_border_events.js +118 -0
  27. package/src/gramlot_examples/pages/layout/06_border_events.md +25 -0
  28. package/src/gramlot_examples/pages/layout/07_tabs.js +300 -0
  29. package/src/gramlot_examples/pages/layout/07_tabs.md +37 -0
  30. package/src/gramlot_examples/pages/layout/08_stack.js +142 -0
  31. package/src/gramlot_examples/pages/layout/08_stack.md +27 -0
  32. package/src/gramlot_examples/pages/layout/README.md +15 -0
@@ -34,7 +34,7 @@ function annotation(parameter) {
34
34
  });
35
35
  return alternatives.length === 1 ? alternatives[0] : { kind: "union", items: alternatives };
36
36
  }
37
- function defineComponent({ name, tag, base, mixins = [], parameters = {}, subTags = "", presentationDefaults = [], dataScope = false, command = false, implementation }) {
37
+ function defineComponent({ name, tag, base, mixins = [], parameters = {}, subTags = "", presentationDefaults = [], dataScope = false, command = false, childParameters = {}, sourceContext = false, implementation }) {
38
38
  webcomponentCode({ code: tag });
39
39
  if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) throw new TypeError("Invalid component authoring name");
40
40
  const effective = { ...base.parameters };
@@ -56,6 +56,8 @@ function defineComponent({ name, tag, base, mixins = [], parameters = {}, subTag
56
56
  command,
57
57
  mixins: Object.freeze([...mixins]),
58
58
  parameters: freeze(structuredClone(effective)),
59
+ childParameters: freeze(structuredClone(childParameters)),
60
+ sourceContext,
59
61
  implementation
60
62
  });
61
63
  }
@@ -112,7 +114,9 @@ var ComponentCollection = class {
112
114
  parameters: structuredClone(component.parameters),
113
115
  presentationDefaults: [...component.presentationDefaults],
114
116
  dataScope: component.dataScope,
115
- command: component.command
117
+ command: component.command,
118
+ ...Object.keys(component.childParameters).length ? { childParameters: structuredClone(component.childParameters) } : {},
119
+ ...component.sourceContext ? { sourceContext: true } : {}
116
120
  } }
117
121
  }]))
118
122
  };
@@ -220,15 +224,848 @@ function projectParameters(element, attrs) {
220
224
  return Object.fromEntries(Object.entries(attrs).filter(([name]) => !Object.hasOwn(element.parameters, name)));
221
225
  }
222
226
 
227
+ // node_modules/@gramlot/gramlot/src/components/container.js
228
+ function Container(HTMLElement) {
229
+ return class extends HTMLElement {
230
+ constructor() {
231
+ super();
232
+ const shadow = this.attachShadow({ mode: "open" });
233
+ const style = this.ownerDocument.createElement("style");
234
+ style.textContent = `:host{display:block;min-width:0;font:inherit;color:var(--gramlot-ink,CanvasText)}
235
+ *,*::before,*::after{box-sizing:border-box}.control-box{min-width:0}slot{display:contents}
236
+ ::slotted(*){min-width:0;max-width:100%}`;
237
+ this.controlBox = this.ownerDocument.createElement("div");
238
+ this.controlBox.className = "control-box";
239
+ this.control = this.controlBox;
240
+ this.control.id = "control";
241
+ this.control.append(this.ownerDocument.createElement("slot"));
242
+ shadow.append(style, this.controlBox);
243
+ }
244
+ projectAttributes(attrs) {
245
+ return projectParameters(this, attrs);
246
+ }
247
+ };
248
+ }
249
+ Container.parameters = {};
250
+
251
+ // node_modules/@gramlot/gramlot/src/components/border-container.js
252
+ var SIDES = ["top", "bottom", "left", "right"];
253
+ var REGIONS = [...SIDES, "center"];
254
+ var borderRegions = /* @__PURE__ */ new WeakMap();
255
+ var horizontal = (side) => side === "top" || side === "bottom";
256
+ function projectBorderChild(element, attrs, { adapt, invoke, publish, publishParent }) {
257
+ const rest = { ...attrs }, splitter = {}, drawer = {};
258
+ for (const [name, value] of Object.entries(rest)) {
259
+ for (const [prefix, group] of [["splitter_", splitter], ["drawer_", drawer]]) {
260
+ if (name.startsWith(prefix)) {
261
+ group[name.slice(prefix.length)] = value;
262
+ delete rest[name];
263
+ }
264
+ }
265
+ }
266
+ const region = rest.region;
267
+ if (!REGIONS.includes(region)) throw new TypeError(`Invalid border region: ${region}`);
268
+ const min = rest.minSize ?? 0, max = rest.maxSize ?? Infinity;
269
+ if (typeof min !== "number" || typeof max !== "number" || !Number.isFinite(min) || min < 0 || max < min || Number.isNaN(max)) {
270
+ throw new TypeError("Border minSize/maxSize must be nonnegative pixel limits, with maxSize >= minSize");
271
+ }
272
+ if (![void 0, null, false, true, "close"].includes(rest.drawer)) throw new TypeError("drawer must be true, false or close");
273
+ const probe = element.ownerDocument.createElement("div");
274
+ probe.style.cssText = rest.style ?? "";
275
+ const size = probe.style[horizontal(region) ? "height" : "width"];
276
+ probe.style.width = "100%";
277
+ probe.style.height = "100%";
278
+ rest.style = probe.style.cssText;
279
+ const { label, icon, minimize = true, onclick, ...drawerStyle } = drawer;
280
+ if (typeof minimize !== "boolean") throw new TypeError("drawer_minimize must be a boolean");
281
+ const drawerAttrs = adapt(drawerStyle);
282
+ const drawerProbe = element.ownerDocument.createElement("div");
283
+ drawerProbe.style.cssText = drawerAttrs.style ?? "";
284
+ const background = drawerProbe.style.backgroundColor;
285
+ if (background) drawerProbe.style.setProperty("--gramlot-drawer-background", background);
286
+ if (drawerAttrs.style != null) drawerAttrs.style = drawerProbe.style.cssText;
287
+ borderRegions.set(element, {
288
+ region,
289
+ size,
290
+ min,
291
+ max,
292
+ splitter: rest.splitter === true,
293
+ drawer: rest.drawer,
294
+ hidden: rest.hidden === true,
295
+ splitterAttrs: adapt(splitter),
296
+ drawerAttrs,
297
+ label,
298
+ icon,
299
+ minimize,
300
+ notifications: {
301
+ closableChange: (payload) => publish("closable_change", payload),
302
+ splitterResize: (payload) => publishParent("splitter_resize", payload)
303
+ },
304
+ onToggle: onclick ? (evt, show) => invoke("drawer_onclick", onclick, { evt, show }) : null
305
+ });
306
+ for (const key of ["region", "splitter", "drawer", "minSize", "maxSize"]) delete rest[key];
307
+ rest.slot = region;
308
+ if (element.parentElement?.constructor.gramlotComponent?.name === "borderContainer") element.parentElement.projectRegion(element);
309
+ return rest;
310
+ }
311
+ function applyAttributes(element, attrs, previous) {
312
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(attrs)])) {
313
+ if (attrs[key] == null) element.removeAttribute(key);
314
+ else element.setAttribute(key, String(attrs[key]));
315
+ }
316
+ return attrs;
317
+ }
318
+ var ContentPane = defineComponent({
319
+ name: "contentPane",
320
+ tag: "gramlot-content-pane",
321
+ base: Container,
322
+ subTags: "*",
323
+ implementation: (Base) => class extends Base {
324
+ constructor() {
325
+ super();
326
+ const style = this.ownerDocument.createElement("style");
327
+ style.textContent = ":host{min-height:0;overflow:auto}.control-box{height:100%;min-height:0}";
328
+ this.shadowRoot.append(style);
329
+ }
330
+ }
331
+ });
332
+ var BorderContainer = defineComponent({
333
+ name: "borderContainer",
334
+ tag: "gramlot-border-container",
335
+ base: Container,
336
+ subTags: "*",
337
+ childParameters: { region: { required: true, enum: REGIONS, unique: true }, drawer_onclick: { inline: ["evt", "show"] } },
338
+ parameters: {
339
+ design: { dtype: "T", default: "headline", binding: "read", enum: ["headline", "sidebar"] },
340
+ persist: { dtype: "B", default: false, binding: "read" }
341
+ },
342
+ implementation: (Base) => class extends Base {
343
+ static projectChild(element, attrs, context) {
344
+ return projectBorderChild(element, attrs, context);
345
+ }
346
+ #regions = /* @__PURE__ */ new Map();
347
+ #observer;
348
+ #contentObserver;
349
+ #drag;
350
+ constructor() {
351
+ super();
352
+ this.control.replaceChildren();
353
+ const doc = this.ownerDocument;
354
+ this.#contentObserver = new doc.defaultView.ResizeObserver((entries) => {
355
+ for (const { target } of entries) {
356
+ const region = target.closest(".region");
357
+ region.style.setProperty("--drawer-content-width", `${target.offsetWidth}px`);
358
+ region.style.setProperty("--drawer-content-height", `${target.offsetHeight}px`);
359
+ }
360
+ });
361
+ const style = doc.createElement("style");
362
+ style.textContent = `:host{min-height:0;overflow:hidden}
363
+ .control-box{height:100%;min-height:0;display:grid;
364
+ grid-template-columns:var(--left,0px) minmax(0,1fr) var(--right,0px);
365
+ grid-template-rows:var(--top,0px) minmax(0,1fr) var(--bottom,0px);
366
+ grid-template-areas:'top top top' 'left center right' 'bottom bottom bottom'}
367
+ .control-box[data-design=sidebar]{grid-template-areas:'left top right' 'left center right' 'left bottom right'}
368
+ .region{position:relative;min-width:0;min-height:0;overflow:visible}
369
+ .viewport{height:100%;min-width:0;min-height:0;overflow:auto}
370
+ .region[data-side=left]>.viewport{padding-right:var(--region-splitter,0px)}
371
+ .region[data-side=right]>.viewport{padding-left:var(--region-splitter,0px)}
372
+ .region[data-side=top]>.viewport{padding-bottom:var(--region-splitter,0px)}
373
+ .region[data-side=bottom]>.viewport{padding-top:var(--region-splitter,0px)}
374
+ .viewport[hidden]{display:none}::slotted(*){min-height:0}
375
+ .separator{position:absolute;z-index:2;touch-action:none;user-select:none;background:var(--gramlot-splitter-background,var(--gramlot-border-control,#aaa))}
376
+ .separator[data-axis=x]{top:0;bottom:0;width:var(--gramlot-splitter-size,1px);cursor:col-resize}
377
+ .separator[data-axis=y]{left:0;right:0;height:var(--gramlot-splitter-size,1px);cursor:row-resize}
378
+ .separator[data-side=left]{right:0}.separator[data-side=right]{left:0}
379
+ .separator[data-side=top]{bottom:0}.separator[data-side=bottom]{top:0}
380
+ .separator:hover{background:var(--gramlot-splitter-hover,#91a4bd)}
381
+ .separator::before{content:'';position:absolute;inset:0}
382
+ .separator[data-axis=x]::before{left:calc((100% - max(100%,var(--gramlot-touch-target,12px))) / 2);width:max(100%,var(--gramlot-touch-target,12px))}
383
+ .separator[data-axis=y]::before{top:calc((100% - max(100%,var(--gramlot-touch-target,12px))) / 2);height:max(100%,var(--gramlot-touch-target,12px))}
384
+ .separator[data-draggable=false]{cursor:default;background:transparent}
385
+ .separator[data-draggable=false]::before{display:none}
386
+ .separator[data-axis=x][data-draggable=false]{width:0!important}
387
+ .separator[data-axis=y][data-draggable=false]{height:0!important}
388
+ .separator:focus-visible{outline:2px solid var(--gramlot-focus-ring,Highlight);outline-offset:1px}
389
+ .drawer{position:absolute;z-index:3;cursor:pointer;appearance:none;font:inherit;
390
+ font-size:var(--gramlot-drawer-font-size,11px);line-height:1.2;white-space:nowrap;
391
+ --drawer-background:var(--gramlot-drawer-background,#777);
392
+ color:var(--gramlot-drawer-color,hsl(from var(--drawer-background) 0 0 clamp(20,calc((60 - l) * 1000),100)));
393
+ background:var(--drawer-background);outline:none;
394
+ border:0;border-radius:0;box-shadow:none;
395
+ min-width:0;min-height:0;padding:0;
396
+ opacity:var(--gramlot-drawer-opacity,.45);
397
+ transition:opacity var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out),
398
+ transform var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out),
399
+ width var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out),
400
+ height var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out),
401
+ max-width var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out),
402
+ max-height var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out),
403
+ margin var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out)}
404
+ .drawer:hover,.drawer:focus-visible{opacity:var(--gramlot-drawer-active-opacity,1)}
405
+ [data-axis=x]>.drawer[data-resizable]{cursor:col-resize}
406
+ [data-axis=y]>.drawer[data-resizable]{cursor:row-resize}
407
+ @media (hover:hover){
408
+ [data-axis=x]>.drawer:hover{transform:scale(var(--gramlot-drawer-hover-cross-scale,1.75),var(--gramlot-drawer-hover-length-scale,1.12))}
409
+ [data-axis=y]>.drawer:hover{transform:scale(var(--gramlot-drawer-hover-length-scale,1.12),var(--gramlot-drawer-hover-cross-scale,1.75))}
410
+ .separator>.drawer:is([data-labelled],[data-icon]):hover{transform:scale(var(--gramlot-drawer-label-hover-scale,1.04))}
411
+ }
412
+
413
+ [data-axis=x]>.drawer{--drawer-closed-width:var(--gramlot-drawer-thickness,4px);--drawer-closed-height:var(--gramlot-drawer-length,28px);
414
+ top:50%;margin-top:calc(var(--gramlot-drawer-length,28px) / -2);width:var(--drawer-closed-width);height:var(--drawer-closed-height)}
415
+ [data-axis=y]>.drawer{--drawer-closed-width:var(--gramlot-drawer-length,28px);--drawer-closed-height:var(--gramlot-drawer-thickness,4px);
416
+ left:50%;margin-left:calc(var(--gramlot-drawer-length,28px) / -2);width:var(--drawer-closed-width);height:var(--drawer-closed-height)}
417
+ .drawer::after{content:'';position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
418
+ width:max(100%,var(--gramlot-touch-target,var(--gramlot-drawer-hit-target,12px)));height:max(100%,var(--gramlot-touch-target,var(--gramlot-drawer-hit-target,12px)))}
419
+ .drawer{--drawer-reveal-width:var(--gramlot-drawer-reveal-width,var(--drawer-content-width,0px));--drawer-reveal-height:var(--gramlot-drawer-reveal-height,var(--drawer-content-height,0px))}
420
+ .drawer[data-labelled]{opacity:var(--gramlot-drawer-active-opacity,1)}
421
+ [data-side=left]>.drawer{transform-origin:left center;left:100%;border-left:0;border-radius:0 var(--gramlot-radius-sm,4px) var(--gramlot-radius-sm,4px) 0}
422
+ [data-side=right]>.drawer{transform-origin:right center;right:100%;border-right:0;border-radius:var(--gramlot-radius-sm,4px) 0 0 var(--gramlot-radius-sm,4px)}
423
+ [data-side=top]>.drawer{transform-origin:center top;top:100%;border-top:0;border-radius:0 0 var(--gramlot-radius-sm,4px) var(--gramlot-radius-sm,4px)}
424
+ [data-side=bottom]>.drawer{transform-origin:center bottom;bottom:100%;border-bottom:0;border-radius:var(--gramlot-radius-sm,4px) var(--gramlot-radius-sm,4px) 0 0}
425
+ .drawer:is([data-labelled],[data-icon]){width:max-content;height:max-content;padding:0}
426
+ .drawer-viewport{display:block;overflow:hidden;min-width:0;min-height:0;max-width:inherit;max-height:inherit}
427
+ .drawer-content{display:flex;width:max-content;height:max-content;align-items:center;gap:var(--gramlot-drawer-content-gap,4px);box-sizing:border-box;
428
+ padding:var(--gramlot-drawer-padding,2px 8px);overflow:hidden;pointer-events:none;
429
+ min-width:0;min-height:0}
430
+ .drawer-icon{display:block;flex:none;font-size:var(--gramlot-drawer-icon-size,1em);line-height:1}
431
+ .drawer-icon[hidden],.drawer-label:empty{display:none}
432
+ .drawer-label{display:block;flex:none;pointer-events:none}
433
+ .drawer-rotated .drawer-content{flex-direction:column;padding:var(--gramlot-drawer-padding,8px 2px)}
434
+ .drawer-rotated .drawer-label{writing-mode:vertical-rl;text-orientation:mixed}
435
+ .separator[data-axis=x]>.drawer.drawer-rotated{margin-top:0;translate:0 -50%}
436
+ /* Icon and caption reveal as one unit; the resting grip stays thin. */
437
+ .separator>.drawer.drawer-reveal:is([data-labelled],[data-icon]){--drawer-open:var(--gramlot-drawer-reveal,0);
438
+ opacity:calc(var(--gramlot-drawer-opacity,.45) + (var(--gramlot-drawer-active-opacity,1) - var(--gramlot-drawer-opacity,.45)) * var(--drawer-open));
439
+ display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);
440
+ min-width:0;min-height:0;
441
+ max-width:calc(var(--drawer-closed-width) + (var(--drawer-reveal-width) - var(--drawer-closed-width)) * var(--drawer-open));
442
+ max-height:calc(var(--drawer-closed-height) + (var(--drawer-reveal-height) - var(--drawer-closed-height)) * var(--drawer-open))}
443
+ .separator[data-axis=x]>.drawer.drawer-reveal{margin-top:0;translate:0 -50%}
444
+ [data-axis=y]>.drawer:is([data-labelled],[data-icon]){margin-left:0;translate:-50% 0}
445
+ .drawer-reveal .drawer-content{opacity:var(--drawer-open);
446
+ transition:opacity var(--gramlot-drawer-duration,.16s) var(--gramlot-drawer-easing,ease-out)}
447
+ @media (hover:none),(pointer:coarse){.separator>.drawer.drawer-reveal:is([data-labelled],[data-icon]){--drawer-open:1}}
448
+ @media (hover:hover){.separator>.drawer.drawer-reveal:is([data-labelled],[data-icon]):hover{--drawer-open:1;transform:scale(var(--gramlot-drawer-label-hover-scale,1.04))}}
449
+ .separator>.drawer:is([data-labelled],[data-icon]):is(:active,[data-active]){transform:scale(var(--gramlot-drawer-label-hover-scale,1.04))}
450
+ .separator>.drawer.drawer-reveal:is([data-labelled],[data-icon]):is(:focus-visible,:active,[data-active]){--drawer-open:1;transform:scale(var(--gramlot-drawer-label-hover-scale,1.04))}
451
+ @media (prefers-reduced-motion:reduce){.drawer,.drawer-reveal .drawer-content{transition:none}}
452
+ .drawer[hidden],.separator[hidden]{display:none}
453
+ .drag-cover{position:absolute;inset:0;z-index:4;display:none}
454
+ :host{position:relative}.drag-cover[data-active]{display:block}`;
455
+ for (const side of REGIONS) {
456
+ const cell = doc.createElement("div");
457
+ cell.className = "region";
458
+ cell.style.gridArea = side;
459
+ const viewport = doc.createElement("div");
460
+ viewport.className = "viewport";
461
+ cell.dataset.side = side;
462
+ viewport.id = `region-${side}`;
463
+ const slot = doc.createElement("slot");
464
+ slot.name = side;
465
+ viewport.append(slot);
466
+ cell.append(viewport);
467
+ const state = { cell, viewport, child: null, config: null, visibility: null, closed: false, suppressDrawerClick: false, size: null, splitterAttrs: {}, drawerAttrs: {} };
468
+ if (side !== "center") {
469
+ const separator = doc.createElement("div");
470
+ separator.className = "separator";
471
+ separator.dataset.side = side;
472
+ separator.dataset.axis = horizontal(side) ? "y" : "x";
473
+ separator.setAttribute("role", "separator");
474
+ separator.setAttribute("aria-label", `Resize ${side} region`);
475
+ separator.setAttribute("aria-orientation", horizontal(side) ? "horizontal" : "vertical");
476
+ separator.setAttribute("aria-controls", viewport.id);
477
+ const drawer = doc.createElement("button");
478
+ drawer.type = "button";
479
+ drawer.className = "drawer";
480
+ drawer.setAttribute("aria-controls", viewport.id);
481
+ const drawerIcon = doc.createElement("span");
482
+ drawerIcon.className = "drawer-icon";
483
+ drawerIcon.setAttribute("aria-hidden", "true");
484
+ const drawerLabel = doc.createElement("span");
485
+ drawerLabel.className = "drawer-label";
486
+ const drawerContent = doc.createElement("span");
487
+ drawerContent.className = "drawer-content";
488
+ const drawerViewport = doc.createElement("span");
489
+ drawerViewport.className = "drawer-viewport";
490
+ drawerContent.append(drawerIcon, drawerLabel);
491
+ drawerViewport.append(drawerContent);
492
+ drawer.append(drawerViewport);
493
+ separator.append(drawer);
494
+ cell.append(separator);
495
+ drawer.addEventListener("pointerdown", (evt) => {
496
+ if (evt.button === 0) drawer.setAttribute("data-active", "");
497
+ });
498
+ for (const event of ["pointerup", "pointercancel", "lostpointercapture", "pointerleave"]) {
499
+ drawer.addEventListener(event, (evt) => {
500
+ if (event !== "pointerleave" || !drawer.hasPointerCapture(evt.pointerId)) drawer.removeAttribute("data-active");
501
+ });
502
+ }
503
+ Object.assign(state, { separator, drawer, drawerIcon, drawerLabel, drawerContent });
504
+ separator.addEventListener("pointerdown", (evt) => this.#startDrag(side, evt));
505
+ separator.addEventListener("keydown", (evt) => this.#keyResize(side, evt));
506
+ drawer.addEventListener("click", (evt) => {
507
+ evt.stopPropagation();
508
+ if (state.suppressDrawerClick && evt.detail !== 0) {
509
+ state.suppressDrawerClick = false;
510
+ return;
511
+ }
512
+ state.closed = !state.closed;
513
+ this.#render(side);
514
+ state.config.onToggle?.(evt, !state.closed);
515
+ });
516
+ }
517
+ this.#regions.set(side, state);
518
+ this.control.append(cell);
519
+ slot.addEventListener("slotchange", () => this.#sync());
520
+ }
521
+ this.cover = doc.createElement("div");
522
+ this.cover.className = "drag-cover";
523
+ this.shadowRoot.append(style, this.cover);
524
+ this.#observer = new doc.defaultView.MutationObserver(() => this.#sync());
525
+ }
526
+ connectedCallback() {
527
+ this.#observer.observe(this, { childList: true });
528
+ for (const state of this.#regions.values()) if (state.drawerContent) this.#contentObserver.observe(state.drawerContent);
529
+ this.#sync();
530
+ }
531
+ disconnectedCallback() {
532
+ this.#observer.disconnect();
533
+ this.#contentObserver.disconnect();
534
+ this.#stopDrag();
535
+ }
536
+ projectAttributes(attrs, adapt) {
537
+ const rest = super.projectAttributes(attrs, adapt);
538
+ if (!["headline", "sidebar"].includes(this.parameters.design)) throw new TypeError("Invalid border design");
539
+ if (Object.hasOwn(attrs, "liveSplitters")) throw new TypeError("Border splitters are always live; omit liveSplitters");
540
+ if (this.parameters.persist && !attrs.id) throw new TypeError("Persistent borderContainer requires a stable id");
541
+ this.control.dataset.design = this.parameters.design;
542
+ return rest;
543
+ }
544
+ #sync() {
545
+ const children = /* @__PURE__ */ new Map();
546
+ for (const child of this.children) {
547
+ const config = borderRegions.get(child);
548
+ if (!config) continue;
549
+ if (children.has(config.region)) throw new Error(`Duplicate border region: ${config.region}`);
550
+ children.set(config.region, child);
551
+ }
552
+ for (const [side, state] of this.#regions) {
553
+ const child = children.get(side) ?? null;
554
+ if (state.child !== child) {
555
+ if (this.#drag?.side === side) this.#stopDrag();
556
+ state.child = child;
557
+ state.config = null;
558
+ state.visibility = null;
559
+ state.size = null;
560
+ state.closed = false;
561
+ }
562
+ if (child) this.projectRegion(child);
563
+ else {
564
+ state.config = null;
565
+ this.#render(side);
566
+ }
567
+ }
568
+ }
569
+ projectRegion(child) {
570
+ const config = borderRegions.get(child), side = config.region, state = this.#regions.get(side);
571
+ if (state.child !== child) {
572
+ this.#sync();
573
+ return;
574
+ }
575
+ const before = state.config;
576
+ if (!before || before.size !== config.size) {
577
+ state.size = config.size || "auto";
578
+ if (!before && this.parameters.persist) {
579
+ const saved = this.ownerDocument.cookie.split("; ").find((item) => item.startsWith(`${this.#cookie(side)}=`));
580
+ if (saved) state.size = decodeURIComponent(saved.split("=").slice(1).join("="));
581
+ }
582
+ }
583
+ if (!before || before.drawer !== config.drawer) state.closed = config.drawer === "close";
584
+ state.config = config;
585
+ if (side !== "center") {
586
+ state.splitterAttrs = applyAttributes(state.separator, config.splitterAttrs, state.splitterAttrs);
587
+ state.drawerAttrs = applyAttributes(state.drawer, config.drawerAttrs, state.drawerAttrs);
588
+ state.separator.classList.add("separator");
589
+ state.drawer.classList.add("drawer");
590
+ state.drawer.classList.toggle("drawer-reveal", config.minimize);
591
+ state.drawer.classList.toggle("drawer-rotated", !horizontal(side));
592
+ state.drawerLabel.innerHTML = config.label ?? "";
593
+ state.drawerIcon.textContent = config.icon ?? "";
594
+ state.drawerIcon.hidden = !config.icon;
595
+ state.drawer.toggleAttribute("data-icon", !!config.icon);
596
+ state.drawer.toggleAttribute("data-labelled", !!config.label);
597
+ }
598
+ this.#render(side);
599
+ }
600
+ #render(side) {
601
+ const state = this.#regions.get(side), c = state.config;
602
+ const visible = !!c && !c.hidden && !state.closed;
603
+ const previous = state.visibility;
604
+ state.visibility = c ? { open: visible, drawer: !!c.drawer } : null;
605
+ state.viewport.hidden = !visible;
606
+ if (side === "center") return;
607
+ this.control.style.setProperty(`--${side}`, visible ? state.size : "0px");
608
+ state.separator.hidden = !c || c.hidden || !c.splitter && !c.drawer;
609
+ const draggable = visible && c.splitter;
610
+ state.cell.style.setProperty("--region-splitter", draggable ? state.separator.style[horizontal(side) ? "height" : "width"] || "var(--gramlot-splitter-size,1px)" : "0px");
611
+ state.separator.dataset.draggable = String(!!draggable);
612
+ state.separator.setAttribute("role", draggable ? "separator" : "presentation");
613
+ state.separator.tabIndex = draggable ? 0 : -1;
614
+ state.drawer.hidden = !c?.drawer;
615
+ state.drawer.toggleAttribute("data-resizable", !!c?.splitter);
616
+ state.drawer.setAttribute("aria-expanded", String(visible));
617
+ state.drawer.setAttribute("aria-label", `${visible ? "Close" : "Open"} ${side} region`);
618
+ state.drawer.title = c?.drawerAttrs.title ?? state.drawer.getAttribute("aria-label");
619
+ state.separator.setAttribute("aria-valuemin", String(c?.min ?? 0));
620
+ state.separator.setAttribute("aria-valuenow", String(Math.round(this.#extent(side))));
621
+ if (c && Number.isFinite(c.max)) state.separator.setAttribute("aria-valuemax", String(c.max));
622
+ else state.separator.removeAttribute("aria-valuemax");
623
+ if (c && previous && (previous.drawer || c.drawer) && previous.open !== visible) {
624
+ c.notifications.closableChange({ open: visible, region: side });
625
+ }
626
+ }
627
+ #extent(side) {
628
+ const box = this.#regions.get(side).cell.getBoundingClientRect();
629
+ return horizontal(side) ? box.height : box.width;
630
+ }
631
+ #resize(side, desired) {
632
+ const state = this.#regions.get(side), c = state.config;
633
+ const opposite = { left: "right", right: "left", top: "bottom", bottom: "top" }[side];
634
+ const box = this.control.getBoundingClientRect();
635
+ const available = Math.max(0, (horizontal(side) ? box.height : box.width) - this.#extent(opposite));
636
+ const size = Math.max(Math.min(c.min, available), Math.min(desired, c.max, available));
637
+ state.size = `${size}px`;
638
+ this.#render(side);
639
+ }
640
+ #keyResize(side, evt) {
641
+ const state = this.#regions.get(side);
642
+ if (evt.target !== state.separator || !state.config?.splitter || state.closed) return;
643
+ const keys = horizontal(side) ? ["ArrowUp", "ArrowDown"] : ["ArrowLeft", "ArrowRight"];
644
+ if (!keys.includes(evt.key)) return;
645
+ evt.preventDefault();
646
+ const sign = ["left", "top"].includes(side) ? 1 : -1;
647
+ const previousSize = this.#extent(side);
648
+ this.#resize(side, previousSize + (evt.key === keys[0] ? -1 : 1) * sign);
649
+ this.#commitResize(side, previousSize);
650
+ }
651
+ #startDrag(side, evt) {
652
+ const state = this.#regions.get(side);
653
+ if (evt.button !== 0) return;
654
+ state.suppressDrawerClick = false;
655
+ if (!state.config?.splitter) return;
656
+ const fromDrawer = state.drawer.contains(evt.target);
657
+ if (!fromDrawer) evt.preventDefault();
658
+ this.#stopDrag();
659
+ const axis = horizontal(side) ? "clientY" : "clientX", start = evt[axis], size = this.#extent(side);
660
+ const sign = ["left", "top"].includes(side) ? 1 : -1;
661
+ const controller = new this.ownerDocument.defaultView.AbortController();
662
+ const captureTarget = fromDrawer ? state.drawer : state.separator, pointerId = evt.pointerId;
663
+ let resizing = !fromDrawer;
664
+ this.#drag = { side, controller, captureTarget, pointerId };
665
+ captureTarget.setPointerCapture(pointerId);
666
+ if (resizing) this.cover.setAttribute("data-active", "");
667
+ const options = { signal: controller.signal };
668
+ captureTarget.addEventListener("pointermove", (event) => {
669
+ if (event.pointerId !== pointerId) return;
670
+ const distance = event[axis] - start;
671
+ if (!resizing && Math.abs(distance) < 4) return;
672
+ state.suppressDrawerClick = fromDrawer;
673
+ if (state.closed && sign * distance <= 0) return;
674
+ const opening = state.closed;
675
+ state.closed = false;
676
+ resizing = true;
677
+ this.cover.setAttribute("data-active", "");
678
+ this.#resize(side, size + sign * distance);
679
+ if (opening) state.config.onToggle?.(event, true);
680
+ }, options);
681
+ const finish = (event) => {
682
+ if (event.pointerId !== pointerId) return;
683
+ this.#stopDrag();
684
+ if (resizing) this.#commitResize(side, size);
685
+ };
686
+ captureTarget.addEventListener("pointerup", finish, options);
687
+ captureTarget.addEventListener("pointercancel", finish, options);
688
+ captureTarget.addEventListener("lostpointercapture", finish, options);
689
+ }
690
+ #stopDrag() {
691
+ if (!this.#drag) return;
692
+ const { controller, captureTarget, pointerId } = this.#drag;
693
+ this.#drag = null;
694
+ controller.abort();
695
+ this.cover.removeAttribute("data-active");
696
+ if (captureTarget.hasPointerCapture(pointerId)) captureTarget.releasePointerCapture(pointerId);
697
+ }
698
+ #commitResize(side, previousSize) {
699
+ const state = this.#regions.get(side), size = this.#extent(side);
700
+ if (size === previousSize) return;
701
+ this.#save(side);
702
+ state.config.notifications.splitterResize({ region: side, size: `${size}px`, previousSize: `${previousSize}px` });
703
+ }
704
+ #cookie(side) {
705
+ return encodeURIComponent(`gramlot-border-${this.id}-${side}`);
706
+ }
707
+ #save(side) {
708
+ if (this.parameters.persist) this.ownerDocument.cookie = `${this.#cookie(side)}=${encodeURIComponent(this.#regions.get(side).size)}; Path=/; SameSite=Lax`;
709
+ }
710
+ }
711
+ });
712
+
713
+ // node_modules/@gramlot/gramlot/src/components/selected-container.js
714
+ var selectionPanels = /* @__PURE__ */ new WeakMap();
715
+ var panelSequence = 0;
716
+ function projectSelectionChild(element, attrs, actions) {
717
+ const { pageName = null, title = "", icon = "", badge = null, badge_color = "", closable = false, onClose, hidden = false, disabled = false, ...rest } = attrs;
718
+ if (pageName !== null && typeof pageName !== "string") throw new TypeError("pageName must be a string");
719
+ if (typeof closable !== "boolean") throw new TypeError("closable must be a boolean");
720
+ const slot = selectionPanels.get(element)?.slot ?? `panel-${++panelSequence}`;
721
+ selectionPanels.set(element, {
722
+ pageName,
723
+ title,
724
+ icon,
725
+ badge,
726
+ badge_color,
727
+ closable,
728
+ hidden,
729
+ disabled,
730
+ slot,
731
+ remove: actions.remove,
732
+ allowClose: () => onClose == null ? true : actions.invoke("onClose", onClose, {})
733
+ });
734
+ rest.slot = slot;
735
+ if (element.parentElement) element.parentElement.scheduleSelection();
736
+ return rest;
737
+ }
738
+ function SelectedContainer(HTMLElement) {
739
+ return class extends Container(HTMLElement) {
740
+ static projectChild(element, attrs, actions) {
741
+ return projectSelectionChild(element, attrs, actions);
742
+ }
743
+ #panels = /* @__PURE__ */ new Map();
744
+ #selected = null;
745
+ #initialized = false;
746
+ #scheduled = false;
747
+ #requested = { selectedPage: null, selected: null };
748
+ #observer;
749
+ #context;
750
+ constructor() {
751
+ super();
752
+ this.control.replaceChildren();
753
+ this.control.className = "control-box selection";
754
+ const style = this.ownerDocument.createElement("style");
755
+ style.textContent = `:host{min-height:0;overflow:hidden}.selection{height:100%;min-height:0;display:flex;flex-direction:column}
756
+ .panels{flex:1;min-height:0;min-width:0;position:relative}.panel{height:100%;min-height:0;overflow:auto}
757
+ .panel[hidden]{display:none}::slotted(*){height:100%;min-height:0}`;
758
+ this.panelArea = this.ownerDocument.createElement("div");
759
+ this.panelArea.className = "panels";
760
+ this.control.append(this.panelArea);
761
+ this.shadowRoot.append(style);
762
+ this.#observer = new this.ownerDocument.defaultView.MutationObserver(() => this.scheduleSelection());
763
+ }
764
+ setSourceContext(context) {
765
+ this.#context = context;
766
+ }
767
+ connectedCallback() {
768
+ this.#observer.observe(this, { childList: true });
769
+ this.scheduleSelection();
770
+ }
771
+ disconnectedCallback() {
772
+ this.#observer.disconnect();
773
+ }
774
+ scheduleSelection() {
775
+ if (this.#scheduled) return;
776
+ this.#scheduled = true;
777
+ queueMicrotask(() => {
778
+ this.#scheduled = false;
779
+ if (this.isConnected) this.#sync();
780
+ });
781
+ }
782
+ projectAttributes(attrs, adapt) {
783
+ const rest = super.projectAttributes(attrs, adapt);
784
+ const { selected, selectedPage } = this.parameters;
785
+ if (selected != null && (!Number.isInteger(selected) || selected < 0)) throw new TypeError("selected must be a nonnegative index or null");
786
+ if (selectedPage != null && typeof selectedPage !== "string") throw new TypeError("selectedPage must be a string or null");
787
+ this.scheduleSelection();
788
+ return rest;
789
+ }
790
+ #sync() {
791
+ const records = [], names = /* @__PURE__ */ new Set();
792
+ for (const child of this.children) {
793
+ const config = selectionPanels.get(child);
794
+ if (!config) continue;
795
+ if (config.pageName != null && names.has(config.pageName)) throw new Error(`Duplicate pageName: ${config.pageName}`);
796
+ names.add(config.pageName);
797
+ let record = this.#panels.get(child);
798
+ if (!record) {
799
+ const viewport = this.ownerDocument.createElement("div");
800
+ viewport.className = "panel";
801
+ viewport.hidden = true;
802
+ viewport.id = config.slot;
803
+ viewport.setAttribute("role", "group");
804
+ const slot = this.ownerDocument.createElement("slot");
805
+ slot.name = config.slot;
806
+ viewport.append(slot);
807
+ this.panelArea.append(viewport);
808
+ record = { child, viewport, config, index: records.length };
809
+ this.#panels.set(child, record);
810
+ }
811
+ record.config = config;
812
+ records.push(record);
813
+ }
814
+ const previousIndex = this.#selected?.index ?? 0;
815
+ for (const [child, record] of this.#panels) {
816
+ if (!records.includes(record)) {
817
+ record.viewport.remove();
818
+ this.#panels.delete(child);
819
+ }
820
+ }
821
+ records.forEach((record, index) => {
822
+ record.index = index;
823
+ });
824
+ this.#panels = new Map(records.map((record) => [record.child, record]));
825
+ const { selectedPage, selected } = this.parameters;
826
+ const nameChanged = !Object.is(selectedPage, this.#requested.selectedPage);
827
+ const indexChanged = !Object.is(selected, this.#requested.selected);
828
+ this.#requested = { selectedPage, selected };
829
+ const available = (record) => record && !record.config.hidden && !record.config.disabled;
830
+ let target = this.#selected;
831
+ if (!this.#initialized || nameChanged || indexChanged) {
832
+ const requested = nameChanged || !this.#initialized && selectedPage != null ? records.find((record) => record.config.pageName === selectedPage) : records[selected];
833
+ if (available(requested)) target = requested;
834
+ }
835
+ if (!records.includes(target) || !available(target)) {
836
+ target = records.slice(previousIndex).find(available) ?? records.slice(0, previousIndex).reverse().find(available) ?? null;
837
+ }
838
+ this.renderTabs(records, target);
839
+ if (records.length || this.#initialized) this.#select(target);
840
+ }
841
+ renderTabs() {
842
+ }
843
+ selectPanel(record) {
844
+ if (record.config.hidden || record.config.disabled) return;
845
+ this.#select(record);
846
+ }
847
+ #payload(record, selected) {
848
+ const page = record.config.pageName ?? record.index;
849
+ return { page, index: record.index, selected, change: `${page}_${selected ? "show" : "hide"}` };
850
+ }
851
+ #writeSelection(target) {
852
+ this.#context.writeParameter("selectedPage", target?.config.pageName ?? null);
853
+ this.#context.writeParameter("selected", target?.index ?? null);
854
+ }
855
+ #select(target) {
856
+ const previous = this.#selected, initial = !this.#initialized;
857
+ if (!initial && target === previous) {
858
+ this.#writeSelection(target);
859
+ return;
860
+ }
861
+ if (!initial) {
862
+ if (previous) this.#context.publish("hiding", { pageName: previous.config.pageName, index: previous.index });
863
+ if (target) this.#context.publish("showing", { pageName: target.config.pageName, index: target.index });
864
+ }
865
+ this.#selected = target;
866
+ this.#initialized = true;
867
+ for (const record of this.#panels.values()) record.viewport.hidden = record !== target;
868
+ this.renderTabs([...this.#panels.values()], target);
869
+ this.#writeSelection(target);
870
+ if (!initial && previous) this.#context.publish("selected", this.#payload(previous, false));
871
+ if (target) this.#context.publish("selected", this.#payload(target, true));
872
+ }
873
+ closePanel(record) {
874
+ if (!record.config.closable) return;
875
+ const allowed = record.config.allowClose();
876
+ if (typeof allowed !== "boolean") throw new TypeError("onClose must return a synchronous boolean");
877
+ if (!allowed) return;
878
+ const payload = { pageName: record.config.pageName, index: record.index, wasSelected: record === this.#selected };
879
+ this.#context.publish("closing", payload);
880
+ record.config.remove();
881
+ this.#sync();
882
+ this.#context.publish("closed", payload);
883
+ }
884
+ };
885
+ }
886
+ SelectedContainer.parameters = {
887
+ selectedPage: { dtype: "T", default: null, nullable: true, binding: "twoWay" },
888
+ selected: { dtype: "L", default: null, nullable: true, binding: "twoWay" }
889
+ };
890
+ var selectionChildParameters = { onClose: { inline: [] } };
891
+ var StackContainer = defineComponent({
892
+ name: "stackContainer",
893
+ tag: "gramlot-stack-container",
894
+ base: SelectedContainer,
895
+ subTags: "*",
896
+ sourceContext: true,
897
+ childParameters: selectionChildParameters,
898
+ implementation: (Base) => class extends Base {
899
+ }
900
+ });
901
+ var TabContainer = defineComponent({
902
+ name: "tabContainer",
903
+ tag: "gramlot-tab-container",
904
+ base: SelectedContainer,
905
+ subTags: "*",
906
+ sourceContext: true,
907
+ childParameters: selectionChildParameters,
908
+ parameters: {
909
+ tabMode: { dtype: "T", default: "tabs", binding: "read", enum: ["tabs", "underline"] },
910
+ tabPosition: { dtype: "T", default: "top", binding: "read", enum: ["top", "bottom", "left", "right"] },
911
+ tabAlign: { dtype: "T", default: "start", binding: "read", enum: ["start", "center", "end"] }
912
+ },
913
+ implementation: (Base) => class extends Base {
914
+ #tabs = /* @__PURE__ */ new Map();
915
+ constructor() {
916
+ super();
917
+ const doc = this.ownerDocument;
918
+ this.tabStrip = doc.createElement("div");
919
+ this.tabStrip.className = "tabs";
920
+ this.tabStrip.setAttribute("role", "tablist");
921
+ this.control.prepend(this.tabStrip);
922
+ const style = doc.createElement("style");
923
+ style.textContent = `
924
+ .selection{min-width:0;--tab-border:var(--gramlot-tab-border,var(--gramlot-border,#aaa));
925
+ --tab-radius:var(--gramlot-tab-radius,4px);--tab-indicator:var(--gramlot-tab-indicator-color,var(--gramlot-action,#3766b5))}
926
+ .panels{background:var(--gramlot-tab-selected-background,var(--gramlot-surface,white))}
927
+ .tabs{display:var(--gramlot-tab-strip-display,flex);flex:none;align-items:stretch;gap:var(--gramlot-tab-gap,3px);min-width:0;min-height:0;
928
+ padding:var(--gramlot-tab-strip-padding,4px);overflow:auto;overscroll-behavior:contain;
929
+ border-bottom:1px solid var(--tab-border);background:var(--gramlot-tab-background,var(--gramlot-surface-subtle,#eef1f5))}
930
+ [data-align=center] .tabs{justify-content:safe center}
931
+ [data-align=end] .tabs{justify-content:safe flex-end}
932
+ .tab-entry{position:relative;display:flex;flex:none;align-items:stretch;border:1px solid transparent;border-bottom:0;
933
+ border-radius:var(--tab-radius) var(--tab-radius) 0 0;color:var(--gramlot-tab-color,var(--gramlot-text-secondary,#555))}
934
+ .tab-entry:has([aria-selected=true]){border-color:var(--tab-border);color:var(--gramlot-tab-selected-color,var(--gramlot-ink,#222));
935
+ background:var(--gramlot-tab-selected-background,var(--gramlot-surface,white))}
936
+ button{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:var(--gramlot-tab-content-gap,.4em);
937
+ font:inherit;line-height:1.2;color:inherit;border:0;background:transparent;cursor:pointer;white-space:nowrap;min-width:0;
938
+ padding:var(--gramlot-tab-padding,4px 10px);min-height:max(var(--gramlot-touch-target,0px),var(--gramlot-tab-height,28px));border-radius:inherit}
939
+ button[aria-selected=true]{font-weight:600}
940
+ .icon,.badge{flex:none}.icon:empty{display:none}.caption{overflow:hidden;text-overflow:ellipsis}
941
+ .badge{font-size:var(--gramlot-tab-badge-size,.8em);font-weight:600;line-height:1.3;border-radius:999px;
942
+ min-width:1.3em;padding:var(--gramlot-tab-badge-padding,0 .35em);text-align:center;
943
+ background:var(--gramlot-tab-badge-background,var(--gramlot-brand-blue,#3766b5));color:var(--gramlot-tab-badge-color,white)}
944
+ @media(hover:hover){button:not(:disabled):hover{background:var(--gramlot-tab-hover-background,var(--gramlot-selected,#e8f1fc))}}
945
+ button:disabled{opacity:.5;cursor:default}
946
+ button:focus-visible{outline:2px solid var(--gramlot-focus-ring,Highlight);outline-offset:-3px}
947
+ .close{align-self:center;flex:none;min-height:var(--gramlot-touch-target,20px);min-width:var(--gramlot-touch-target,20px);
948
+ padding:0;margin-inline-end:3px;border-radius:var(--tab-radius);font-size:1.15em}
949
+ [hidden]{display:none!important}
950
+ [data-position=top] .tabs{padding-bottom:0}
951
+ [data-position=bottom]{flex-direction:column-reverse}
952
+ [data-position=bottom] .tabs{border-bottom:0;border-top:1px solid var(--tab-border);padding-top:0}
953
+ [data-position=bottom] .tab-entry{border-bottom:1px solid transparent;border-top:0;border-radius:0 0 var(--tab-radius) var(--tab-radius)}
954
+ [data-position=left]{flex-direction:row}[data-position=right]{flex-direction:row-reverse}
955
+ :is([data-position=left],[data-position=right]) .tabs{flex-direction:column;max-width:min(40%,var(--gramlot-tab-side-width,12rem));border-bottom:0}
956
+ :is([data-position=left],[data-position=right]) button[role=tab]{flex:1;justify-content:flex-start}
957
+ [data-position=left] .tabs{border-right:1px solid var(--tab-border);padding-right:0}
958
+ [data-position=right] .tabs{border-left:1px solid var(--tab-border);padding-left:0}
959
+ [data-position=left] .tab-entry{border-bottom:1px solid transparent;border-right:0;border-radius:var(--tab-radius) 0 0 var(--tab-radius)}
960
+ [data-position=right] .tab-entry{border-bottom:1px solid transparent;border-left:0;border-radius:0 var(--tab-radius) var(--tab-radius) 0}
961
+ .tab-entry:has([aria-selected=true]){border-color:var(--tab-border)}
962
+ [data-mode=underline] .tabs{background:transparent}
963
+ [data-mode=underline] .tab-entry{border:0;border-radius:0;background:transparent}
964
+ [data-mode=underline] .tab-entry:has([aria-selected=true])::after{content:'';position:absolute;pointer-events:none;
965
+ background:var(--tab-indicator);height:var(--gramlot-tab-indicator-size,2px);bottom:0;left:0;right:0}
966
+ [data-mode=underline][data-position=bottom] .tab-entry::after{top:0;bottom:auto}
967
+ [data-mode=underline]:is([data-position=left],[data-position=right]) .tab-entry::after{height:auto;width:var(--gramlot-tab-indicator-size,2px);top:0;bottom:0}
968
+ [data-mode=underline][data-position=left] .tab-entry::after{left:auto;right:0}
969
+ [data-mode=underline][data-position=right] .tab-entry::after{left:0;right:auto}`;
970
+ this.shadowRoot.append(style);
971
+ }
972
+ projectAttributes(attrs, adapt) {
973
+ const rest = super.projectAttributes(attrs, adapt);
974
+ const { tabMode, tabPosition, tabAlign } = this.parameters;
975
+ if (!["tabs", "underline"].includes(tabMode)) throw new TypeError("Invalid tabMode");
976
+ if (!["top", "bottom", "left", "right"].includes(tabPosition)) throw new TypeError("Invalid tabPosition");
977
+ if (!["start", "center", "end"].includes(tabAlign)) throw new TypeError("Invalid tabAlign");
978
+ this.control.dataset.mode = tabMode;
979
+ this.control.dataset.position = tabPosition;
980
+ this.control.dataset.align = tabAlign;
981
+ this.tabStrip.setAttribute("aria-orientation", ["left", "right"].includes(tabPosition) ? "vertical" : "horizontal");
982
+ return rest;
983
+ }
984
+ renderTabs(records, selected) {
985
+ for (const [record, entry] of this.#tabs) {
986
+ if (!records.includes(record)) {
987
+ entry.root.remove();
988
+ this.#tabs.delete(record);
989
+ }
990
+ }
991
+ for (const [index, record] of records.entries()) {
992
+ let entry = this.#tabs.get(record);
993
+ if (!entry) {
994
+ const doc = this.ownerDocument, root = doc.createElement("div"), button = doc.createElement("button"), close = doc.createElement("button");
995
+ root.className = "tab-entry";
996
+ button.type = close.type = "button";
997
+ button.setAttribute("role", "tab");
998
+ button.id = `tab-${record.config.slot}`;
999
+ button.setAttribute("aria-controls", record.viewport.id);
1000
+ record.viewport.setAttribute("role", "tabpanel");
1001
+ record.viewport.setAttribute("aria-labelledby", button.id);
1002
+ const icon = doc.createElement("span"), caption2 = doc.createElement("span"), badge = doc.createElement("span");
1003
+ icon.className = "icon";
1004
+ icon.setAttribute("aria-hidden", "true");
1005
+ caption2.className = "caption";
1006
+ badge.className = "badge";
1007
+ button.append(icon, caption2, badge);
1008
+ close.className = "close";
1009
+ close.textContent = "\xD7";
1010
+ button.addEventListener("click", () => this.selectPanel(record));
1011
+ close.addEventListener("click", () => {
1012
+ this.closePanel(record);
1013
+ if (!close.isConnected) this.tabStrip.querySelector("[aria-selected=true]")?.focus();
1014
+ });
1015
+ button.addEventListener("keydown", (event) => {
1016
+ if (event.key === "Delete") {
1017
+ event.preventDefault();
1018
+ this.closePanel(record);
1019
+ this.tabStrip.querySelector("[aria-selected=true]")?.focus();
1020
+ return;
1021
+ }
1022
+ const vertical = ["left", "right"].includes(this.parameters.tabPosition);
1023
+ const previous = vertical ? "ArrowUp" : "ArrowLeft", nextKey = vertical ? "ArrowDown" : "ArrowRight";
1024
+ if (![previous, nextKey, "Home", "End"].includes(event.key)) return;
1025
+ event.preventDefault();
1026
+ const available = [...this.#tabs].filter(([r]) => !r.config.hidden && !r.config.disabled).sort(([a], [b]) => a.index - b.index);
1027
+ let index2 = available.findIndex(([r]) => r === record);
1028
+ index2 = event.key === "Home" ? 0 : event.key === "End" ? available.length - 1 : (index2 + (event.key === nextKey ? 1 : -1) + available.length) % available.length;
1029
+ const [next, tab] = available[index2];
1030
+ this.selectPanel(next);
1031
+ tab.button.focus();
1032
+ tab.button.scrollIntoView({ block: "nearest", inline: "nearest" });
1033
+ });
1034
+ root.append(button, close);
1035
+ this.tabStrip.append(root);
1036
+ entry = { root, button, close, icon, caption: caption2, badge };
1037
+ this.#tabs.set(record, entry);
1038
+ }
1039
+ if (this.tabStrip.children[index] !== entry.root) this.tabStrip.insertBefore(entry.root, this.tabStrip.children[index] ?? null);
1040
+ entry.root.hidden = !!record.config.hidden;
1041
+ const caption = record.config.title || record.config.pageName || String(record.index + 1);
1042
+ entry.caption.textContent = caption;
1043
+ entry.button.title = caption;
1044
+ entry.icon.textContent = record.config.icon;
1045
+ entry.badge.textContent = record.config.badge ?? "";
1046
+ entry.badge.hidden = record.config.badge == null || record.config.badge === "";
1047
+ entry.badge.style.backgroundColor = record.config.badge_color;
1048
+ entry.button.setAttribute("aria-label", entry.badge.hidden ? caption : `${caption} ${record.config.badge}`);
1049
+ entry.button.disabled = !!record.config.disabled;
1050
+ entry.button.setAttribute("aria-selected", String(record === selected));
1051
+ entry.button.tabIndex = record === selected ? 0 : -1;
1052
+ entry.close.hidden = !record.config.closable;
1053
+ entry.close.disabled = !!record.config.disabled;
1054
+ entry.close.setAttribute("aria-label", `Close ${caption}`);
1055
+ }
1056
+ }
1057
+ }
1058
+ });
1059
+
223
1060
  // node_modules/@gramlot/gramlot/src/components/popup.js
224
1061
  var VIEWPORT_MARGIN = 8;
225
1062
  var ANCHOR_GAP = 3;
226
- function positionPopup(popup, anchor, { align = "start", matchWidth = false } = {}) {
1063
+ function positionPopup(popup, anchor, { align = "start", matchWidth = false, inset = 0 } = {}) {
227
1064
  const view = anchor.ownerDocument.defaultView;
228
1065
  const field = anchor.getBoundingClientRect();
229
1066
  const availableWidth = Math.max(0, view.innerWidth - 2 * VIEWPORT_MARGIN);
230
1067
  popup.style.setProperty("--gramlot-popup-available-width", `${availableWidth}px`);
231
- if (matchWidth) popup.style.width = `${Math.min(field.width, availableWidth)}px`;
1068
+ if (matchWidth) popup.style.width = `${Math.min(Math.max(0, field.width - 2 * inset), availableWidth)}px`;
232
1069
  popup.style.removeProperty("--gramlot-popup-available-height");
233
1070
  const natural = popup.getBoundingClientRect();
234
1071
  const below = Math.max(0, view.innerHeight - VIEWPORT_MARGIN - field.bottom - ANCHOR_GAP);
@@ -236,7 +1073,7 @@ function positionPopup(popup, anchor, { align = "start", matchWidth = false } =
236
1073
  const upwards = natural.height > below && above > below;
237
1074
  popup.style.setProperty("--gramlot-popup-available-height", `${upwards ? above : below}px`);
238
1075
  const size = popup.getBoundingClientRect();
239
- const left = align === "end" ? field.right - size.width : field.left;
1076
+ const left = align === "end" ? field.right - size.width - inset : field.left + inset;
240
1077
  const top = upwards ? field.top - size.height - ANCHOR_GAP : field.bottom + ANCHOR_GAP;
241
1078
  popup.style.left = `${Math.max(VIEWPORT_MARGIN, Math.min(left, view.innerWidth - size.width - VIEWPORT_MARGIN))}px`;
242
1079
  popup.style.top = `${Math.max(VIEWPORT_MARGIN, Math.min(top, view.innerHeight - size.height - VIEWPORT_MARGIN))}px`;
@@ -318,28 +1155,6 @@ var FormletLabels = class {
318
1155
 
319
1156
  // node_modules/@gramlot/gramlot/src/components/layout.js
320
1157
  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
1158
  var Formlet = defineComponent({
344
1159
  name: "formlet",
345
1160
  tag: "gramlot-formlet",
@@ -533,9 +1348,13 @@ var GroupBox = defineComponent({
533
1348
  }
534
1349
  }
535
1350
  });
536
- var layout_default = defineCollection({ name: "layout", components: [Formlet, GroupBox] });
1351
+ var layout_default = defineCollection({ name: "layout", components: [Formlet, GroupBox, ContentPane, BorderContainer, StackContainer, TabContainer] });
537
1352
  export {
1353
+ BorderContainer,
1354
+ ContentPane,
538
1355
  Formlet,
539
1356
  GroupBox,
1357
+ StackContainer,
1358
+ TabContainer,
540
1359
  layout_default as default
541
1360
  };