@alacris/ui 0.4.1 → 0.4.3

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.
package/README.md CHANGED
@@ -46,10 +46,10 @@ The published package is plain ESM. Point an import map at a pinned CDN build of
46
46
  <script type="importmap">
47
47
  {
48
48
  "imports": {
49
- "@alacris/core": "https://cdn.jsdelivr.net/npm/@alacris/core@0.11.3/dist/alacris.js",
50
- "@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.4.1/src/index.js",
51
- "@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.4.1/src/theme/index.js",
52
- "@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.4.1/src/components/"
49
+ "@alacris/core": "https://cdn.jsdelivr.net/npm/@alacris/core@0.11.4/dist/alacris.js",
50
+ "@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.4.3/src/index.js",
51
+ "@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.4.3/src/theme/index.js",
52
+ "@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.4.3/src/components/"
53
53
  }
54
54
  }
55
55
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alacris/ui",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Themeable design system for Alacris — Material defaults, sixty-eight custom elements, ESM-only, no build step.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,7 +13,7 @@ export const base = css`
13
13
  font-synthesis: none;
14
14
  -webkit-tap-highlight-color: transparent;
15
15
  }
16
- :host([hidden]) { display: none; }
16
+ :host([hidden]) { display: none !important; }
17
17
  @media (prefers-reduced-motion: reduce) {
18
18
  *, *::before, *::after {
19
19
  transition-duration: 0.01ms;
@@ -146,7 +146,7 @@ define('ui-accordion-item', {
146
146
  <ui-icon class=${() => `chevron${expanded() ? ' open' : ''}`} name="expand-more"></ui-icon>
147
147
  </button>
148
148
  </h3>
149
- <div part="content" class="content" id="content" role="region" aria-labelledby="header"
149
+ <div part="content" class="content" id="content" role="region" aria-labelledby="header" ?inert=${() => !expanded()}
150
150
  ref=${(el) => { contentEl = el; el.hidden = !expanded.peek(); }}>
151
151
  <div class="body" part="body"><slot></slot></div>
152
152
  </div>`;
@@ -132,6 +132,7 @@ define('ui-alert', {
132
132
  const onDismiss = () => {
133
133
  if (dismissing) return;
134
134
  dismissing = true;
135
+ host.inert = true;
135
136
  const height = host.scrollHeight || 0;
136
137
  // Simulated DOMs (and display:none hosts) measure 0 — skip straight out.
137
138
  if (!height) { host.emit('dismiss'); return; }
@@ -276,8 +276,16 @@ define('ui-autocomplete', {
276
276
  open.set(false);
277
277
  };
278
278
 
279
+ let isComposing = false;
280
+ const onCompositionStart = () => { isComposing = true; };
281
+ const onCompositionEnd = (e) => {
282
+ isComposing = false;
283
+ onInput(e);
284
+ };
285
+
279
286
  const onInput = (e) => {
280
287
  e.stopPropagation();
288
+ if (isComposing) return;
281
289
  query.set(e.target.value);
282
290
  open.set(true);
283
291
  host.emit('input', { value: e.target.value });
@@ -358,6 +366,8 @@ define('ui-autocomplete', {
358
366
  .value=${query}
359
367
  placeholder=${() => placeholder() || null}
360
368
  ?disabled=${disabled} ?required=${required}
369
+ @compositionstart=${onCompositionStart}
370
+ @compositionend=${onCompositionEnd}
361
371
  @input=${onInput} @change=${(e) => e.stopPropagation()} @keydown=${onKeydown}
362
372
  @focus=${onFocus} @blur=${onBlur}>
363
373
  <ui-icon class="arrow" name="arrow-drop-down"></ui-icon>
@@ -183,6 +183,7 @@ define('ui-chip', {
183
183
  const dismiss = (e) => {
184
184
  e.stopPropagation();
185
185
  if (disabled()) return;
186
+ host.inert = true;
186
187
  const w = host.getBoundingClientRect().width;
187
188
  const anim = animate(host, [
188
189
  { inlineSize: `${w}px`, opacity: 1 },
@@ -111,19 +111,22 @@ define('ui-dialog', {
111
111
  };
112
112
 
113
113
  // Trap focus + lock scroll exactly while open; presence handles the DOM.
114
+ let prevActive = null;
114
115
  effect(() => {
115
116
  if (open()) {
117
+ prevActive = document.activeElement;
116
118
  document.addEventListener('keydown', onDocKeydown, true);
117
119
  unlock = scrollLock();
118
120
  // The surface renders synchronously with the signal write, but wait a
119
121
  // microtask so slotted content is distributed before we look for it.
120
122
  queueMicrotask(() => {
121
- if (open() && !releaseTrap) releaseTrap = focusTrap(host);
123
+ if (open() && !releaseTrap) releaseTrap = focusTrap(host, { restore: prevActive });
122
124
  });
123
125
  } else {
124
126
  document.removeEventListener('keydown', onDocKeydown, true);
125
127
  releaseTrap?.();
126
128
  releaseTrap = null;
129
+ prevActive = null;
127
130
  unlock?.();
128
131
  unlock = null;
129
132
  if (surfaceEl?.isConnected) {
@@ -105,17 +105,20 @@ define('ui-drawer', {
105
105
  };
106
106
 
107
107
  // Trap focus + lock scroll exactly while the modal drawer is open.
108
+ let prevActive = null;
108
109
  effect(() => {
109
110
  if (open() && variant() === 'modal') {
111
+ prevActive = document.activeElement;
110
112
  document.addEventListener('keydown', onDocKeydown, true);
111
113
  if (!unlock) unlock = scrollLock();
112
114
  queueMicrotask(() => {
113
- if (open.peek() && !releaseTrap) releaseTrap = focusTrap(host);
115
+ if (open.peek() && !releaseTrap) releaseTrap = focusTrap(host, { restore: prevActive });
114
116
  });
115
117
  } else {
116
118
  document.removeEventListener('keydown', onDocKeydown, true);
117
119
  releaseTrap?.();
118
120
  releaseTrap = null;
121
+ prevActive = null;
119
122
  unlock?.();
120
123
  unlock = null;
121
124
  }
@@ -150,7 +153,7 @@ define('ui-drawer', {
150
153
  return html`
151
154
  ${() =>
152
155
  variant() === 'standard'
153
- ? html`<aside class=${() => `std ${anchor()}${open() ? ' open' : ''}`} part="surface"
156
+ ? html`<aside class=${() => `std ${anchor()}${open() ? ' open' : ''}`} ?inert=${() => !open()} part="surface"
154
157
  aria-label=${() => label() || 'Navigation'}>
155
158
  <div class="inner"><slot></slot></div>
156
159
  </aside>`
@@ -126,17 +126,20 @@ define('ui-sheet', {
126
126
  if (e.key === 'Escape') requestClose('esc');
127
127
  };
128
128
 
129
+ let prevActive = null;
129
130
  effect(() => {
130
131
  if (open() && variant() !== 'standard') {
132
+ prevActive = document.activeElement;
131
133
  document.addEventListener('keydown', onDocKeydown, true);
132
134
  unlock = scrollLock();
133
135
  queueMicrotask(() => {
134
- if (open() && !releaseTrap) releaseTrap = focusTrap(host);
136
+ if (open() && !releaseTrap) releaseTrap = focusTrap(host, { restore: prevActive });
135
137
  });
136
138
  } else {
137
139
  document.removeEventListener('keydown', onDocKeydown, true);
138
140
  releaseTrap?.();
139
141
  releaseTrap = null;
142
+ prevActive = null;
140
143
  unlock?.();
141
144
  unlock = null;
142
145
  if (surfaceEl?.isConnected) {
@@ -183,7 +186,7 @@ define('ui-sheet', {
183
186
  return html`
184
187
  ${() =>
185
188
  variant() === 'standard'
186
- ? html`<div class=${() => `std${open() ? ' open' : ''}`} part="surface" role="region"
189
+ ? html`<div class=${() => `std${open() ? ' open' : ''}`} ?inert=${() => !open()} part="surface" role="region"
187
190
  aria-label=${() => label() || 'Sheet'}>
188
191
  <div class="std-inner">
189
192
  <div class="handle" part="handle" aria-hidden="true"></div>
@@ -130,17 +130,20 @@ define('ui-side-sheet', {
130
130
  if (e.key === 'Escape') requestClose('esc');
131
131
  };
132
132
 
133
+ let prevActive = null;
133
134
  effect(() => {
134
135
  if (open() && variant() !== 'standard') {
136
+ prevActive = document.activeElement;
135
137
  document.addEventListener('keydown', onDocKeydown, true);
136
138
  unlock = scrollLock();
137
139
  queueMicrotask(() => {
138
- if (open() && !releaseTrap) releaseTrap = focusTrap(host);
140
+ if (open() && !releaseTrap) releaseTrap = focusTrap(host, { restore: prevActive });
139
141
  });
140
142
  } else {
141
143
  document.removeEventListener('keydown', onDocKeydown, true);
142
144
  releaseTrap?.();
143
145
  releaseTrap = null;
146
+ prevActive = null;
144
147
  unlock?.();
145
148
  unlock = null;
146
149
  }
@@ -192,7 +195,7 @@ define('ui-side-sheet', {
192
195
  return html`
193
196
  ${() =>
194
197
  variant() === 'standard'
195
- ? html`<aside class=${() => `std ${anchor()}${open() ? ' open' : ''}`} part="surface"
198
+ ? html`<aside class=${() => `std ${anchor()}${open() ? ' open' : ''}`} ?inert=${() => !open()} part="surface"
196
199
  role="region"
197
200
  aria-labelledby=${() => (hasHeadline() ? 'headline' : null)}
198
201
  aria-label=${() => (hasHeadline() ? null : (label() || 'Side sheet'))}>
@@ -144,7 +144,7 @@ define('ui-switch', {
144
144
  ${() => (icons() ? html`<ui-icon name=${() => (checked() ? 'check' : 'close')}></ui-icon>` : null)}
145
145
  </span>
146
146
  </button>
147
- ${() => (label() ? html`<span id="label">${label}</span>` : null)}
147
+ ${() => (label() ? html`<span id="label" part="label">${label}</span>` : html`<slot></slot>`)}
148
148
  </label>`;
149
149
  },
150
150
  });
@@ -320,7 +320,15 @@ define('ui-text-field', {
320
320
  hasLeading() && 'with-leading', type() === 'textarea' && 'multiline',
321
321
  type() === 'number' && 'numeric'].filter(Boolean).join(' '));
322
322
 
323
+ let isComposing = false;
324
+ const onCompositionStart = () => { isComposing = true; };
325
+ const onCompositionEnd = (e) => {
326
+ isComposing = false;
327
+ onInput(e);
328
+ };
329
+
323
330
  const onInput = (e) => {
331
+ if (isComposing) return;
324
332
  value.set(e.target.value);
325
333
  host.emit('input', { value: value() });
326
334
  };
@@ -350,6 +358,8 @@ define('ui-text-field', {
350
358
  spellcheck=${attr(spellcheck)}
351
359
  inputmode=${attr(inputmode)}
352
360
  enterkeyhint=${attr(enterkeyhint)}
361
+ @compositionstart=${onCompositionStart}
362
+ @compositionend=${onCompositionEnd}
353
363
  @input=${onInput} @change=${commit}
354
364
  @focus=${() => focused.set(true)} @blur=${() => focused.set(false)}></textarea></span>`
355
365
  : html`<input part="input" ref=${(el) => (input = el)}
@@ -364,6 +374,8 @@ define('ui-text-field', {
364
374
  spellcheck=${attr(spellcheck)}
365
375
  inputmode=${attr(inputmode)}
366
376
  enterkeyhint=${attr(enterkeyhint)}
377
+ @compositionstart=${onCompositionStart}
378
+ @compositionend=${onCompositionEnd}
367
379
  @input=${onInput} @change=${commit}
368
380
  @focus=${() => focused.set(true)} @blur=${() => focused.set(false)}>`)}
369
381
  ${() => (clearable() && value() !== '' && !disabled()
package/src/util/focus.js CHANGED
@@ -32,8 +32,8 @@ export function focusables(host) {
32
32
  *
33
33
  * const release = focusTrap(host, { initial: shadowCloseButton });
34
34
  */
35
- export function focusTrap(host, { initial } = {}) {
36
- const previous = document.activeElement;
35
+ export function focusTrap(host, { initial, restore } = {}) {
36
+ const previous = restore || document.activeElement;
37
37
 
38
38
  const first = initial || focusables(host)[0] || host;
39
39
  first.focus?.();