@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
package/NOTICE CHANGED
@@ -3,3 +3,7 @@ Copyright 2026 Aster UI contributors
3
3
 
4
4
  Aster UI code in this package is licensed under the Apache License, Version 2.0.
5
5
  See LICENSE. Separately installed dependencies retain their respective licenses.
6
+
7
+ The font files in styles/fonts/ are licensed under the SIL Open Font License,
8
+ Version 1.1. Their copyright notices and license texts are in the
9
+ OFL-bricolage-grotesque.txt and OFL-fraunces.txt files beside them.
package/README.md CHANGED
@@ -18,6 +18,12 @@ resolves npm modules and CSS imports. The package ships TypeScript declarations.
18
18
  Its main entry registers browser elements and requires a DOM; use
19
19
  `@getaster/data` for headless work. Package tooling requires Node 22 or later.
20
20
 
21
+ `@getaster/elements/styles/theme-lamplight.css` and
22
+ `@getaster/elements/styles/theme-harbour.css` each compose the base primitives,
23
+ element styles and one identity. Lamplight's entry also loads the packaged
24
+ Bricolage Grotesque and Fraunces Latin faces from
25
+ `@getaster/elements/styles/fonts.css`. Import one composed entry, not the parts.
26
+
21
27
  ## Connect a grid
22
28
 
23
29
  With an OData service exposing a `Projects` entity set and JSON CSDL metadata:
@@ -52,10 +58,13 @@ keyboard interaction and semantic markup; application labels, focus flow and
52
58
  accessibility still need testing in the completed application.
53
59
 
54
60
  The package depends on Aster's data core, Lit HTML and Preact signals. Keep one
55
- compatible copy of each shared runtime in your build. The optional ECharts
56
- adapter accepts an application-supplied runtime; ECharts, showcase data, fonts
57
- and the mock service are not bundled into this package. There are no install
58
- scripts.
61
+ compatible copy of each shared runtime in your build. The YAML compiler is a
62
+ separate subpath, `@getaster/elements/yaml`, and is the only entry that loads
63
+ the `yaml` parser; a browser that imports a compiled definition never loads it.
64
+ The optional ECharts adapter accepts an application-supplied runtime; ECharts,
65
+ showcase data and the mock service are not bundled into this package. The
66
+ Lamplight font files and their SIL Open Font License texts are included under
67
+ `styles/fonts/`. There are no install scripts.
59
68
 
60
69
  ## Documentation and support
61
70
 
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
3
 
4
- // packages/elements/tools/aster-theme-check.ts
5
- import { readFileSync } from "fs";
4
+ // packages/elements/tools/theme-check.ts
5
+ import { readFileSync } from "node:fs";
6
+ import { dirname, resolve as resolvePath } from "node:path";
6
7
 
7
8
  // packages/elements/src/styles/contract.ts
8
9
  var TOKENS = {
@@ -34,8 +35,23 @@ var TOKENS = {
34
35
  var TOKEN_NAMES = Object.values(TOKENS).flat().map((t) => `--aster-${t}`);
35
36
  var DENSITY = ["ctl", "ctl-tight", "pad-y", "row-y", "pad", "gap"];
36
37
  var DENSITY_NAMES = DENSITY.map((t) => `--aster-${t}`);
38
+ var COMPONENT_VARS = {
39
+ form: ["label-width"],
40
+ grid: ["header-height", "toolbar-height", "row-min-height", "tree-indent", "summary-height"],
41
+ menu: ["cell-height", "icon-size"],
42
+ window: ["header-height"],
43
+ dialog: ["max-width"]
44
+ };
45
+ var COMPONENT_VAR_NAMES = Object.entries(COMPONENT_VARS).flatMap(([component, names]) => names.map((name) => `--aster-${component}-${name}`));
37
46
 
38
47
  // packages/elements/tools/theme-check.ts
48
+ function readWithImports(file, seen = new Set) {
49
+ const path = resolvePath(file);
50
+ if (seen.has(path))
51
+ return "";
52
+ seen.add(path);
53
+ return readFileSync(path, "utf8").replace(/@import\s+(?:url\()?["']([^"']+)["']\)?[^;]*;/g, (rule, target) => target.startsWith(".") ? readWithImports(resolvePath(dirname(path), target), seen) : rule);
54
+ }
39
55
  var PAIRS = [
40
56
  ["text", "surface", 4.5],
41
57
  ["text", "page", 4.5],
@@ -59,8 +75,23 @@ var PAIRS = [
59
75
  ["toast-text", "toast-bg", 4.5],
60
76
  ["toast-action", "toast-bg", 4.5]
61
77
  ];
62
- function identities(css) {
78
+ function identityLayer(css) {
63
79
  const text = css.replace(/\/\*[\s\S]*?\*\//g, "");
80
+ const start = /@layer\s+aster\.identity\s*\{/.exec(text)?.index;
81
+ if (start === undefined)
82
+ return text;
83
+ const open = text.indexOf("{", start);
84
+ let depth = 0;
85
+ for (let i = open;i < text.length; i++) {
86
+ if (text[i] === "{")
87
+ depth++;
88
+ else if (text[i] === "}" && --depth === 0)
89
+ return text.slice(open + 1, i);
90
+ }
91
+ return text.slice(open + 1);
92
+ }
93
+ function identities(css) {
94
+ const text = identityLayer(css);
64
95
  const found = new Map;
65
96
  for (const m of text.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
66
97
  const selector = m[1].trim();
@@ -298,7 +329,7 @@ if (files.length === 0) {
298
329
  }
299
330
  var failed = false;
300
331
  for (const file of files) {
301
- const found = identities(readFileSync(file, "utf8"));
332
+ const found = identities(readWithImports(file));
302
333
  if (found.length === 0) {
303
334
  console.log(`\u2717 ${file}: no :root rule with --aster- tokens`);
304
335
  failed = true;
@@ -34,7 +34,14 @@ export declare abstract class AsterElement extends HTMLElement {
34
34
  protected onDisconnect(cleanup: () => void): void;
35
35
  /** Listens on `target` until the element disconnects. */
36
36
  protected listen<K extends keyof WindowEventMap>(target: EventTarget, type: K, handler: (event: WindowEventMap[K]) => void, options?: AddEventListenerOptions): void;
37
- protected emit<T>(type: string, detail?: T): boolean;
37
+ /**
38
+ * Dispatches a bubbling CustomEvent. Action events are cancelable by
39
+ * default; a notification that fires after the fact passes
40
+ * `{ cancelable: false }`.
41
+ */
42
+ protected emit<T>(type: string, detail?: T, options?: {
43
+ readonly cancelable?: boolean;
44
+ }): boolean;
38
45
  }
39
46
  export declare function define(name: string, ctor: CustomElementConstructor): void;
40
47
  /** Makes `scope` the page scope for `el` and everything inside it. */
@@ -66,8 +66,13 @@ export class AsterElement extends HTMLElement {
66
66
  target.addEventListener(type, handler, options);
67
67
  this.onDisconnect(() => target.removeEventListener(type, handler, options));
68
68
  }
69
- emit(type, detail) {
70
- return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, cancelable: true }));
69
+ /**
70
+ * Dispatches a bubbling CustomEvent. Action events are cancelable by
71
+ * default; a notification that fires after the fact passes
72
+ * `{ cancelable: false }`.
73
+ */
74
+ emit(type, detail, options = {}) {
75
+ return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, cancelable: options.cancelable ?? true }));
71
76
  }
72
77
  #upgradeProperties() {
73
78
  for (const name of Object.keys(this)) {
@@ -1,12 +1,17 @@
1
1
  import { AsterElement, type Bindable, type View } from "../base/element.ts";
2
+ /** A button's visual weight: neutral, accented, or destructive. */
3
+ export type ButtonTone = "secondary" | "primary" | "danger";
4
+ /** A button's size on the density scale. */
5
+ export type ButtonSize = "sm" | "md" | "lg";
2
6
  /**
3
7
  * A button with every state: rest, hover, pressed, focus, busy and
4
8
  * disabled. Busy and confirmation swap the label in place; every label is
5
9
  * stacked in one grid cell, so the width never changes. While busy, clicks
6
10
  * are ignored and a submit doesn't fire.
7
11
  *
8
- * Prefer `reason` to disabling: the button stays focusable, says why in its
9
- * title, and its click is swallowed.
12
+ * `disabled` is the native state: the button leaves the tab order and can't
13
+ * be pressed. Prefer `reason` for an advisory refusal: the button stays
14
+ * focusable, says why in its title, and its click is swallowed.
10
15
  */
11
16
  export declare class Button extends AsterElement {
12
17
  #private;
@@ -20,8 +25,18 @@ export declare class Button extends AsterElement {
20
25
  set confirmLabel(v: string | undefined);
21
26
  get busy(): Bindable<boolean>;
22
27
  set busy(v: Bindable<boolean>);
28
+ /** Sugar for `tone`: `"primary"` or `"secondary"`. A `"danger"` tone reads back as secondary. */
23
29
  get variant(): "secondary" | "primary";
24
30
  set variant(v: "secondary" | "primary");
31
+ /** The button's visual weight. Default `"secondary"`. */
32
+ get tone(): ButtonTone;
33
+ set tone(v: ButtonTone);
34
+ /** The button's size. Default `"md"`. */
35
+ get size(): ButtonSize;
36
+ set size(v: ButtonSize);
37
+ /** The native disabled state: out of the tab order, and no click or submit. Default `false`. */
38
+ get disabled(): boolean;
39
+ set disabled(v: boolean);
25
40
  get type(): "button" | "submit";
26
41
  set type(v: "button" | "submit");
27
42
  get icon(): View;
@@ -6,8 +6,9 @@ import { AsterElement, define, read } from "../base/element.js";
6
6
  * stacked in one grid cell, so the width never changes. While busy, clicks
7
7
  * are ignored and a submit doesn't fire.
8
8
  *
9
- * Prefer `reason` to disabling: the button stays focusable, says why in its
10
- * title, and its click is swallowed.
9
+ * `disabled` is the native state: the button leaves the tab order and can't
10
+ * be pressed. Prefer `reason` for an advisory refusal: the button stays
11
+ * focusable, says why in its title, and its click is swallowed.
11
12
  */
12
13
  export class Button extends AsterElement {
13
14
  #label = this.prop("");
@@ -15,7 +16,9 @@ export class Button extends AsterElement {
15
16
  #confirmLabel = this.prop(undefined);
16
17
  #busy = this.prop(false);
17
18
  #confirming = this.prop(false);
18
- #variant = this.prop("secondary");
19
+ #tone = this.prop("secondary");
20
+ #size = this.prop("md");
21
+ #disabled = this.prop(false);
19
22
  #type = this.prop("button");
20
23
  #icon = this.prop(nothing);
21
24
  #reason = this.prop(undefined);
@@ -46,11 +49,39 @@ export class Button extends AsterElement {
46
49
  set busy(v) {
47
50
  this.#busy.value = v;
48
51
  }
52
+ /** Sugar for `tone`: `"primary"` or `"secondary"`. A `"danger"` tone reads back as secondary. */
49
53
  get variant() {
50
- return this.#variant.value;
54
+ return this.#tone.value === "primary" ? "primary" : "secondary";
51
55
  }
52
56
  set variant(v) {
53
- this.#variant.value = v;
57
+ if (v !== "secondary" && v !== "primary")
58
+ throw new TypeError('variant must be "secondary" or "primary"');
59
+ this.#tone.value = v;
60
+ }
61
+ /** The button's visual weight. Default `"secondary"`. */
62
+ get tone() {
63
+ return this.#tone.value;
64
+ }
65
+ set tone(v) {
66
+ if (v !== "secondary" && v !== "primary" && v !== "danger")
67
+ throw new TypeError('tone must be "secondary", "primary" or "danger"');
68
+ this.#tone.value = v;
69
+ }
70
+ /** The button's size. Default `"md"`. */
71
+ get size() {
72
+ return this.#size.value;
73
+ }
74
+ set size(v) {
75
+ if (v !== "sm" && v !== "md" && v !== "lg")
76
+ throw new TypeError('size must be "sm", "md" or "lg"');
77
+ this.#size.value = v;
78
+ }
79
+ /** The native disabled state: out of the tab order, and no click or submit. Default `false`. */
80
+ get disabled() {
81
+ return this.#disabled.value;
82
+ }
83
+ set disabled(v) {
84
+ this.#disabled.value = v;
54
85
  }
55
86
  get type() {
56
87
  return this.#type.value;
@@ -90,8 +121,11 @@ export class Button extends AsterElement {
90
121
  const labels = [this.#label.value, this.#busyLabel.value, this.#confirmLabel.value];
91
122
  const showing = busy && labels[1] ? 1 : confirming && labels[2] ? 2 : 0;
92
123
  const reason = this.#reason.value;
93
- return html `<button class="aster-button" type=${this.#type.value} data-variant=${this.#variant.value}
94
- aria-busy=${busy ? "true" : "false"} aria-disabled=${reason ? "true" : nothing} title=${reason ?? nothing}>
124
+ const disabled = this.#disabled.value;
125
+ return html `<button class="aster-button" type=${this.#type.value} data-tone=${this.#tone.value}
126
+ data-variant=${this.#tone.value === "primary" ? "primary" : "secondary"} data-size=${this.#size.value}
127
+ ?disabled=${disabled}
128
+ aria-busy=${busy ? "true" : "false"} aria-disabled=${reason && !disabled ? "true" : nothing} title=${reason ?? nothing}>
95
129
  ${this.#icon.value}
96
130
  <span class="aster-label-swap">
97
131
  ${labels.map((l, i) => (l === undefined ? nothing : html `<span ?data-off=${i !== showing}>${l}</span>`))}
@@ -99,7 +133,7 @@ export class Button extends AsterElement {
99
133
  </button>`;
100
134
  }
101
135
  #guard(e) {
102
- if (read(this.#busy.peek()) || this.#reason.peek()) {
136
+ if (this.#disabled.peek() || read(this.#busy.peek()) || this.#reason.peek()) {
103
137
  e.preventDefault();
104
138
  e.stopImmediatePropagation();
105
139
  }