@getaster/elements 0.3.5 → 0.4.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 (93) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +13 -4
  3. package/bin/aster-theme-check.js +35 -4
  4. package/dist/base/element.d.ts +8 -1
  5. package/dist/base/element.js +7 -2
  6. package/dist/button/button.d.ts +17 -2
  7. package/dist/button/button.js +42 -8
  8. package/dist/composition/builtins.d.ts +623 -1
  9. package/dist/composition/builtins.js +232 -4
  10. package/dist/date/calendar.d.ts +3 -0
  11. package/dist/date/calendar.js +8 -2
  12. package/dist/date/date-field.d.ts +23 -3
  13. package/dist/date/date-field.js +77 -9
  14. package/dist/date/dates.d.ts +10 -2
  15. package/dist/date/dates.js +23 -3
  16. package/dist/dialog/config.d.ts +4 -0
  17. package/dist/dialog/config.js +8 -0
  18. package/dist/dialog/dialog.d.ts +34 -6
  19. package/dist/dialog/dialog.js +151 -14
  20. package/dist/field/bound-field.d.ts +17 -1
  21. package/dist/field/bound-field.js +68 -7
  22. package/dist/field/check.js +1 -0
  23. package/dist/field/field.js +1 -1
  24. package/dist/field/input.d.ts +1 -0
  25. package/dist/field/input.js +2 -2
  26. package/dist/field/number-field.js +2 -2
  27. package/dist/filter/filter-builder.d.ts +7 -1
  28. package/dist/filter/filter-builder.js +8 -1
  29. package/dist/form/controls.d.ts +40 -8
  30. package/dist/form/controls.js +58 -15
  31. package/dist/form/custom-field.d.ts +1 -0
  32. package/dist/form/custom-field.js +23 -0
  33. package/dist/form/features.d.ts +27 -0
  34. package/dist/form/features.js +52 -0
  35. package/dist/form/form.d.ts +67 -7
  36. package/dist/form/form.js +267 -24
  37. package/dist/form/policy.d.ts +29 -0
  38. package/dist/form/policy.js +16 -0
  39. package/dist/grid/appearance.d.ts +365 -0
  40. package/dist/grid/appearance.js +814 -0
  41. package/dist/grid/features.d.ts +38 -0
  42. package/dist/grid/features.js +78 -0
  43. package/dist/grid/grid.d.ts +216 -4
  44. package/dist/grid/grid.js +1810 -308
  45. package/dist/grid/policy.d.ts +58 -0
  46. package/dist/grid/policy.js +17 -0
  47. package/dist/grid/view.d.ts +32 -2
  48. package/dist/grid/view.js +89 -4
  49. package/dist/index.d.ts +14 -11
  50. package/dist/index.js +2 -1
  51. package/dist/menu/config.d.ts +7 -0
  52. package/dist/menu/config.js +44 -0
  53. package/dist/menu/menu.d.ts +23 -0
  54. package/dist/menu/menu.js +69 -17
  55. package/dist/picker/picker.js +8 -3
  56. package/dist/section/accordion.d.ts +26 -0
  57. package/dist/section/accordion.js +86 -8
  58. package/dist/section/policy.d.ts +8 -0
  59. package/dist/section/policy.js +17 -0
  60. package/dist/section/section.d.ts +30 -0
  61. package/dist/section/section.js +106 -3
  62. package/dist/select/choice-element.d.ts +12 -0
  63. package/dist/select/choice-element.js +36 -0
  64. package/dist/select/choice.d.ts +12 -2
  65. package/dist/select/choice.js +93 -28
  66. package/dist/select/select.d.ts +12 -0
  67. package/dist/select/select.js +37 -1
  68. package/dist/styles/contract.d.ts +14 -0
  69. package/dist/styles/contract.js +15 -0
  70. package/dist/switch/switch.d.ts +16 -0
  71. package/dist/switch/switch.js +56 -6
  72. package/dist/tree/navigator.d.ts +3 -0
  73. package/dist/tree/navigator.js +26 -2
  74. package/dist/window/chrome.d.ts +5 -1
  75. package/dist/window/chrome.js +4 -3
  76. package/dist/window/config.d.ts +71 -0
  77. package/dist/window/config.js +40 -0
  78. package/dist/window/window.d.ts +51 -13
  79. package/dist/window/window.js +233 -38
  80. package/dist/yaml.d.ts +19 -0
  81. package/dist/yaml.js +57 -0
  82. package/package.json +10 -4
  83. package/styles/aster-ui.css +181 -10
  84. package/styles/base.css +14 -0
  85. package/styles/contract.ts +16 -0
  86. package/styles/elements.css +167 -10
  87. package/styles/fonts/OFL-bricolage-grotesque.txt +93 -0
  88. package/styles/fonts/OFL-fraunces.txt +93 -0
  89. package/styles/fonts/bricolage-grotesque-latin-opsz-normal.woff2 +0 -0
  90. package/styles/fonts/fraunces-latin-full-normal.woff2 +0 -0
  91. package/styles/fonts.css +17 -0
  92. package/styles/theme-harbour.css +5 -0
  93. package/styles/theme-lamplight.css +5 -0
@@ -11,6 +11,20 @@
11
11
  @media (pointer: coarse) {
12
12
  :root:not([data-density]) { --aster-ctl: 38px; --aster-ctl-tight: 32px; --aster-pad-y: 12px; --aster-row-y: 9px; --aster-pad: 16px; --aster-gap: 18px; }
13
13
  }
14
+ /* Component geometry intents: defaults an identity or an app may override.
15
+ `auto` follows the content; lengths are floors a placement may raise. */
16
+ :root {
17
+ --aster-form-label-width: 9rem;
18
+ --aster-grid-header-height: calc(var(--aster-pad-y) * 2 + 16.8px);
19
+ --aster-grid-toolbar-height: auto;
20
+ --aster-grid-row-min-height: auto;
21
+ --aster-grid-tree-indent: 18px;
22
+ --aster-grid-summary-height: auto;
23
+ --aster-menu-cell-height: auto;
24
+ --aster-menu-icon-size: 14px;
25
+ --aster-window-header-height: calc(var(--aster-ctl) + var(--aster-pad) - 1px);
26
+ --aster-dialog-max-width: 100%;
27
+ }
14
28
 
15
29
  *, *::before, *::after { box-sizing: border-box; }
16
30
  html, body { margin: 0; height: 100%; }
@@ -161,6 +175,8 @@
161
175
  .aster-number:has(.aster-number-steps) { height:auto; min-height:48px; }
162
176
  .aster-number-steps button, .aster-split-tools button, .aster-breadcrumbs :is(a,button) { min-width:44px; min-height:44px; }
163
177
  .aster-accordion > .aster-accordion-item > aster-section > .aster-section-head button { min-height:44px; }
178
+ .aster-accordion-reorder button { width:44px; height:44px; }
179
+ .aster-section-head > .aster-section-close { width:44px; height:44px; }
164
180
  }
165
181
  @media (forced-colors:active) { .aster-number-steps button { border:1px solid ButtonText; } }
166
182
  aster-component { display: block; min-width: 0; }
@@ -274,6 +290,17 @@
274
290
  .aster-button[aria-busy="true"] .aster-label-swap { opacity: .75; }
275
291
  .aster-button[aria-disabled="true"] { opacity: .45; box-shadow: var(--aster-ring); transform: none; }
276
292
  .aster-button[data-variant="primary"][aria-disabled="true"] { background: var(--aster-text-3); }
293
+ .aster-button[data-tone="danger"] { color: var(--aster-negative); box-shadow: inset 0 0 0 .5px oklch(from var(--aster-negative) l c h / .55), var(--aster-e1); }
294
+ .aster-button[data-tone="danger"]:hover { background: oklch(from var(--aster-negative) l c h / .08); box-shadow: inset 0 0 0 .5px var(--aster-negative), var(--aster-e2); }
295
+ .aster-button[data-tone="danger"]:active { box-shadow: inset 1px 1px 2px var(--aster-press), inset 0 0 0 .5px var(--aster-negative); }
296
+ .aster-button[data-size="sm"] { height: var(--aster-ctl-tight); padding: 0 8px; font-size: 12.5px; }
297
+ .aster-button[data-size="lg"] { height: var(--aster-ctl); padding: 0 16px; font-size: 13.5px; }
298
+ @media (forced-colors: active) {
299
+ /* The button edge is a shadow; forced colours drop shadows, so draw it. */
300
+ .aster-button { border: 1px solid ButtonText; }
301
+ .aster-button[aria-disabled="true"], .aster-button:disabled { border-color: GrayText; color: GrayText; }
302
+ }
303
+ .aster-button:disabled { opacity: .45; box-shadow: var(--aster-ring); cursor: not-allowed; transform: none; }
277
304
  .aster-button svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.7; }
278
305
  .aster-label-swap { display: inline-grid; }
279
306
  .aster-label-swap > * { grid-area: 1 / 1; transition: opacity 120ms; }
@@ -312,6 +339,10 @@
312
339
  aster-switch:last-child { border-bottom: 0; }
313
340
  aster-switch .aster-error { padding-bottom: 8px; }
314
341
  .aster-switch-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: calc(var(--aster-row-y) + 2px) 0; user-select: none; }
342
+ .aster-switch-end { display: flex; align-items: center; gap: 10px; flex: none; }
343
+ .aster-switch-state { font-size: 12px; color: var(--aster-text-3); }
344
+ .aster-switch-row[data-disabled] { opacity: .55; }
345
+ .aster-switch-row[data-disabled], .aster-switch-row[data-readonly] { cursor: default; }
315
346
  .aster-switch {
316
347
  position: relative; width: 28px; height: 16px; flex: none; border-radius: 999px;
317
348
  background: var(--aster-sunk); box-shadow: var(--aster-well); transition: background var(--aster-t-micro);
@@ -372,10 +403,16 @@
372
403
  .aster-dialog[open]::backdrop { opacity: 1; }
373
404
  @starting-style { .aster-dialog[open]::backdrop { opacity: 0; } }
374
405
  .aster-dialog-panel {
375
- width: var(--dialog-width, 372px); max-width: 100%; border-radius: var(--aster-r-dialog); overflow: hidden;
406
+ width: var(--dialog-width, 372px); max-width: min(var(--aster-dialog-max-width), 100%); border-radius: var(--aster-r-dialog); overflow: hidden;
376
407
  background: var(--aster-surface); box-shadow: var(--aster-e3);
377
408
  view-transition-name: aster-dialog-panel; corner-shape: squircle;
378
409
  }
410
+ .aster-dialog-head { display: flex; align-items: center; gap: 8px; padding: 12px 10px 0 16px; }
411
+ .aster-dialog-title { flex: 1; min-width: 0; margin: 0; font-size: 15px; font-weight: var(--aster-weight-strong); }
412
+ .aster-dialog-close { display: grid; place-items: center; flex: none; width: var(--aster-ctl); height: var(--aster-ctl); border-radius: var(--aster-r-ctl); color: var(--aster-text-2); }
413
+ .aster-dialog-close:hover { background: var(--aster-accent-soft); color: var(--aster-text); }
414
+ .aster-dialog-close svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
415
+ .aster-dialog-footer { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--aster-rule-2); }
379
416
  ::view-transition-group(aster-dialog-panel) { animation-duration: var(--aster-t-morph); animation-timing-function: var(--aster-ease); }
380
417
  ::view-transition-old(aster-dialog-panel) { animation: 90ms ease both aster-vt-out; }
381
418
  ::view-transition-new(aster-dialog-panel) { animation: var(--aster-t-ink) ease 80ms both aster-vt-in; }
@@ -409,12 +446,22 @@
409
446
  /* Against an element, and every submenu: anchor positioning, flipping at the edges. */
410
447
  .aster-menu[data-anchored] { left: auto; top: auto; translate: none; margin: 4px 0; position-area: bottom span-right; position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; }
411
448
  .aster-menu[data-depth]:not([data-depth="0"]) { margin: 0 2px; position-area: right span-bottom; position-try-fallbacks: flip-inline, flip-block, flip-inline flip-block; }
412
- .aster-menu [role^="menuitem"] { display: flex; align-items: center; gap: 6px; padding: var(--aster-row-y) 10px var(--aster-row-y) 6px; border-radius: var(--aster-r-ctl); text-align: left; }
449
+ .aster-menu[data-depth]:not([data-depth="0"])[data-submenu="end"] { position-area: right span-bottom; position-try-fallbacks: flip-block; }
450
+ .aster-menu[data-depth]:not([data-depth="0"])[data-submenu="start"] { position-area: left span-bottom; position-try-fallbacks: flip-block; }
451
+ :dir(rtl) .aster-menu[data-depth]:not([data-depth="0"])[data-submenu="end"] { position-area: left span-bottom; }
452
+ :dir(rtl) .aster-menu[data-depth]:not([data-depth="0"])[data-submenu="start"] { position-area: right span-bottom; }
453
+ .aster-menu [role^="menuitem"] { display: flex; align-items: center; gap: 6px; min-height: var(--aster-menu-cell-height); padding: var(--aster-row-y) 10px var(--aster-row-y) 6px; border-radius: var(--aster-r-ctl); text-align: left; }
413
454
  .aster-menu [role^="menuitem"]:hover, .aster-menu [role^="menuitem"]:focus, .aster-menu [aria-expanded="true"] { background: var(--aster-accent-soft); outline: none; }
414
455
  .aster-menu [role^="menuitem"]:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: -2px; }
415
456
  .aster-menu [aria-disabled="true"] { color: var(--aster-text-3); }
416
457
  .aster-menu-mark { width: 12px; flex: none; text-align: center; color: var(--aster-accent); }
458
+ .aster-menu-icon { display: grid; place-items: center; flex: none; width: calc(var(--aster-menu-icon-size) + 2px); color: var(--aster-text-2); }
459
+ .aster-menu-icon svg { width: var(--aster-menu-icon-size); height: var(--aster-menu-icon-size); fill: none; stroke: currentColor; stroke-width: 1.4; }
460
+ .aster-menu-shortcut { margin-inline-start: auto; padding-inline-start: 24px; color: var(--aster-text-3); font-size: 11.5px; white-space: nowrap; }
461
+ .aster-menu-shortcut + .aster-menu-more { margin-left: 8px; padding-left: 0; }
417
462
  .aster-menu-more { margin-left: auto; padding-left: 16px; color: var(--aster-text-3); }
463
+ .aster-menu-empty { padding: calc(var(--aster-row-y) + 2px) 10px; color: var(--aster-text-2); font-size: 12.5px; }
464
+ .aster-menu:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: 2px; }
418
465
  .aster-menu-separator { height: 1px; margin: 4px 6px; background: var(--aster-rule-2); }
419
466
  .aster-menu-heading { padding: var(--aster-row-y) 10px 2px 24px; font-size: 11.5px; font-weight: var(--aster-weight-label); color: var(--aster-text-3); }
420
467
 
@@ -437,6 +484,9 @@
437
484
  .aster-list-edit-row[data-new] .aster-field { box-shadow: var(--aster-well), inset 2px 0 0 var(--aster-accent); }
438
485
  [data-readonly] .aster-field { background: transparent; box-shadow: inset 0 0 0 .5px var(--aster-rule); }
439
486
  [data-readonly] .aster-field :is(input, select) { color: var(--aster-text-2); }
487
+ [data-disabled] .aster-field { background: var(--aster-sunk); opacity: .6; }
488
+ [data-disabled] .aster-field :is(input, textarea) { cursor: not-allowed; }
489
+ [data-disabled] .aster-field-head { opacity: .6; }
440
490
 
441
491
  /* ── check ── */
442
492
  aster-check { display: grid; gap: 6px; }
@@ -447,6 +497,15 @@
447
497
  .aster-select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; outline: none; user-select: none; }
448
498
  .aster-select-value[data-placeholder] { color: var(--aster-text-3); }
449
499
  [data-readonly] .aster-select-value { color: var(--aster-text-2); }
500
+ .aster-select:has([aria-disabled="true"]) { opacity: .55; cursor: not-allowed; }
501
+ .aster-select-clear {
502
+ display: grid; place-items: center; flex: none; width: 18px; height: 18px; border-radius: 999px;
503
+ color: var(--aster-text-2);
504
+ }
505
+ .aster-select-clear:hover { background: oklch(from var(--aster-text) l c h / .08); color: var(--aster-text); }
506
+ .aster-select-clear:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: -1px; }
507
+ .aster-select-clear svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
508
+ .aster-choice-empty { padding: var(--aster-row-y) 10px; color: var(--aster-text-2); font-size: 12.5px; }
450
509
 
451
510
  /* ── picker ── */
452
511
  .aster-picker { position: relative; }
@@ -461,18 +520,28 @@
461
520
  aster-window { display: contents; }
462
521
  .aster-window {
463
522
  position: fixed; inset: auto; margin: 0; left: var(--window-x, auto); top: var(--window-y, 24px); right: auto;
464
- display: none; flex-direction: column; width: 340px; height: 420px; min-width: min(240px, calc(100vw - 16px)); min-height: min(160px, calc(100dvh - 16px));
465
- max-width: calc(100vw - 16px); max-height: calc(100dvh - 16px); padding: 0; border: 0; overflow: hidden; resize: both;
523
+ display: none; flex-direction: column; width: 340px; height: 420px;
524
+ min-width: min(240px, var(--window-cap-width, calc(100vw - 16px))); min-height: min(160px, var(--window-cap-height, calc(100dvh - 16px)));
525
+ max-width: var(--window-cap-width, calc(100vw - 16px)); max-height: var(--window-cap-height, calc(100dvh - 16px));
526
+ padding: 0; border: 0; overflow: hidden; resize: both;
466
527
  border-radius: var(--aster-r-pop); background: var(--aster-surface); color: inherit; box-shadow: var(--aster-e3);
467
528
  }
468
529
  .aster-window:popover-open { display: flex; }
530
+ /* A modal window is a native dialog's child: the dialog supplies inertness and focus containment. */
531
+ .aster-window-modal {
532
+ position: fixed; inset: 0; width: auto; height: auto; max-width: none; max-height: none;
533
+ margin: 0; padding: 0; border: 0; background: transparent; overflow: visible;
534
+ }
535
+ .aster-window-modal::backdrop { background: var(--aster-scrim); }
536
+ .aster-window-modal[open] > .aster-window { display: flex; }
537
+ .aster-window[data-resizer="false"] { resize: none; }
469
538
  .aster-window[data-state="maximized"] {
470
539
  left: 8px; top: 8px; width: calc(100vw - 16px) !important; height: calc(100dvh - 16px) !important; resize: none;
471
540
  }
472
541
  .aster-window[data-active="true"] { outline: 1px solid var(--aster-rule); }
473
542
  .aster-window:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: 2px; }
474
543
  .aster-window-bar {
475
- display: flex; align-items: center; gap: 2px; padding: calc(var(--aster-pad) / 2) 6px calc(var(--aster-pad) / 2) 14px; cursor: grab; touch-action: none; user-select: none;
544
+ display: flex; align-items: center; gap: 2px; min-height: var(--aster-window-header-height); padding: calc(var(--aster-pad) / 2) 6px calc(var(--aster-pad) / 2) 14px; cursor: grab; touch-action: none; user-select: none;
476
545
  border-bottom: 1px solid var(--aster-rule-2);
477
546
  }
478
547
  .aster-window-bar:active { cursor: grabbing; }
@@ -489,6 +558,8 @@
489
558
  .aster-window-bar .aster-window-move, .aster-window-bar button[aria-describedby$="-resize-help"] { display:none; }
490
559
  }
491
560
  .aster-window-body { flex: 1; min-height: 0; overflow: auto; padding: var(--aster-pad) 14px; }
561
+ .aster-window-footer { flex: none; padding: calc(var(--aster-pad) / 2) 14px; border-top: 1px solid var(--aster-rule-2); }
562
+ .aster-window-status { flex: none; padding: 3px 14px; border-top: 1px solid var(--aster-rule-2); color: var(--aster-text-2); font-size: 11.5px; }
492
563
  /* Dashboard geometry stays in CSS; stored sizes are column/row intent. */
493
564
  aster-dashboard { display: block; min-width: 0; container-type: inline-size; }
494
565
  .aster-dashboard-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: var(--dashboard-row, 300px); gap: var(--dashboard-gap, 16px); }
@@ -538,6 +609,8 @@
538
609
  .aster-calendar td[role="gridcell"]:hover { background: var(--aster-accent-soft); }
539
610
  .aster-calendar td[aria-current="date"] { font-weight: var(--aster-weight-label); color: var(--aster-accent); }
540
611
  .aster-calendar td[aria-selected="true"] { background: var(--aster-accent); color: var(--aster-on-accent); }
612
+ .aster-calendar td[aria-disabled="true"] { color: var(--aster-text-3); text-decoration: line-through; cursor: not-allowed; }
613
+ .aster-calendar td[aria-disabled="true"]:hover { background: none; }
541
614
  .aster-calendar td:focus { outline: 2px solid var(--aster-focus); outline-offset: -2px; }
542
615
  .aster-picker-list {
543
616
  min-width: anchor-size(width); max-width: min(480px, 100vw - 32px); max-height: min(280px, 45vh); overflow: auto;
@@ -614,11 +687,34 @@
614
687
 
615
688
  /* ── section ── */
616
689
  aster-section { display: block; }
690
+ .aster-accordion-tools { display: flex; justify-content: flex-end; padding-bottom: 4px; }
691
+ .aster-accordion-tools button { min-height: var(--aster-ctl); padding: 0 8px; border-radius: var(--aster-r-ctl); color: var(--aster-accent); font-size: 12.5px; font-weight: var(--aster-weight-strong); }
692
+ .aster-accordion-tools button:hover { background: var(--aster-accent-soft); }
693
+ .aster-accordion-reorder { display: inline-flex; gap: 2px; }
694
+ .aster-accordion-reorder button { width: var(--aster-ctl); height: var(--aster-ctl); border-radius: var(--aster-r-ctl); color: var(--aster-text-2); }
695
+ .aster-accordion-reorder button:hover:not(:disabled) { background: var(--aster-accent-soft); color: var(--aster-text); }
696
+ .aster-accordion-reorder button:disabled { opacity: .4; }
617
697
  .aster-section-head { display: flex; align-items: center; gap: 10px; margin: 0; padding: var(--aster-pad) 0 calc(var(--aster-pad) - 4px); font: inherit; border-bottom: 1px solid var(--aster-rule-2); }
618
698
  .aster-section-head button { display: flex; align-items: center; gap: 8px; font-size: 13.5px; font-weight: var(--aster-weight-strong); color: var(--aster-text); }
619
699
  .aster-section-head svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 1.5; transition: rotate var(--aster-t-micro) var(--aster-ease); }
620
- aster-section[data-open] > .aster-section-head > button > svg { rotate: 90deg; }
700
+ aster-section[data-open] > .aster-section-head > button:not(.aster-section-close) > svg { rotate: 90deg; }
701
+ .aster-section-title { font-size: 13.5px; font-weight: var(--aster-weight-strong); color: var(--aster-text); }
621
702
  .aster-section-summary { font-size: 12px; color: var(--aster-text-3); }
703
+ .aster-section-head > .aster-section-close {
704
+ flex: none; display: grid; place-items: center; width: calc(var(--aster-ctl) - 2px); height: calc(var(--aster-ctl) - 2px);
705
+ border-radius: var(--aster-r-ctl); color: var(--aster-text-2);
706
+ }
707
+ .aster-section-head > .aster-section-close:hover { background: var(--aster-accent-soft); color: var(--aster-text); }
708
+ .aster-section-head > .aster-section-close:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: -2px; }
709
+ .aster-section-close svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
710
+ /* The window bar without its drag affordance: an in-flow panel. */
711
+ aster-section > .aster-window-bar { cursor: default; touch-action: auto; user-select: auto; }
712
+ aster-section > .aster-window-bar > h2 { display: flex; align-items: baseline; gap: 6px; }
713
+ .aster-window-bar .aster-section-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
714
+ .aster-window-bar .aster-section-summary { flex: none; font-weight: var(--aster-weight); }
715
+ .aster-window-bar .aster-section-fold svg { transition: rotate var(--aster-t-micro) var(--aster-ease); }
716
+ aster-section[data-open] > .aster-window-bar > .aster-section-fold svg { rotate: 180deg; }
717
+ .aster-section-footer { margin-top: var(--aster-pad); padding-top: calc(var(--aster-pad) - 4px); border-top: 1px solid var(--aster-rule-2); }
622
718
  /* Height animates as a grid track, so nothing is measured in JS. */
623
719
  .aster-section-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--aster-t-morph) var(--aster-ease); }
624
720
  aster-section[data-open] > .aster-section-body { grid-template-rows: 1fr; }
@@ -629,9 +725,26 @@
629
725
  aster-form { display: block; container-type: inline-size; }
630
726
  .aster-form-grid { display: grid; grid-template-columns: repeat(var(--columns, 2), minmax(0, 1fr)); gap: var(--aster-gap) calc(var(--aster-gap) + 4px); padding: var(--aster-gap) 0 calc(var(--aster-gap) + 4px); }
631
727
  .aster-form-grid > * { grid-column: span min(var(--span, 1), var(--columns, 2)); }
728
+ .aster-form-summary {
729
+ margin: 0 0 var(--aster-gap); padding: var(--aster-pad); border-radius: var(--aster-r-card);
730
+ background: var(--aster-sunk); box-shadow: inset 0 0 0 .5px var(--aster-negative), var(--aster-well);
731
+ }
732
+ .aster-form-summary-title { margin: 0 0 4px; color: var(--aster-negative); font-size: 13px; font-weight: var(--aster-weight-strong); }
733
+ .aster-form-summary-form { margin: 4px 0; color: var(--aster-text); font-size: 12.5px; }
734
+ .aster-form-summary ul { display: grid; gap: 2px; margin: 0; padding-left: 18px; }
735
+ .aster-form-summary button { color: var(--aster-negative); font-size: 12.5px; text-align: left; text-decoration: underline; text-underline-offset: 2px; border-radius: 2px; }
736
+ .aster-form-summary button:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: 1px; }
737
+ aster-form[data-title-orientation="start"] .aster-form-grid > * {
738
+ display: grid; grid-template-columns: var(--aster-form-label-width, 9rem) minmax(0, 1fr);
739
+ column-gap: var(--aster-gap); row-gap: 6px; align-items: start;
740
+ }
741
+ aster-form[data-title-orientation="start"] .aster-form-grid > * > .aster-field-head { grid-column: 1; grid-row: 1; align-self: center; }
742
+ aster-form[data-title-orientation="start"] .aster-form-grid > * > :not(.aster-field-head) { grid-column: 2; }
632
743
  @container (max-width: 520px) {
633
744
  .aster-form-grid { grid-template-columns: minmax(0, 1fr); }
634
745
  .aster-form-grid > * { grid-column: auto; }
746
+ aster-form[data-title-orientation="start"] .aster-form-grid > * { display: grid; grid-template-columns: minmax(0, 1fr); }
747
+ aster-form[data-title-orientation="start"] .aster-form-grid > * > :not(.aster-field-head) { grid-column: auto; }
635
748
  }
636
749
 
637
750
  /* ── split ── */
@@ -791,11 +904,22 @@
791
904
 
792
905
  /* ── grid ── */
793
906
  aster-grid { position: relative; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; min-height: 0; container: aster-grid / inline-size; }
907
+ /* The chrome row is always present: a hidden toolbar leaves it empty, and the table still takes the middle row. */
908
+ /* A hidden header stays in the accessibility tree so columnheader names and aria-rowcount arithmetic survive. */
909
+ .aster-grid-scroll[data-head-hidden] .aster-grid-head { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
794
910
 
795
911
  aster-grid[data-dragging], aster-grid[data-dragging] * { cursor: grabbing; }
796
- .aster-grid-toolbar { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; padding: var(--aster-row-y) 12px; border-bottom: 1px solid var(--aster-rule-2); }
912
+ .aster-grid-chrome { min-width: 0; }
913
+ .aster-grid-toolbar { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; min-height: var(--aster-grid-toolbar-height); padding: var(--aster-row-y) 12px; border-bottom: 1px solid var(--aster-rule-2); }
914
+ /* Drift lives outside the toolbar: hiding the toolbar cannot silence it. */
915
+ .aster-grid-drift { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: var(--aster-row-y) 12px; border-bottom: 1px solid var(--aster-rule-2); font-size: 12.5px; color: var(--aster-text-2); background: var(--aster-surface); }
916
+ .aster-grid-drift > span { flex: 1; min-width: 0; }
917
+ .aster-grid-drift button { flex: none; color: var(--aster-accent); font-weight: var(--aster-weight-label); }
918
+ .aster-grid-paging { display: flex; align-items: center; gap: 4px; padding: var(--aster-row-y) 12px; border-bottom: 1px solid var(--aster-rule-2); background: var(--aster-surface); }
919
+ .aster-grid-page { flex: 1; min-width: 0; text-align: center; font-size: 12.5px; color: var(--aster-text-2); font-variant-numeric: tabular-nums; }
920
+ .aster-grid-paging button:disabled { opacity: .4; }
797
921
  .aster-grid-group-tools { display: inline-flex; align-items: center; max-width: 100%; }
798
- .aster-grid-summary { flex: none; padding: var(--aster-row-y) 12px; border-top: 1px solid var(--aster-rule-2); font-size: 12.5px; }
922
+ .aster-grid-summary { flex: none; min-height: var(--aster-grid-summary-height); padding: var(--aster-row-y) 12px; border-top: 1px solid var(--aster-rule-2); font-size: 12.5px; }
799
923
  .aster-grid-summary dl { display: flex; flex-wrap: wrap; gap: 8px 24px; margin: 0; }
800
924
  .aster-grid-summary dl > div { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; min-width: 0; }
801
925
  .aster-grid-summary dt { color: var(--aster-text-2); overflow-wrap: anywhere; }
@@ -861,6 +985,8 @@
861
985
  .aster-drill-crumbs button { color: var(--aster-accent); font-size: 12.5px; }
862
986
  .aster-drill-crumbs button:hover { text-decoration: underline; }
863
987
  .aster-drill-crumbs button[aria-current] { color: var(--aster-text); font-weight: var(--aster-weight-strong); }
988
+ .aster-drill-search { flex: none; margin-inline-start: auto; }
989
+ .aster-drill-search input { width: 9rem; min-height: var(--aster-ctl); max-width: 34vw; font-size: 12.5px; }
864
990
  .aster-drill-heading { margin: 0; font-size: 14px; font-weight: var(--aster-weight-label); }
865
991
  .aster-drill-heading:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: 2px; border-radius: 2px; }
866
992
  .aster-drill-viewport { position: relative; isolation: isolate; overflow: hidden; min-height: 48px; }
@@ -891,11 +1017,13 @@
891
1017
  .aster-grid-row[data-drop="ok"] > .aster-grid-cell { background: var(--aster-accent-soft); box-shadow: inset 0 -2px 0 var(--aster-accent); }
892
1018
  .aster-grid-row[data-drop="refused"] > .aster-grid-cell { background: oklch(from var(--aster-negative) l c h / .08); cursor: not-allowed; }
893
1019
  /* Tree grid: the first column's indent and opener. */
894
- .aster-grid-tree { display: inline-flex; align-items: center; flex: none; padding-left: calc(var(--level, 0) * 18px); margin-right: 4px; vertical-align: middle; }
1020
+ .aster-grid-tree { display: inline-flex; align-items: center; flex: none; padding-left: calc(var(--level, 0) * var(--aster-grid-tree-indent)); margin-right: 4px; vertical-align: middle; }
895
1021
  .aster-grid-opener { display: grid; place-items: center; width: 18px; height: 18px; border-radius: var(--aster-r-ctl); color: var(--aster-text-2); }
896
1022
  button.aster-grid-opener:hover { background: var(--aster-accent-soft); color: var(--aster-text); }
897
1023
  .aster-grid-opener svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 1.4; transition: rotate var(--aster-t-micro) var(--aster-ease); }
898
1024
  .aster-grid-opener[data-open] svg { rotate: 90deg; }
1025
+ .aster-grid-tree-icon { display: inline-grid; place-items: center; flex: none; width: 18px; height: 18px; margin-inline-end: 4px; color: var(--aster-text-2); }
1026
+ .aster-grid-tree-icon svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; }
899
1027
  .aster-grid-note { font-size: 12.5px; color: var(--aster-text-2); }
900
1028
  /* A popover under its toolbar button, aligned to the button's right edge. */
901
1029
  .aster-grid-panel {
@@ -922,6 +1050,7 @@
922
1050
  .aster-grid-sort-panel li { gap: 8px; }
923
1051
  .aster-grid-sort-name { flex: 1; min-width: 0; color: var(--aster-text-2); }
924
1052
  .aster-grid-sort-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1053
+ .aster-grid-sort-reason { display: block; margin-top: 2px; font-size: 11px; color: var(--aster-text-3); overflow-wrap: anywhere; }
925
1054
  .aster-grid-sort-panel li:has([aria-pressed="true"]) .aster-grid-sort-name { color: var(--aster-text); }
926
1055
  .aster-grid-sort-actions { flex: none; display: flex; align-items: center; gap: 2px; }
927
1056
  .aster-grid-sort-priority { width: 14px; flex: none; text-align: right; font-size: 10px; font-variant-numeric: tabular-nums; color: var(--aster-text-3); }
@@ -958,10 +1087,42 @@
958
1087
  min-width: 0; padding: var(--aster-pad-y) 12px; background: var(--aster-surface);
959
1088
  overflow-wrap: anywhere; line-height: 1.4;
960
1089
  }
1090
+ /* Record rows keep at least a single line's height; the header, filter row,
1091
+ group headers and the card layout set their own geometry. */
1092
+ .aster-grid-row:not(.aster-grid-head, .aster-grid-filters, .aster-grid-group) > .aster-grid-cell { min-height: var(--aster-grid-row-min-height); }
1093
+ .aster-grid-head .aster-grid-cell { min-height: var(--aster-grid-header-height); }
961
1094
  .aster-grid-cell[data-numeric] { text-align: right; font-variant-numeric: tabular-nums; }
1095
+ .aster-grid-cell[data-align="start"] { text-align: start; }
1096
+ .aster-grid-cell[data-align="center"] { text-align: center; }
1097
+ .aster-grid-cell[data-align="end"] { text-align: end; }
1098
+ /* A row tone is a leading edge above the cells, never a background the cells cover. */
1099
+ .aster-grid-row[data-tone]::before {
1100
+ content: ""; position: absolute; inset-block: 0; inset-inline-start: 0; z-index: 3;
1101
+ width: 3px; background: var(--aster-text-3); pointer-events: none;
1102
+ }
1103
+ .aster-grid-row[data-tone="positive"]::before { background: var(--aster-positive); }
1104
+ .aster-grid-row[data-tone="caution"]::before { background: var(--aster-caution); }
1105
+ .aster-grid-row[data-tone="negative"]::before { background: var(--aster-negative); }
1106
+ @media (forced-colors: active) { .aster-grid-row[data-tone]::before { background: CanvasText; } }
962
1107
  .aster-grid-cell[data-frozen] { position: sticky; z-index: 1; }
963
1108
  .aster-grid-cell[data-frozen]:last-of-type, .aster-grid-cell[data-frozen]:has(+ :not([data-frozen])) { box-shadow: 1px 0 0 var(--aster-rule); }
964
1109
  .aster-grid-row[aria-selected="true"] > .aster-grid-cell { background: color-mix(in oklch, var(--aster-accent-soft), var(--aster-surface)); }
1110
+ /* Cell selection: mark the active column instead of the whole row. */
1111
+ .aster-grid-scroll[data-selection-appearance="cell"] .aster-grid-row[aria-selected="true"] > .aster-grid-cell { background: var(--aster-surface); }
1112
+ .aster-grid-scroll[data-selection-appearance="cell"] .aster-grid-cell[data-selected-cell] { background: color-mix(in oklch, var(--aster-accent-soft), var(--aster-surface)); }
1113
+ .aster-grid-cell[data-tone="positive"] { background: color-mix(in oklch, var(--aster-positive) 9%, var(--aster-surface)); }
1114
+ .aster-grid-cell[data-tone="caution"] { background: color-mix(in oklch, var(--aster-caution) 9%, var(--aster-surface)); }
1115
+ .aster-grid-cell[data-tone="negative"] { background: color-mix(in oklch, var(--aster-negative) 9%, var(--aster-surface)); }
1116
+ .aster-grid-cell[data-tone="neutral"] { background: color-mix(in oklch, var(--aster-text-3) 9%, var(--aster-surface)); }
1117
+ /* The selection column: a leading, always-present track of its own. */
1118
+ .aster-grid-cell.aster-grid-select {
1119
+ position: sticky; left: 0; z-index: 1; display: grid; place-items: center; padding: 0;
1120
+ background: var(--aster-surface); box-shadow: inset -1px 0 0 var(--aster-rule-2);
1121
+ }
1122
+ .aster-grid-head .aster-grid-cell.aster-grid-select { z-index: 3; }
1123
+ .aster-grid-select input { width: 16px; height: 16px; margin: 0; accent-color: var(--aster-accent); cursor: pointer; }
1124
+ .aster-grid-select input:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: 2px; border-radius: 2px; }
1125
+ .aster-grid-select-rank { position: absolute; inset-inline-end: 2px; bottom: 1px; font-size: 10px; color: var(--aster-text-3); font-variant-numeric: tabular-nums; }
965
1126
  /* The header and filter row stick together. */
966
1127
  .aster-grid-headgroup { position: sticky; top: 0; z-index: 2; display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }
967
1128
  .aster-grid-head { border-bottom: 1px solid var(--aster-rule); }
@@ -975,6 +1136,7 @@
975
1136
  .aster-grid-resize::after { content: ""; position: absolute; top: 25%; bottom: 25%; left: 3px; width: 2px; border-radius: 1px; background: transparent; transition: background var(--aster-t-micro); }
976
1137
  .aster-grid-resize:hover::after { background: var(--aster-accent); }
977
1138
  .aster-grid-filters .aster-grid-cell { padding: 4px 6px; background: var(--aster-surface); }
1139
+ .aster-grid-filter-refused { display: inline-flex; align-items: center; height: var(--aster-ctl-tight); font-size: 12.5px; color: var(--aster-text-3); }
978
1140
  .aster-grid-filters .aster-field { height: var(--aster-ctl-tight); padding: 0 8px; font-size: 12.5px; }
979
1141
  .aster-grid-filters .aster-error { font-size: 11.5px; }
980
1142
  .aster-grid-sort {
@@ -982,6 +1144,9 @@
982
1144
  font-size: 12px; font-weight: var(--aster-weight-label); color: var(--aster-text-2);
983
1145
  }
984
1146
  .aster-grid-header[data-numeric] .aster-grid-sort { justify-content: flex-end; }
1147
+ .aster-grid-header[data-align="start"] .aster-grid-sort { justify-content: flex-start; }
1148
+ .aster-grid-header[data-align="center"] .aster-grid-sort { justify-content: center; }
1149
+ .aster-grid-header[data-align="end"] .aster-grid-sort { justify-content: flex-end; }
985
1150
  .aster-grid-sort:hover { color: var(--aster-text); }
986
1151
  .aster-grid-sort-mark { width: 10px; height: 10px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; }
987
1152
  .aster-grid-sort small { font-size: 10px; color: var(--aster-text-3); }
@@ -1006,6 +1171,10 @@
1006
1171
  .aster-grid-row-note button { color: var(--aster-accent); font-weight: var(--aster-weight-strong); }
1007
1172
  .aster-grid-skeleton { display: block; height: 10px; margin-block: 4px; border-radius: 3px; background: oklch(from var(--aster-text) l c h / .06); }
1008
1173
  .aster-grid-empty { padding: 24px 14px; color: var(--aster-text-3); }
1174
+ .aster-grid-loading { padding: 24px 14px; color: var(--aster-text-2); }
1175
+ .aster-grid-failure { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: var(--aster-row-y) 12px; border-bottom: 1px solid var(--aster-rule-2); background: var(--aster-surface); color: var(--aster-negative); font-size: 12.5px; }
1176
+ .aster-grid-failure > span { flex: 1; min-width: 0; }
1177
+ .aster-grid-failure button { flex: none; color: var(--aster-accent); font-weight: var(--aster-weight-label); }
1009
1178
 
1010
1179
  /* Touch: larger targets, same look. */
1011
1180
  @media (pointer: coarse) {
@@ -1031,13 +1200,15 @@
1031
1200
  .aster-grid-row:not(.aster-grid-head) { display: block; padding: calc(var(--aster-pad) - 4px) var(--aster-pad); border-bottom: 1px solid var(--aster-rule); }
1032
1201
  .aster-grid-row:not(.aster-grid-head) > .aster-grid-cell {
1033
1202
  position: static; display: grid; grid-template-columns: minmax(6.5rem, 34%) minmax(0, 1fr); gap: 8px;
1034
- padding: 3px 0; box-shadow: none; text-align: left;
1203
+ min-height: 0; padding: 3px 0; box-shadow: none; text-align: left;
1035
1204
  }
1036
1205
  .aster-grid-row:not(.aster-grid-head) > .aster-grid-cell[data-numeric] { justify-content: initial; }
1037
1206
  /* The card, not its cells, carries the row's background, so selection and drop targets cover its padding too. */
1038
1207
  .aster-grid-row:not(.aster-grid-head) { background: var(--aster-surface); }
1039
1208
  .aster-grid-row:not(.aster-grid-head) > .aster-grid-cell { background: none; }
1040
1209
  .aster-grid-row[aria-selected="true"]:not(.aster-grid-head) { background: color-mix(in oklch, var(--aster-accent-soft), var(--aster-surface)); }
1210
+ .aster-grid-scroll[data-selection-appearance="cell"] .aster-grid-row[aria-selected="true"]:not(.aster-grid-head) { background: var(--aster-surface); }
1211
+ .aster-grid-row:not(.aster-grid-head) > .aster-grid-cell.aster-grid-select { position: static; box-shadow: none; padding: 3px 0; }
1041
1212
  .aster-grid-row[data-drop="ok"]:not(.aster-grid-head) { background: var(--aster-accent-soft); box-shadow: inset 0 -2px 0 var(--aster-accent); }
1042
1213
  .aster-grid-row[data-drop="refused"]:not(.aster-grid-head) { background: oklch(from var(--aster-negative) l c h / .08); }
1043
1214
  .aster-grid-row:not(.aster-grid-head) > .aster-grid-cell[data-label]::before {
package/styles/base.css CHANGED
@@ -11,6 +11,20 @@
11
11
  @media (pointer: coarse) {
12
12
  :root:not([data-density]) { --aster-ctl: 38px; --aster-ctl-tight: 32px; --aster-pad-y: 12px; --aster-row-y: 9px; --aster-pad: 16px; --aster-gap: 18px; }
13
13
  }
14
+ /* Component geometry intents: defaults an identity or an app may override.
15
+ `auto` follows the content; lengths are floors a placement may raise. */
16
+ :root {
17
+ --aster-form-label-width: 9rem;
18
+ --aster-grid-header-height: calc(var(--aster-pad-y) * 2 + 16.8px);
19
+ --aster-grid-toolbar-height: auto;
20
+ --aster-grid-row-min-height: auto;
21
+ --aster-grid-tree-indent: 18px;
22
+ --aster-grid-summary-height: auto;
23
+ --aster-menu-cell-height: auto;
24
+ --aster-menu-icon-size: 14px;
25
+ --aster-window-header-height: calc(var(--aster-ctl) + var(--aster-pad) - 1px);
26
+ --aster-dialog-max-width: 100%;
27
+ }
14
28
 
15
29
  *, *::before, *::after { box-sizing: border-box; }
16
30
  html, body { margin: 0; height: 100%; }
@@ -39,3 +39,19 @@ export const TOKEN_NAMES: readonly string[] = Object.values(TOKENS).flat().map((
39
39
  export const DENSITY = ["ctl", "ctl-tight", "pad-y", "row-y", "pad", "gap"] as const;
40
40
  export const DENSITIES = ["compact", "standard", "comfortable"] as const;
41
41
  export const DENSITY_NAMES: readonly string[] = DENSITY.map((t) => `--aster-${t}`);
42
+
43
+ /**
44
+ * Component geometry intents, by component. Unlike [[TOKENS]] an identity
45
+ * need not define these: each has a default in `base.css`, and a component
46
+ * uses it for geometry CSS owns. An app may set one on a placement.
47
+ */
48
+ export const COMPONENT_VARS = {
49
+ form: ["label-width"],
50
+ grid: ["header-height", "toolbar-height", "row-min-height", "tree-indent", "summary-height"],
51
+ menu: ["cell-height", "icon-size"],
52
+ window: ["header-height"],
53
+ dialog: ["max-width"],
54
+ } as const;
55
+ /** Every name in [[constant:COMPONENT_VARS]] as a full custom property name, such as `--aster-grid-header-height`. */
56
+ export const COMPONENT_VAR_NAMES: readonly string[] = Object.entries(COMPONENT_VARS)
57
+ .flatMap(([component, names]) => names.map((name) => `--aster-${component}-${name}`));