@adia-ai/web-components 0.8.4 → 0.8.5

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 (53) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/USAGE.md +60 -25
  3. package/components/adia-mark/adia-mark.a2ui.json +123 -0
  4. package/components/adia-mark/adia-mark.class.js +64 -0
  5. package/components/adia-mark/adia-mark.css +48 -0
  6. package/components/adia-mark/adia-mark.d.ts +20 -0
  7. package/components/adia-mark/adia-mark.examples.md +26 -0
  8. package/components/adia-mark/adia-mark.js +17 -0
  9. package/components/adia-mark/adia-mark.test.js +69 -0
  10. package/components/adia-mark/adia-mark.yaml +121 -0
  11. package/components/agent-questions/agent-questions.a2ui.json +2 -1
  12. package/components/agent-questions/agent-questions.class.js +96 -53
  13. package/components/agent-questions/agent-questions.css +27 -106
  14. package/components/agent-questions/agent-questions.yaml +1 -0
  15. package/components/button/button.class.js +5 -1
  16. package/components/calendar-grid/calendar-grid.a2ui.json +3 -1
  17. package/components/calendar-grid/calendar-grid.class.js +11 -6
  18. package/components/calendar-grid/calendar-grid.css +42 -20
  19. package/components/calendar-grid/calendar-grid.yaml +5 -0
  20. package/components/calendar-picker/calendar-picker.a2ui.json +3 -1
  21. package/components/calendar-picker/calendar-picker.class.js +11 -6
  22. package/components/calendar-picker/calendar-picker.css +39 -19
  23. package/components/calendar-picker/calendar-picker.yaml +5 -0
  24. package/components/chart/chart.class.js +17 -14
  25. package/components/color-input/color-input.class.js +8 -5
  26. package/components/color-picker/color-picker.class.js +6 -3
  27. package/components/field/field.class.js +10 -7
  28. package/components/fields/fields.class.js +15 -12
  29. package/components/icon/icon.class.js +9 -5
  30. package/components/index.js +1 -0
  31. package/components/nav/nav.class.js +2 -1
  32. package/components/noodles/noodles.class.js +14 -10
  33. package/components/page/page.class.js +3 -2
  34. package/components/select/select.class.js +3 -2
  35. package/components/swatch/swatch.class.js +27 -24
  36. package/components/swiper/swiper.class.js +4 -1
  37. package/components/swiper/swiper.css +13 -4
  38. package/components/toolbar/toolbar.class.js +11 -6
  39. package/components/tree/tree.class.js +5 -2
  40. package/core/data-stream.js +21 -15
  41. package/core/element.js +36 -1
  42. package/core/element.test.js +67 -0
  43. package/core/icons-phosphor.js +86 -22
  44. package/core/icons.js +22 -8
  45. package/dist/theme-provider.min.js +2 -2
  46. package/dist/web-components.min.css +1 -1
  47. package/dist/web-components.min.js +107 -102
  48. package/dist/web-components.sheet.js +1 -1
  49. package/package.json +1 -5
  50. package/styles/components.css +1 -0
  51. package/traits/error-shake/error-shake.js +23 -17
  52. package/traits/fade-presence/fade-presence.js +19 -15
  53. package/traits/success-checkmark/success-checkmark.js +21 -16
package/core/element.js CHANGED
@@ -64,6 +64,26 @@ function installProps(el, props) {
64
64
  }
65
65
  }
66
66
 
67
+ // A minimal no-op ElementInternals shim for environments with no real
68
+ // implementation (linkedom and other DOM-shim SSR passes — gh#285). Real
69
+ // form-participation/validation behavior is meaningless server-side (no
70
+ // live user interaction to validate); the shim exists so the ~12
71
+ // call sites across the framework that read `this.internals.setValidity(…)`
72
+ // / `.setFormValue(…)` / `.validity` etc. keep working as inert no-ops
73
+ // instead of throwing on a missing method. Surface matches exactly what
74
+ // the framework's own consumers call (grepped, not guessed).
75
+ const NOOP_INTERNALS = Object.freeze({
76
+ setFormValue() {},
77
+ setValidity() {},
78
+ checkValidity() { return true; },
79
+ reportValidity() { return true; },
80
+ get form() { return null; },
81
+ get labels() { return []; },
82
+ get validity() { return {}; },
83
+ get validationMessage() { return ''; },
84
+ get willValidate() { return false; },
85
+ });
86
+
67
87
  function reflect(el, a, v, t) {
68
88
  t === Boolean
69
89
  ? v ? el.setAttribute(a, '') : el.removeAttribute(a)
@@ -78,6 +98,11 @@ function adoptStyles(ctor) {
78
98
  ctor._sa = true;
79
99
  const sheets = ctor.styles;
80
100
  if (!sheets) return;
101
+ // gh#285 — linkedom's document has no adoptedStyleSheets at all; SSR
102
+ // has no rendered stylesheet to adopt into anyway (styles matter for
103
+ // paint, which doesn't happen server-side), so skipping is a correct
104
+ // no-op, not a degraded feature.
105
+ if (!('adoptedStyleSheets' in document)) return;
81
106
  const list = Array.isArray(sheets) ? sheets : [sheets];
82
107
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...list.filter(s => !document.adoptedStyleSheets.includes(s))];
83
108
  }
@@ -114,7 +139,17 @@ export class UIElement extends HTMLElement {
114
139
 
115
140
  constructor() {
116
141
  super();
117
- this.internals = this.attachInternals();
142
+ // gh#285 — linkedom (and other DOM-shim SSR passes) implement no
143
+ // ElementInternals; an unconditional attachInternals() call threw
144
+ // HERE, before ANY subclass code ran — the single highest-leverage
145
+ // crash site in the whole framework under SSR. Feature-detect and
146
+ // fall back to a no-op shim (form participation is meaningless
147
+ // server-side) rather than leaving `this.internals` undefined,
148
+ // which would just move the crash to every consumer's first
149
+ // `.setValidity(…)` call instead of fixing it.
150
+ this.internals = typeof this.attachInternals === 'function'
151
+ ? this.attachInternals()
152
+ : NOOP_INTERNALS;
118
153
  installProps(this, this.constructor.properties);
119
154
  }
120
155
 
@@ -249,3 +249,70 @@ describe('UIElement — addTrait', () => {
249
249
  expect(disconnects).toBe(1);
250
250
  });
251
251
  });
252
+
253
+ // gh#285 — SSR DOM shims (linkedom et al.) implement no ElementInternals
254
+ // and no document.adoptedStyleSheets. These tests simulate that absence
255
+ // directly (deleting the real APIs) rather than trusting a mock — the
256
+ // bug was an unconditional call crashing the CONSTRUCTOR, before any
257
+ // subclass code ran, so the only real proof is exercising construction
258
+ // with the API genuinely gone.
259
+ describe('UIElement — SSR browser-API absence (gh#285)', () => {
260
+ beforeEach(() => { document.body.innerHTML = ''; });
261
+
262
+ it('constructs without throwing when attachInternals does not exist', () => {
263
+ const original = HTMLElement.prototype.attachInternals;
264
+ delete HTMLElement.prototype.attachInternals;
265
+ try {
266
+ class El extends UIElement {}
267
+ const tag = registerTestElement(El);
268
+ expect(() => mount(tag)).not.toThrow();
269
+ } finally {
270
+ HTMLElement.prototype.attachInternals = original;
271
+ }
272
+ });
273
+
274
+ it('the no-op internals shim answers every call site the framework actually makes', () => {
275
+ const original = HTMLElement.prototype.attachInternals;
276
+ delete HTMLElement.prototype.attachInternals;
277
+ let el;
278
+ try {
279
+ class El extends UIElement {}
280
+ const tag = registerTestElement(El);
281
+ el = mount(tag);
282
+ } finally {
283
+ HTMLElement.prototype.attachInternals = original;
284
+ }
285
+ expect(() => el.internals.setFormValue('x')).not.toThrow();
286
+ expect(() => el.internals.setValidity({})).not.toThrow();
287
+ expect(el.internals.checkValidity()).toBe(true);
288
+ expect(el.internals.reportValidity()).toBe(true);
289
+ expect(el.internals.form).toBeNull();
290
+ expect(el.internals.labels).toEqual([]);
291
+ expect(el.internals.validity).toEqual({});
292
+ expect(el.internals.validationMessage).toBe('');
293
+ expect(el.internals.willValidate).toBe(false);
294
+ });
295
+
296
+ it('a real attachInternals is still used when the browser provides one (no regression)', () => {
297
+ class El extends UIElement {}
298
+ const tag = registerTestElement(El);
299
+ const el = mount(tag);
300
+ // happy-dom's real attachInternals — not the shim.
301
+ expect(el.internals).not.toBe(null);
302
+ expect(typeof el.internals.setFormValue).toBe('function');
303
+ });
304
+
305
+ it('adoptStyles no-ops instead of throwing when adoptedStyleSheets does not exist', () => {
306
+ const desc = Object.getOwnPropertyDescriptor(Document.prototype, 'adoptedStyleSheets');
307
+ delete Document.prototype.adoptedStyleSheets;
308
+ try {
309
+ class El extends UIElement {
310
+ static styles = ['/* fake sheet */'];
311
+ }
312
+ const tag = registerTestElement(El);
313
+ expect(() => mount(tag)).not.toThrow();
314
+ } finally {
315
+ if (desc) Object.defineProperty(Document.prototype, 'adoptedStyleSheets', desc);
316
+ }
317
+ });
318
+ });
@@ -31,13 +31,66 @@
31
31
  * import { installIconLoaders } from '@adia-ai/web-components/core/icons';
32
32
  * installIconLoaders({
33
33
  * regular: import.meta.glob(
34
- * '/node_modules/@phosphor-icons/core/assets/regular/{caret-right,moon,sun,x}.svg',
34
+ * '../node_modules/@phosphor-icons/core/assets/regular/{caret-right,moon,sun,x}.svg',
35
35
  * { query: '?raw', import: 'default', eager: true }
36
36
  * ),
37
37
  * });
38
38
  *
39
39
  * Brace-list globs are statically analyzed; only the named files are
40
- * emitted as chunks (or inlined when `eager: true`).
40
+ * emitted as chunks (or inlined when `eager: true`). Use the `../node_modules/...`
41
+ * form shown here, not `/node_modules/...` — see "Why the glob is entry-relative,
42
+ * not root-relative" below; the same reasoning applies to a hand-written
43
+ * scoped glob as to the auto-discovery globs this file installs by default.
44
+ *
45
+ * ## Why the glob is entry-relative, not root-relative
46
+ *
47
+ * `import.meta.glob` only accepts a small set of static forms (`/`-rooted,
48
+ * `./`, `../`, `**\/`, or a `#`-subpath with a literal `*`), and each resolves
49
+ * via a literal path join, not module resolution (confirmed against Vite 8's
50
+ * `toAbsoluteGlob`, gh#287). A `/`-rooted glob joins to Vite's CONFIGURED
51
+ * PROJECT ROOT — for a consumer app, that only reaches `@phosphor-icons/core`
52
+ * when it's ALSO hoisted to that exact root. Under pnpm's default
53
+ * (non-hoisted, symlinked) linking, a package you depend on only
54
+ * TRANSITIVELY — `@phosphor-icons/core` is a dependency of THIS package, not
55
+ * necessarily your app's — is linked only inside that package's own
56
+ * resolution scope, never flattened into your app's top-level `node_modules`.
57
+ * A root-rooted glob then silently matches zero files.
58
+ *
59
+ * A `../`-prefixed glob instead joins to THIS FILE'S OWN directory
60
+ * (`core/`), one level inside `@adia-ai/web-components` — so `../node_modules`
61
+ * reaches `@adia-ai/web-components`'s own local `node_modules`, which pnpm
62
+ * always populates with a package's own direct dependencies (a symlink into
63
+ * the pnpm store) regardless of whether your app also hoists them. This
64
+ * holds for the published package too — `core/icons-phosphor.js` ships at
65
+ * the same one-level depth (see `package.json`'s `files` field). Verified
66
+ * empirically against a real hoisting-gap install (a pnpm workspace where
67
+ * `@phosphor-icons/core` is declared only by a nested package, not the
68
+ * workspace root): the root-rooted form matches zero files; the
69
+ * entry-relative form matches the full set with real content.
70
+ *
71
+ * ## Vite dep-optimizer pre-bundling (gh#287, second independent failure mode)
72
+ *
73
+ * In DEV, Vite pre-bundles bare-specifier dependencies with esbuild
74
+ * (`optimizeDeps`) for faster cold starts, BEFORE its own plugin pipeline
75
+ * (which includes the glob transform) ever sees them. Confirmed empirically:
76
+ * when this package gets swept into that pre-bundling step, esbuild's scan
77
+ * transforms each `import.meta.glob(...)` call into a literal `Object.assign({})`
78
+ * — no exception, `hasViteGlob` stays `true`, but every weight's map is empty.
79
+ * This is indistinguishable from a genuine zero-match glob to the code below,
80
+ * so the same `totalIcons === 0` diagnostic fires — the fix differs, though:
81
+ * add this package to your Vite config's `optimizeDeps.exclude` so it's
82
+ * requested directly instead of pre-bundled (confirmed empirically to restore
83
+ * full auto-discovery with no code change):
84
+ *
85
+ * // vite.config.js
86
+ * export default defineConfig({
87
+ * optimizeDeps: { exclude: ['@adia-ai/web-components', '@adia-ai/web-modules'] },
88
+ * });
89
+ *
90
+ * If you can't control the Vite config (e.g. Astro, or another framework
91
+ * that manages it), fall back to the scoped, explicit-import recipe above —
92
+ * plain bare-specifier imports in your OWN first-party source are never
93
+ * swept into `optimizeDeps` in the first place.
41
94
  *
42
95
  * ## Vite glob detection
43
96
  *
@@ -57,12 +110,12 @@ let weightModules;
57
110
  let hasViteGlob = false;
58
111
  try {
59
112
  weightModules = {
60
- regular: import.meta.glob('/node_modules/@phosphor-icons/core/assets/regular/*.svg', { query: '?raw', import: 'default' }),
61
- thin: import.meta.glob('/node_modules/@phosphor-icons/core/assets/thin/*.svg', { query: '?raw', import: 'default' }),
62
- light: import.meta.glob('/node_modules/@phosphor-icons/core/assets/light/*.svg', { query: '?raw', import: 'default' }),
63
- bold: import.meta.glob('/node_modules/@phosphor-icons/core/assets/bold/*.svg', { query: '?raw', import: 'default' }),
64
- fill: import.meta.glob('/node_modules/@phosphor-icons/core/assets/fill/*.svg', { query: '?raw', import: 'default' }),
65
- duotone: import.meta.glob('/node_modules/@phosphor-icons/core/assets/duotone/*.svg', { query: '?raw', import: 'default' }),
113
+ regular: import.meta.glob('../node_modules/@phosphor-icons/core/assets/regular/*.svg', { query: '?raw', import: 'default' }),
114
+ thin: import.meta.glob('../node_modules/@phosphor-icons/core/assets/thin/*.svg', { query: '?raw', import: 'default' }),
115
+ light: import.meta.glob('../node_modules/@phosphor-icons/core/assets/light/*.svg', { query: '?raw', import: 'default' }),
116
+ bold: import.meta.glob('../node_modules/@phosphor-icons/core/assets/bold/*.svg', { query: '?raw', import: 'default' }),
117
+ fill: import.meta.glob('../node_modules/@phosphor-icons/core/assets/fill/*.svg', { query: '?raw', import: 'default' }),
118
+ duotone: import.meta.glob('../node_modules/@phosphor-icons/core/assets/duotone/*.svg', { query: '?raw', import: 'default' }),
66
119
  };
67
120
  hasViteGlob = true;
68
121
  } catch (err) {
@@ -85,28 +138,39 @@ if (hasViteGlob) {
85
138
  // FB-08: detect the silent-empty-glob case — `import.meta.glob` returns
86
139
  // `{}` when zero files match (it does NOT throw). The previous behavior
87
140
  // installed empty per-weight maps and the consumer saw an empty icon
88
- // registry with no diagnostic. This happens when the consumer's Vite
89
- // root sits below `node_modules`, or with pnpm/yarn-berry virtual paths,
90
- // or when this file runs from inside a published package directory
91
- // (its absolute-style glob is resolved relative to the Vite root, not
92
- // this file's directory).
141
+ // registry with no diagnostic. gh#287 names two independent causes, both
142
+ // producing this same empty-map symptom:
143
+ // 1. A packaging/bundler step re-nested this file at a different depth
144
+ // than it ships at (breaks the `../`-relative glob's one-level-inside
145
+ // assumption see "Why the glob is entry-relative" above), a Yarn
146
+ // Berry PnP install (virtual/zip filesystem), or @phosphor-icons/core
147
+ // missing entirely.
148
+ // 2. Vite's dev-server `optimizeDeps` pre-bundled this package (see
149
+ // "Vite dep-optimizer pre-bundling" above) — confirmed empirically to
150
+ // transform the glob into a literal empty object, no exception.
93
151
  const totalIcons = Object.values(weightModules).reduce(
94
152
  (sum, weight) => sum + Object.keys(weight).length, 0
95
153
  );
96
154
  if (totalIcons === 0) {
97
155
  console.warn(
98
156
  `[icons-phosphor] No Phosphor icons matched the auto-install glob ` +
99
- `\`/node_modules/@phosphor-icons/core/assets/<weight>/*.svg\`. ` +
100
- `This usually means your Vite root sits below node_modules, you use ` +
101
- `pnpm/yarn-berry virtual paths, or this module is loaded from a ` +
102
- `published package directory. Install loaders manually from your app entry:\n` +
157
+ `\`../node_modules/@phosphor-icons/core/assets/<weight>/*.svg\` (resolved ` +
158
+ `relative to this file's own directory hoisting-safe under pnpm, gh#287). ` +
159
+ `Two known causes: (1) this package got pre-bundled by Vite's dev-server ` +
160
+ `optimizeDeps, which empties the glob add it to optimizeDeps.exclude in ` +
161
+ `your Vite config:\n` +
162
+ ` optimizeDeps: { exclude: ['@adia-ai/web-components', '@adia-ai/web-modules'] }\n` +
163
+ `(2) @phosphor-icons/core is missing, or this file was re-nested to a ` +
164
+ `different depth by a packaging step (Yarn PnP, a custom bundler). If ` +
165
+ `neither fix applies, install loaders manually instead:\n` +
103
166
  ` import { installIconLoaders } from '@adia-ai/web-components/core/icons';\n` +
167
+ ` import caretRight from '@phosphor-icons/core/assets/regular/caret-right.svg?raw';\n` +
104
168
  ` installIconLoaders({\n` +
105
- ` regular: import.meta.glob(\n` +
106
- ` '../node_modules/@phosphor-icons/core/assets/regular/*.svg',\n` +
107
- ` { query: '?raw', import: 'default', eager: true }\n` +
108
- ` ),\n` +
109
- ` });`
169
+ ` regular: {\n` +
170
+ ` '/node_modules/@phosphor-icons/core/assets/regular/caret-right.svg': caretRight,\n` +
171
+ ` },\n` +
172
+ ` });\n` +
173
+ `See the JSDoc header of icons-phosphor.js for the full recipe.`
110
174
  );
111
175
  }
112
176
  installIconLoaders(weightModules);
package/core/icons.js CHANGED
@@ -33,17 +33,25 @@
33
33
  * import '@adia-ai/web-components/core/icons-phosphor';
34
34
  *
35
35
  * 3. **Scoped registration (recommended for production bundle-size
36
- * SLOs).** Skip `icons-phosphor.js` entirely and register only the
37
- * icons you actually use. `import.meta.glob` with a brace-list emits
38
- * one chunk per matched file — for a 4-icon app, that's ~4 chunks
39
- * instead of the ~9000 the full set would produce:
36
+ * SLOs, and for Vite dev-servers that pre-bundle this package via
37
+ * `optimizeDeps` see `icons-phosphor.js`'s JSDoc header, "Vite
38
+ * dep-optimizer pre-bundling", gh#287).** Skip `icons-phosphor.js`
39
+ * entirely and register only the icons you actually use, via plain
40
+ * bare-specifier imports in your OWN first-party source — never swept
41
+ * into `optimizeDeps`, unlike a glob written inside this package:
40
42
  *
41
43
  * import { installIconLoaders } from '@adia-ai/web-components/core/icons';
44
+ * import caretRight from '@phosphor-icons/core/assets/regular/caret-right.svg?raw';
45
+ * import caretUpDown from '@phosphor-icons/core/assets/regular/caret-up-down.svg?raw';
46
+ * import moon from '@phosphor-icons/core/assets/regular/moon.svg?raw';
47
+ * import sun from '@phosphor-icons/core/assets/regular/sun.svg?raw';
42
48
  * installIconLoaders({
43
- * regular: import.meta.glob(
44
- * '/node_modules/@phosphor-icons/core/assets/regular/{caret-right,caret-up-down,moon,sun}.svg',
45
- * { query: '?raw', import: 'default', eager: true }
46
- * ),
49
+ * regular: {
50
+ * '/node_modules/@phosphor-icons/core/assets/regular/caret-right.svg': caretRight,
51
+ * '/node_modules/@phosphor-icons/core/assets/regular/caret-up-down.svg': caretUpDown,
52
+ * '/node_modules/@phosphor-icons/core/assets/regular/moon.svg': moon,
53
+ * '/node_modules/@phosphor-icons/core/assets/regular/sun.svg': sun,
54
+ * },
47
55
  * });
48
56
  *
49
57
  * Or register single SVGs directly:
@@ -236,6 +244,12 @@ export function installIconLoaders(modules) {
236
244
  * import { installIconLoadersForRegistered } from '@adia-ai/web-components/core/icons';
237
245
  *
238
246
  * // Build the Phosphor loader map from your bundler (Vite, Webpack, etc.).
247
+ * // NOTE: this glob only matches under a hoisted install (npm/yarn classic,
248
+ * // or pnpm with @phosphor-icons/core hoisted to your project root). Plain
249
+ * // pnpm links a transitive dependency like this one inside its own
250
+ * // resolution scope only — the glob silently matches zero files there.
251
+ * // Use the plain bare-specifier import recipe in this file's own JSDoc
252
+ * // header (path 3, above) instead if that's your install. See gh#287.
239
253
  * const modules = import.meta.glob('/node_modules/@phosphor-icons/core/assets/regular/*.svg', { query: '?raw' });
240
254
  *
241
255
  * // Aggregate requiredIcons across all defined primitives + install.