@getaster/elements 0.1.0-preview.0 → 0.2.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.
@@ -171,8 +171,8 @@ function oklch(args, lookup) {
171
171
  if (!base)
172
172
  return;
173
173
  const [l, c, h] = toOklch(base);
174
- const parts2 = splitTop(from[2]);
175
- if (parts2.length !== 3)
174
+ const parts = splitTop(from[2]);
175
+ if (parts.length !== 3)
176
176
  return;
177
177
  const channel = (p, own, key) => {
178
178
  if (p === key)
@@ -180,12 +180,12 @@ function oklch(args, lookup) {
180
180
  const calc = new RegExp(`^calc\\(${key}\\s*([+-])\\s*([\\d.]+)\\)$`).exec(p);
181
181
  return calc ? own + (calc[1] === "+" ? 1 : -1) * Number(calc[2]) : number(p);
182
182
  };
183
- const L2 = channel(parts2[0], l, "l");
184
- const C2 = channel(parts2[1], c, "c");
185
- const H2 = channel(parts2[2], h, "h");
186
- if ([L2, C2, H2].some((x) => x === undefined || Number.isNaN(x)))
183
+ const L = channel(parts[0], l, "l");
184
+ const C = channel(parts[1], c, "c");
185
+ const H = channel(parts[2], h, "h");
186
+ if ([L, C, H].some((x) => x === undefined || Number.isNaN(x)))
187
187
  return;
188
- return { ...fromOklch(L2, C2, H2), a: alpha ?? base.a };
188
+ return { ...fromOklch(L, C, H), a: alpha ?? base.a };
189
189
  }
190
190
  const parts = main.split(/\s+/).filter(Boolean);
191
191
  if (parts.length !== 3)
@@ -14,6 +14,7 @@ export class Carousel extends AsterElement {
14
14
  /** The slides don't all fit: only then are there places to move to. */
15
15
  #overflows = this.prop(false);
16
16
  #resize;
17
+ #resizeFrame = 0;
17
18
  #id = uid("aster-carousel");
18
19
  #observer;
19
20
  /** Rebuilt when descriptors change or the element reconnects. */
@@ -59,6 +60,8 @@ export class Carousel extends AsterElement {
59
60
  disconnectedCallback() {
60
61
  this.#observer?.disconnect();
61
62
  this.#resize?.disconnect();
63
+ cancelAnimationFrame(this.#resizeFrame);
64
+ this.#resizeFrame = 0;
62
65
  this.#observed = undefined;
63
66
  super.disconnectedCallback();
64
67
  }
@@ -122,7 +125,15 @@ export class Carousel extends AsterElement {
122
125
  for (const el of this.#slideEls())
123
126
  this.#observer.observe(el);
124
127
  this.#resize?.disconnect();
125
- this.#resize = new ResizeObserver(() => (this.#overflows.value = rail.scrollWidth > rail.clientWidth + 1));
128
+ this.#resize = new ResizeObserver(() => {
129
+ // Adding navigation changes the rail's layout; commit outside observer delivery.
130
+ if (this.#resizeFrame)
131
+ return;
132
+ this.#resizeFrame = requestAnimationFrame(() => {
133
+ this.#resizeFrame = 0;
134
+ this.#overflows.value = rail.scrollWidth > rail.clientWidth + 1;
135
+ });
136
+ });
126
137
  this.#resize.observe(rail);
127
138
  for (const el of this.#slideEls())
128
139
  this.#resize.observe(el);
package/dist/grid/grid.js CHANGED
@@ -2167,6 +2167,9 @@ export class Grid extends AsterElement {
2167
2167
  }
2168
2168
  #focusEditor(key, col) {
2169
2169
  const row = this.querySelector(`.aster-grid-row[data-key="${CSS.escape(key)}"]`);
2170
+ // A user may choose another control before deferred initial focus runs.
2171
+ if (row?.contains(document.activeElement))
2172
+ return;
2170
2173
  const cells = row ? [...row.querySelectorAll('[role="gridcell"]')] : [];
2171
2174
  const target = cells[col]?.querySelector("input:not([readonly]), textarea:not([readonly]), [role=combobox]:not([aria-readonly])") ??
2172
2175
  row?.querySelector("input:not([readonly]), textarea:not([readonly]), [role=combobox]:not([aria-readonly])");
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { ProgressStatus } from "./progress/status.ts";
2
2
  export { provideProgressPolicy, progressPolicyOf } from "./progress/policy.ts";
3
3
  export { asterComponents } from "./composition/builtins.ts";
4
4
  export { Sidebar, sidebarItems, type SidebarItem } from "./sidebar/sidebar.ts";
5
- export { AsterElement, define, each, provideScope, read, scopeOf, type Bindable, type View } from "./base/element.ts";
5
+ export { AsterElement, define, each, uid, provideScope, read, scopeOf, type Bindable, type View } from "./base/element.ts";
6
6
  export { Composite, compositeComponent, type CompositeOptions, type CompositeChildHandle, type CompositePlacement } from "./composition/composite.ts";
7
7
  export { CommandBar } from "./composition/command-bar.ts";
8
8
  export { defineCompositeRegistry, defineCompositeRenderer, CompositeAuthoring, type RegistryCatalog, type CompositeDiagnostic, type CompositeCompletion } from "./composition/authoring.ts";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { ProgressStatus } from "./progress/status.js";
2
2
  export { provideProgressPolicy, progressPolicyOf } from "./progress/policy.js";
3
3
  export { asterComponents } from "./composition/builtins.js";
4
4
  export { Sidebar, sidebarItems } from "./sidebar/sidebar.js";
5
- export { AsterElement, define, each, provideScope, read, scopeOf } from "./base/element.js";
5
+ export { AsterElement, define, each, uid, provideScope, read, scopeOf } from "./base/element.js";
6
6
  export { Composite, compositeComponent } from "./composition/composite.js";
7
7
  export { CommandBar } from "./composition/command-bar.js";
8
8
  export { defineCompositeRegistry, defineCompositeRenderer, CompositeAuthoring } from "./composition/authoring.js";
package/dist/tabs/tabs.js CHANGED
@@ -108,10 +108,24 @@ export class Tabs extends AsterElement {
108
108
  });
109
109
  }
110
110
  #revealSelected() {
111
- this.querySelector(".aster-tabs")?.querySelector('[role="tab"][aria-selected="true"]')?.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "instant" });
111
+ const tab = this.querySelector(".aster-tabs")?.querySelector('[role="tab"][aria-selected="true"]');
112
+ if (tab)
113
+ this.#reveal(tab);
114
+ }
115
+ /** Reveal within the strip without scrolling the containing page or panel. */
116
+ #reveal(tab) {
117
+ const viewport = this.querySelector(".aster-tabs-viewport");
118
+ if (!viewport)
119
+ return;
120
+ const visible = viewport.getBoundingClientRect();
121
+ const target = tab.getBoundingClientRect();
122
+ const left = target.left < visible.left ? target.left - visible.left : Math.max(0, target.right - visible.right);
123
+ if (left)
124
+ viewport.scrollBy({ left, behavior: "instant" });
112
125
  }
113
126
  #focusSelected() {
114
- this.querySelector(".aster-tabs")?.querySelector('[role="tab"][aria-selected="true"]')?.focus();
127
+ this.querySelector(".aster-tabs")?.querySelector('[role="tab"][aria-selected="true"]')?.focus({ preventScroll: true });
128
+ this.#revealSelected();
115
129
  }
116
130
  /** Stable for a value while the element lives; encoded so any value is a valid single id token. */
117
131
  #tabId(value) {
@@ -193,8 +207,8 @@ export class Tabs extends AsterElement {
193
207
  }
194
208
  e.preventDefault();
195
209
  tabs.forEach((tab, index) => { tab.tabIndex = index === n ? 0 : -1; });
196
- tabs[n].focus();
197
- tabs[n].scrollIntoView({ block: "nearest", inline: "nearest", behavior: "instant" });
210
+ tabs[n].focus({ preventScroll: true });
211
+ this.#reveal(tabs[n]);
198
212
  if (this.#activation.peek() === "automatic")
199
213
  this.#choose(tabs[n].dataset.value);
200
214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getaster/elements",
3
- "version": "0.1.0-preview.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -11,7 +11,7 @@
11
11
  "./styles/*": "./styles/*"
12
12
  },
13
13
  "dependencies": {
14
- "@getaster/data": "0.1.0-preview.0",
14
+ "@getaster/data": "0.2.0",
15
15
  "@preact/signals-core": "1.14.4",
16
16
  "lit-html": "3.3.3"
17
17
  },
@@ -34,6 +34,8 @@
34
34
  mix-blend-mode: var(--aster-texture-blend, normal);
35
35
  }
36
36
  button, input, select, textarea { font: inherit; color: inherit; }
37
+ /* Native controls inherit ink, so their surfaces must follow the same theme. */
38
+ select, option, optgroup { background-color: var(--aster-sunk); }
37
39
  button { background: none; border: 0; padding: 0; margin: 0; text-align: inherit; }
38
40
  h1, h2, h3 { text-wrap: balance; }
39
41
  p { text-wrap: pretty; }
@@ -460,6 +462,10 @@
460
462
  .aster-window-bar button:hover { background: var(--aster-accent-soft); color: var(--aster-text); }
461
463
  .aster-window-bar button:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: -2px; }
462
464
  .aster-window-bar svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
465
+ @media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
466
+ .aster-window { left:8px; top:8px; width:calc(100vw - 16px) !important; height:min(420px, calc(100dvh - 16px)) !important; resize:none; }
467
+ .aster-window-bar .aster-window-move, .aster-window-bar button[aria-describedby$="-resize-help"] { display:none; }
468
+ }
463
469
  .aster-window-body { flex: 1; min-height: 0; overflow: auto; padding: var(--aster-pad) 14px; }
464
470
  /* Dashboard geometry stays in CSS; stored sizes are column/row intent. */
465
471
  aster-dashboard { display: block; min-width: 0; container-type: inline-size; }
package/styles/base.css CHANGED
@@ -34,6 +34,8 @@
34
34
  mix-blend-mode: var(--aster-texture-blend, normal);
35
35
  }
36
36
  button, input, select, textarea { font: inherit; color: inherit; }
37
+ /* Native controls inherit ink, so their surfaces must follow the same theme. */
38
+ select, option, optgroup { background-color: var(--aster-sunk); }
37
39
  button { background: none; border: 0; padding: 0; margin: 0; text-align: inherit; }
38
40
  h1, h2, h3 { text-wrap: balance; }
39
41
  p { text-wrap: pretty; }
@@ -373,6 +373,10 @@
373
373
  .aster-window-bar button:hover { background: var(--aster-accent-soft); color: var(--aster-text); }
374
374
  .aster-window-bar button:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: -2px; }
375
375
  .aster-window-bar svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
376
+ @media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
377
+ .aster-window { left:8px; top:8px; width:calc(100vw - 16px) !important; height:min(420px, calc(100dvh - 16px)) !important; resize:none; }
378
+ .aster-window-bar .aster-window-move, .aster-window-bar button[aria-describedby$="-resize-help"] { display:none; }
379
+ }
376
380
  .aster-window-body { flex: 1; min-height: 0; overflow: auto; padding: var(--aster-pad) 14px; }
377
381
  /* Dashboard geometry stays in CSS; stored sizes are column/row intent. */
378
382
  aster-dashboard { display: block; min-width: 0; container-type: inline-size; }