@adia-ai/web-components 0.0.7 → 0.0.8

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.
@@ -79,7 +79,9 @@ class AdiaPopover extends AdiaElement {
79
79
  const content = this.#content ?? this.querySelector('[slot="content"]');
80
80
  if (!trigger || !content) return;
81
81
 
82
- if (!content.matches(':popover-open')) content.showPopover?.();
82
+ if (!content.matches(':popover-open')) {
83
+ try { content.showPopover(); } catch { /* popover API unavailable — anchor positioning still runs */ }
84
+ }
83
85
 
84
86
  this.#anchorCleanup?.();
85
87
  this.#anchorCleanup = anchorPopover(trigger, content, {
@@ -102,7 +104,9 @@ class AdiaPopover extends AdiaElement {
102
104
  this.#anchorCleanup = null;
103
105
 
104
106
  const content = this.#content ?? this.querySelector('[slot="content"]');
105
- if (content?.matches(':popover-open')) content.hidePopover?.();
107
+ if (content?.matches(':popover-open')) {
108
+ try { content.hidePopover(); } catch { /* popover API unavailable */ }
109
+ }
106
110
 
107
111
  if (this.#rafId != null) {
108
112
  cancelAnimationFrame(this.#rafId);
package/core/icons.js CHANGED
@@ -51,19 +51,59 @@ export function listIcons() {
51
51
 
52
52
  // ── Async loader ──
53
53
 
54
- // Vite resolves each glob at build time into a map of path → module.
55
- // One glob per weight so the bundler can tree-shake what isn't referenced.
56
- // In non-Vite environments (e.g. plain static serving) `import.meta.glob`
57
- // is undefined fall back to empty maps so consumers using registerIcon()
58
- // still work.
59
- const weightModules = typeof import.meta.glob === 'function' ? {
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' }),
66
- } : { regular: {}, thin: {}, light: {}, bold: {}, fill: {}, duotone: {} };
54
+ // Vite dev: `import.meta.glob(...)` is a build-time macro Vite's AST
55
+ // transform replaces each call with a literal `{ path: loader }` object
56
+ // before the module reaches the browser. `import.meta.glob` itself is NOT
57
+ // exposed as a runtime property, so `typeof import.meta.glob` is always
58
+ // `'undefined'` at run time — don't try to feature-detect with it.
59
+ //
60
+ // Production static serving (no Vite): the glob calls remain in the
61
+ // source and `import.meta.glob` is undefined, so calling it throws a
62
+ // TypeError. We wrap the assignment in try/catch to handle both worlds:
63
+ // - Vite dev Vite rewrote the calls to literals → try block succeeds.
64
+ // - Static → actual runtime call throws → catch block falls back to
65
+ // EMPTY_WEIGHTS; the manifest branch below fills it in.
66
+ const EMPTY_WEIGHTS = { regular: {}, thin: {}, light: {}, bold: {}, fill: {}, duotone: {} };
67
+
68
+ // `let` so the background manifest load (non-Vite branch) can swap in a
69
+ // real map once `icons-manifest.js` resolves. `resolveLoader` reads
70
+ // through this each call, so new icons become visible after manifest load.
71
+ let weightModules;
72
+ let hasViteGlob = false;
73
+ try {
74
+ weightModules = {
75
+ regular: import.meta.glob('/node_modules/@phosphor-icons/core/assets/regular/*.svg', { query: '?raw', import: 'default' }),
76
+ thin: import.meta.glob('/node_modules/@phosphor-icons/core/assets/thin/*.svg', { query: '?raw', import: 'default' }),
77
+ light: import.meta.glob('/node_modules/@phosphor-icons/core/assets/light/*.svg', { query: '?raw', import: 'default' }),
78
+ bold: import.meta.glob('/node_modules/@phosphor-icons/core/assets/bold/*.svg', { query: '?raw', import: 'default' }),
79
+ fill: import.meta.glob('/node_modules/@phosphor-icons/core/assets/fill/*.svg', { query: '?raw', import: 'default' }),
80
+ duotone: import.meta.glob('/node_modules/@phosphor-icons/core/assets/duotone/*.svg', { query: '?raw', import: 'default' }),
81
+ };
82
+ hasViteGlob = true;
83
+ } catch {
84
+ weightModules = EMPTY_WEIGHTS;
85
+ }
86
+
87
+ // Non-Vite environments (plain static serving): fetch the build-time
88
+ // manifest in the background and rebuild `weightModules` with lazy
89
+ // fetch-based loaders. No top-level await — the module finishes loading
90
+ // immediately; icons appear once the one-shot manifest fetch resolves.
91
+ if (!hasViteGlob) {
92
+ // Specifier is hidden behind a variable so Vite's static analysis
93
+ // never tries to pre-resolve it in dev (where the file doesn't exist).
94
+ const manifestSpec = './icons-manifest.js';
95
+ import(/* @vite-ignore */ manifestSpec).then(({ default: manifest }) => {
96
+ weightModules = Object.fromEntries(Object.entries(manifest).map(([weight, names]) => {
97
+ const prefix = `/node_modules/@phosphor-icons/core/assets/${weight}/`;
98
+ const entries = names.map(name => {
99
+ const path = prefix + name;
100
+ const loader = () => fetch(path).then(r => r.ok ? r.text() : Promise.reject(new Error(`icon fetch failed: ${path}`)));
101
+ return [path, loader];
102
+ });
103
+ return [weight, Object.fromEntries(entries)];
104
+ }));
105
+ }).catch(() => { /* keep EMPTY_WEIGHTS */ });
106
+ }
67
107
 
68
108
  /**
69
109
  * Phosphor filename convention: `star.svg` for regular, `star-fill.svg` for
package/core/template.js CHANGED
@@ -68,12 +68,21 @@ export function stamp(result, container) {
68
68
  update(inst.p, result.values);
69
69
  }
70
70
 
71
+ // Safari 14.0 lacks ChildNode.replaceChildren. Fallback manually removes
72
+ // existing children, then appends the new content. Runs once per call;
73
+ // cheap enough to be unconditional.
74
+ function replaceChildren(container, ...nodes) {
75
+ if (container.replaceChildren) { container.replaceChildren(...nodes); return; }
76
+ while (container.firstChild) container.removeChild(container.firstChild);
77
+ for (const n of nodes) container.appendChild(n);
78
+ }
79
+
71
80
  function mount(result, container) {
72
81
  const { strings } = result;
73
82
  const tpl = getTemplate(strings);
74
83
  const f = tpl.content.cloneNode(true);
75
84
  const parts = scan(f, result.values.length);
76
- container.replaceChildren(f);
85
+ replaceChildren(container, f);
77
86
  return { s: strings, p: parts };
78
87
  }
79
88
 
@@ -138,7 +147,7 @@ function applyValue(p, v) {
138
147
  stamp(v, wrap(p));
139
148
  } else if (Array.isArray(v)) {
140
149
  const c = wrap(p);
141
- c.replaceChildren();
150
+ replaceChildren(c);
142
151
  for (const item of v) {
143
152
  if (isResult(item)) {
144
153
  const el = document.createElement('span');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-utils.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -6,6 +6,10 @@ export const intersectionObserver = defineTrait({
6
6
  events: ['element-visible', 'element-hidden'],
7
7
  config: ['data-intersection-threshold'],
8
8
  setup({ host }) {
9
+ // No-op in environments lacking IntersectionObserver (older Safari,
10
+ // happy-dom without polyfill). Trait becomes inert rather than throwing.
11
+ if (typeof IntersectionObserver === 'undefined') return () => {};
12
+
9
13
  const threshold = parseFloat(host.getAttribute('data-intersection-threshold')) || 0;
10
14
 
11
15
  const observer = new IntersectionObserver((entries) => {
@@ -6,6 +6,11 @@ export const resizeObserver = defineTrait({
6
6
  events: ['element-resize'],
7
7
  config: [],
8
8
  setup({ host }) {
9
+ // Guard for older browsers + non-DOM test environments (happy-dom, jsdom
10
+ // without polyfill). The trait becomes a no-op rather than throwing on
11
+ // the `new ResizeObserver` call.
12
+ if (typeof ResizeObserver === 'undefined') return () => {};
13
+
9
14
  const observer = new ResizeObserver((entries) => {
10
15
  for (const entry of entries) {
11
16
  const { width, height } = entry.contentRect;