@alacris/ui 0.5.0 → 0.5.2

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.4/dist/alacris.js",
50
- "@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.0/src/index.js",
51
- "@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.0/src/theme/index.js",
52
- "@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.0/src/components/"
49
+ "@alacris/core": "https://cdn.jsdelivr.net/npm/@alacris/core@0.11.5/dist/alacris.js",
50
+ "@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.2/src/index.js",
51
+ "@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.2/src/theme/index.js",
52
+ "@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.2/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.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Themeable design system for Alacris — Material defaults, sixty-nine custom elements, ESM-only, no build step.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -36,7 +36,7 @@ import { sys } from '../tokens/sys.js';
36
36
  import { base } from './base.js';
37
37
  import { formBind } from '../util/form.js';
38
38
  import { presence } from '../motion/presence.js';
39
- import { animate, fx } from '../motion/animate.js';
39
+ import { animate, fx, releaseFill } from '../motion/animate.js';
40
40
  import { autoUpdate } from '../util/position.js';
41
41
  import { escapeLayer } from '../util/keys.js';
42
42
  import { popupEvent } from '../util/popup.js';
@@ -274,17 +274,27 @@ const styles = css`
274
274
  .overlay {
275
275
  position: fixed;
276
276
  inset: 0;
277
+ inline-size: 100vw;
278
+ block-size: 100vh;
279
+ max-inline-size: 100vw;
280
+ max-block-size: 100vh;
281
+ margin: 0;
282
+ padding: 0;
283
+ border: none;
284
+ background: transparent;
285
+ overflow: visible;
277
286
  z-index: ${sys.z.popup};
278
287
  display: grid;
279
288
  place-items: center;
280
289
  }
281
- .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
282
- @keyframes ui-date-picker-in {
283
- from { transform: scale(0.8); }
284
- to { transform: none; }
290
+ .overlay:popover-open {
291
+ display: grid;
285
292
  }
293
+ .overlay::backdrop {
294
+ display: none;
295
+ }
296
+ .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
286
297
  .modal-surface {
287
- animation: ui-date-picker-in ${sys.duration.medium2} ${sys.easing.emphasizedDecelerate};
288
298
  position: relative;
289
299
  display: flex;
290
300
  flex-direction: column;
@@ -714,12 +724,25 @@ define('ui-date-picker', {
714
724
  ${calGrid}
715
725
  </div>`;
716
726
 
727
+ const modalSurfaceRef = (el) => {
728
+ modalSurfaceEl = el;
729
+ releaseFill(animate(el, fx.scaleIn, { duration: 'medium2', easing: 'emphasizedDecelerate' }));
730
+ };
731
+
732
+ const modalOverlayRef = (el) => {
733
+ queueMicrotask(() => {
734
+ try {
735
+ if (el.isConnected) el.showPopover?.();
736
+ } catch {}
737
+ });
738
+ };
739
+
717
740
  const modalView = () => html`
718
- <div class="overlay">
741
+ <div class="overlay" popover="manual" ref=${modalOverlayRef}>
719
742
  <div class="scrim" aria-hidden="true" @click=${closePanel}></div>
720
743
  <div class="modal-surface" part="panel" role="dialog" aria-modal="true"
721
744
  aria-label=${() => label() || 'Choose date'}
722
- ref=${(el) => (modalSurfaceEl = el)}>
745
+ ref=${modalSurfaceRef}>
723
746
  <div class="headline">${() => (range() ? 'Select dates' : 'Select date')}</div>
724
747
  <div class="picked">${pickedLabel}</div>
725
748
  <div class="cal-header">
@@ -29,7 +29,7 @@ import { define, html, css, vars, effect, onCleanup, signal } from '@alacris/cor
29
29
  import { sys } from '../tokens/sys.js';
30
30
  import { base } from './base.js';
31
31
  import { presence } from '../motion/presence.js';
32
- import { animate, fx } from '../motion/animate.js';
32
+ import { animate, fx, releaseFill } from '../motion/animate.js';
33
33
  import { focusTrap, scrollLock } from '../util/focus.js';
34
34
 
35
35
  const t = vars('ui-dialog', {
@@ -46,17 +46,27 @@ const styles = css`
46
46
  .overlay {
47
47
  position: fixed;
48
48
  inset: 0;
49
+ inline-size: 100vw;
50
+ block-size: 100vh;
51
+ max-inline-size: 100vw;
52
+ max-block-size: 100vh;
53
+ margin: 0;
54
+ padding: 0;
55
+ border: none;
56
+ background: transparent;
57
+ overflow: visible;
49
58
  z-index: ${sys.z.modal};
50
59
  display: grid;
51
60
  place-items: center;
52
61
  }
53
- .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
54
- @keyframes ui-dialog-in {
55
- from { transform: scale(0.8); }
56
- to { transform: none; }
62
+ .overlay:popover-open {
63
+ display: grid;
57
64
  }
65
+ .overlay::backdrop {
66
+ display: none;
67
+ }
68
+ .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
58
69
  .surface {
59
- animation: ui-dialog-in ${sys.duration.medium2} ${sys.easing.emphasizedDecelerate};
60
70
  position: relative;
61
71
  display: flex;
62
72
  flex-direction: column;
@@ -146,13 +156,26 @@ define('ui-dialog', {
146
156
  sync();
147
157
  };
148
158
 
159
+ const surfaceRef = (el) => {
160
+ surfaceEl = el;
161
+ releaseFill(animate(el, fx.scaleIn, { duration: 'medium2', easing: 'emphasizedDecelerate' }));
162
+ };
163
+
164
+ const overlayRef = (el) => {
165
+ queueMicrotask(() => {
166
+ try {
167
+ if (el.isConnected) el.showPopover?.();
168
+ } catch {}
169
+ });
170
+ };
171
+
149
172
  const view = () => html`
150
- <div class="overlay">
173
+ <div class="overlay" popover="manual" ref=${overlayRef}>
151
174
  <div class="scrim" part="scrim" aria-hidden="true" @click=${() => requestClose('scrim')}></div>
152
175
  <div class="surface" part="surface" role="dialog" aria-modal="true"
153
176
  aria-labelledby=${() => (hasHeadline() ? 'headline' : null)}
154
177
  aria-label=${() => (hasHeadline() ? null : (label() || 'Dialog'))}
155
- tabindex="-1" ref=${(el) => (surfaceEl = el)}>
178
+ tabindex="-1" ref=${surfaceRef}>
156
179
  <div class="headline" part="headline" id="headline"
157
180
  ref=${(el) => hasSlot(el.querySelector('slot'), (has) => {
158
181
  hasHeadline.set(has);
@@ -47,7 +47,23 @@ const styles = css`
47
47
  .overlay {
48
48
  position: fixed;
49
49
  inset: 0;
50
+ inline-size: 100vw;
51
+ block-size: 100vh;
52
+ max-inline-size: 100vw;
53
+ max-block-size: 100vh;
54
+ margin: 0;
55
+ padding: 0;
56
+ border: none;
57
+ background: transparent;
58
+ overflow: visible;
50
59
  z-index: ${sys.z.drawer};
60
+ display: block;
61
+ }
62
+ .overlay:popover-open {
63
+ display: block;
64
+ }
65
+ .overlay::backdrop {
66
+ display: none;
51
67
  }
52
68
  .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
53
69
  .surface {
@@ -116,9 +132,11 @@ define('ui-drawer', {
116
132
  };
117
133
 
118
134
  // Trap focus + lock scroll exactly while the modal drawer is open.
135
+ let closingViaSwipe = false;
119
136
  let prevActive = null;
120
137
  effect(() => {
121
138
  if (open() && variant() === 'modal') {
139
+ closingViaSwipe = false;
122
140
  prevActive = document.activeElement;
123
141
  document.addEventListener('keydown', onDocKeydown, true);
124
142
  if (!unlock) unlock = scrollLock();
@@ -143,7 +161,7 @@ define('ui-drawer', {
143
161
 
144
162
  // The panel slides out while the presence overlay (scrim included) fades.
145
163
  effect(() => {
146
- if (!open() && surfaceEl?.isConnected) {
164
+ if (!open() && surfaceEl?.isConnected && !closingViaSwipe) {
147
165
  animate(surfaceEl, slideOut(), { duration: 'short4', easing: 'emphasizedAccelerate' });
148
166
  }
149
167
  });
@@ -161,6 +179,7 @@ define('ui-drawer', {
161
179
  return true;
162
180
  },
163
181
  onStart() {
182
+ el.getAnimations?.()?.forEach((a) => a.cancel());
164
183
  el.style.transition = 'none';
165
184
  },
166
185
  onMove({ dx }) {
@@ -183,29 +202,34 @@ define('ui-drawer', {
183
202
  const shouldDismiss = !cancelled && dismissDirection;
184
203
 
185
204
  if (shouldDismiss) {
205
+ closingViaSwipe = true;
186
206
  const targetTransform = isEnd ? 'translateX(100%)' : 'translateX(-100%)';
187
207
  const remaining = Math.max(0, w - Math.abs(dx));
188
208
  const ms = Math.min(300, Math.max(120, Math.round(remaining / (Math.max(Math.abs(vx), 0.8)))));
189
209
  if (scrimEl) {
190
210
  animate(scrimEl, fx.fadeOut, { duration: ms, easing: 'emphasizedAccelerate' });
191
211
  }
192
- const anim = animate(el, [
212
+ animate(el, [
193
213
  { transform: el.style.transform || `translateX(${dx}px)` },
194
214
  { transform: targetTransform },
195
- ], { duration: ms, easing: 'emphasizedAccelerate' });
196
- anim.finished.then(() => {
197
- requestClose('swipe');
198
- });
215
+ ], { duration: ms, easing: 'emphasizedAccelerate', fill: 'forwards' });
216
+ requestClose('swipe');
199
217
  } else {
200
218
  if (scrimEl) {
201
- animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
219
+ const scrimSnap = animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
202
220
  duration: 'short4', easing: 'emphasizedDecelerate',
203
- }).finished.then(() => { if (scrimEl) scrimEl.style.opacity = ''; });
221
+ });
222
+ scrimSnap.finished.then(() => {
223
+ try { scrimSnap.cancel(); } catch {}
224
+ if (scrimEl) scrimEl.style.opacity = '';
225
+ });
204
226
  }
205
- animate(el, [
227
+ const snapAnim = animate(el, [
206
228
  { transform: el.style.transform || `translateX(${dx}px)` },
207
229
  { transform: 'translateX(0)' },
208
- ], { duration: 'short4', easing: 'emphasizedDecelerate' }).finished.then(() => {
230
+ ], { duration: 'short4', easing: 'emphasizedDecelerate' });
231
+ snapAnim.finished.then(() => {
232
+ try { snapAnim.cancel(); } catch {}
209
233
  if (el) el.style.transform = '';
210
234
  });
211
235
  }
@@ -213,8 +237,16 @@ define('ui-drawer', {
213
237
  });
214
238
  };
215
239
 
240
+ const overlayRef = (el) => {
241
+ queueMicrotask(() => {
242
+ try {
243
+ if (el.isConnected) el.showPopover?.();
244
+ } catch {}
245
+ });
246
+ };
247
+
216
248
  const overlay = () => html`
217
- <div class="overlay">
249
+ <div class="overlay" popover="manual" ref=${overlayRef}>
218
250
  <div class="scrim" part="scrim" aria-hidden="true"
219
251
  ref=${(el) => { scrimEl = el; }}
220
252
  @click=${() => requestClose('scrim')}></div>
@@ -48,11 +48,26 @@ const styles = css`
48
48
  .overlay {
49
49
  position: fixed;
50
50
  inset: 0;
51
+ inline-size: 100vw;
52
+ block-size: 100vh;
53
+ max-inline-size: 100vw;
54
+ max-block-size: 100vh;
55
+ margin: 0;
56
+ padding: 0;
57
+ border: none;
58
+ background: transparent;
59
+ overflow: visible;
51
60
  z-index: ${sys.z.modal};
52
61
  display: flex;
53
62
  align-items: flex-end;
54
63
  justify-content: center;
55
64
  }
65
+ .overlay:popover-open {
66
+ display: flex;
67
+ }
68
+ .overlay::backdrop {
69
+ display: none;
70
+ }
56
71
  .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
57
72
  .surface {
58
73
  position: relative;
@@ -134,9 +149,11 @@ define('ui-sheet', {
134
149
  if (e.key === 'Escape') requestClose('esc');
135
150
  };
136
151
 
152
+ let closingViaSwipe = false;
137
153
  let prevActive = null;
138
154
  effect(() => {
139
155
  if (open() && variant() !== 'standard') {
156
+ closingViaSwipe = false;
140
157
  prevActive = document.activeElement;
141
158
  document.addEventListener('keydown', onDocKeydown, true);
142
159
  unlock = scrollLock();
@@ -150,7 +167,7 @@ define('ui-sheet', {
150
167
  prevActive = null;
151
168
  unlock?.();
152
169
  unlock = null;
153
- if (surfaceEl?.isConnected) {
170
+ if (surfaceEl?.isConnected && !closingViaSwipe) {
154
171
  animate(surfaceEl, fx.sheetOut, { duration: 'short4', easing: 'emphasizedAccelerate' });
155
172
  }
156
173
  }
@@ -185,6 +202,7 @@ define('ui-sheet', {
185
202
  return true;
186
203
  },
187
204
  onStart() {
205
+ el.getAnimations?.()?.forEach((a) => a.cancel());
188
206
  el.style.transition = 'none';
189
207
  },
190
208
  onMove({ dy }) {
@@ -204,28 +222,33 @@ define('ui-sheet', {
204
222
  const shouldDismiss = !cancelled && (vy > 0.4 || dy > h * 0.35);
205
223
 
206
224
  if (shouldDismiss) {
225
+ closingViaSwipe = true;
207
226
  const remaining = Math.max(0, h - dy);
208
227
  const ms = Math.min(300, Math.max(120, Math.round(remaining / (Math.max(vy, 0.8)))));
209
228
  if (scrimEl) {
210
229
  animate(scrimEl, fx.fadeOut, { duration: ms, easing: 'emphasizedAccelerate' });
211
230
  }
212
- const anim = animate(el, [
231
+ animate(el, [
213
232
  { transform: el.style.transform || `translateY(${dy}px)` },
214
233
  { transform: 'translateY(100%)' },
215
- ], { duration: ms, easing: 'emphasizedAccelerate' });
216
- anim.finished.then(() => {
217
- requestClose('swipe');
218
- });
234
+ ], { duration: ms, easing: 'emphasizedAccelerate', fill: 'forwards' });
235
+ requestClose('swipe');
219
236
  } else {
220
237
  if (scrimEl) {
221
- animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
238
+ const scrimSnap = animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
222
239
  duration: 'short4', easing: 'emphasizedDecelerate',
223
- }).finished.then(() => { if (scrimEl) scrimEl.style.opacity = ''; });
240
+ });
241
+ scrimSnap.finished.then(() => {
242
+ try { scrimSnap.cancel(); } catch {}
243
+ if (scrimEl) scrimEl.style.opacity = '';
244
+ });
224
245
  }
225
- animate(el, [
246
+ const snapAnim = animate(el, [
226
247
  { transform: el.style.transform || `translateY(${dy}px)` },
227
248
  { transform: 'translateY(0)' },
228
- ], { duration: 'short4', easing: 'emphasizedDecelerate' }).finished.then(() => {
249
+ ], { duration: 'short4', easing: 'emphasizedDecelerate' });
250
+ snapAnim.finished.then(() => {
251
+ try { snapAnim.cancel(); } catch {}
229
252
  if (el) el.style.transform = '';
230
253
  });
231
254
  }
@@ -233,8 +256,16 @@ define('ui-sheet', {
233
256
  });
234
257
  };
235
258
 
259
+ const overlayRef = (el) => {
260
+ queueMicrotask(() => {
261
+ try {
262
+ if (el.isConnected) el.showPopover?.();
263
+ } catch {}
264
+ });
265
+ };
266
+
236
267
  const modalView = () => html`
237
- <div class="overlay">
268
+ <div class="overlay" popover="manual" ref=${overlayRef}>
238
269
  <div class="scrim" part="scrim" aria-hidden="true"
239
270
  ref=${(el) => { scrimEl = el; }}
240
271
  @click=${() => requestClose('scrim')}></div>
@@ -48,7 +48,23 @@ const styles = css`
48
48
  .overlay {
49
49
  position: fixed;
50
50
  inset: 0;
51
+ inline-size: 100vw;
52
+ block-size: 100vh;
53
+ max-inline-size: 100vw;
54
+ max-block-size: 100vh;
55
+ margin: 0;
56
+ padding: 0;
57
+ border: none;
58
+ background: transparent;
59
+ overflow: visible;
51
60
  z-index: ${sys.z.modal};
61
+ display: block;
62
+ }
63
+ .overlay:popover-open {
64
+ display: block;
65
+ }
66
+ .overlay::backdrop {
67
+ display: none;
52
68
  }
53
69
  .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
54
70
  .surface {
@@ -136,9 +152,11 @@ define('ui-side-sheet', {
136
152
  if (e.key === 'Escape') requestClose('esc');
137
153
  };
138
154
 
155
+ let closingViaSwipe = false;
139
156
  let prevActive = null;
140
157
  effect(() => {
141
158
  if (open() && variant() !== 'standard') {
159
+ closingViaSwipe = false;
142
160
  prevActive = document.activeElement;
143
161
  document.addEventListener('keydown', onDocKeydown, true);
144
162
  unlock = scrollLock();
@@ -155,7 +173,7 @@ define('ui-side-sheet', {
155
173
  }
156
174
  });
157
175
  effect(() => {
158
- if (!open() && surfaceEl?.isConnected) {
176
+ if (!open() && surfaceEl?.isConnected && !closingViaSwipe) {
159
177
  animate(surfaceEl, slideOut(), { duration: 'short4', easing: 'emphasizedAccelerate' });
160
178
  }
161
179
  });
@@ -179,6 +197,7 @@ define('ui-side-sheet', {
179
197
  return true;
180
198
  },
181
199
  onStart() {
200
+ el.getAnimations?.()?.forEach((a) => a.cancel());
182
201
  el.style.transition = 'none';
183
202
  },
184
203
  onMove({ dx }) {
@@ -201,29 +220,34 @@ define('ui-side-sheet', {
201
220
  const shouldDismiss = !cancelled && dismissDirection;
202
221
 
203
222
  if (shouldDismiss) {
223
+ closingViaSwipe = true;
204
224
  const targetTransform = isEnd ? 'translateX(100%)' : 'translateX(-100%)';
205
225
  const remaining = Math.max(0, w - Math.abs(dx));
206
226
  const ms = Math.min(300, Math.max(120, Math.round(remaining / (Math.max(Math.abs(vx), 0.8)))));
207
227
  if (scrimEl) {
208
228
  animate(scrimEl, fx.fadeOut, { duration: ms, easing: 'emphasizedAccelerate' });
209
229
  }
210
- const anim = animate(el, [
230
+ animate(el, [
211
231
  { transform: el.style.transform || `translateX(${dx}px)` },
212
232
  { transform: targetTransform },
213
- ], { duration: ms, easing: 'emphasizedAccelerate' });
214
- anim.finished.then(() => {
215
- requestClose('swipe');
216
- });
233
+ ], { duration: ms, easing: 'emphasizedAccelerate', fill: 'forwards' });
234
+ requestClose('swipe');
217
235
  } else {
218
236
  if (scrimEl) {
219
- animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
237
+ const scrimSnap = animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
220
238
  duration: 'short4', easing: 'emphasizedDecelerate',
221
- }).finished.then(() => { if (scrimEl) scrimEl.style.opacity = ''; });
239
+ });
240
+ scrimSnap.finished.then(() => {
241
+ try { scrimSnap.cancel(); } catch {}
242
+ if (scrimEl) scrimEl.style.opacity = '';
243
+ });
222
244
  }
223
- animate(el, [
245
+ const snapAnim = animate(el, [
224
246
  { transform: el.style.transform || `translateX(${dx}px)` },
225
247
  { transform: 'translateX(0)' },
226
- ], { duration: 'short4', easing: 'emphasizedDecelerate' }).finished.then(() => {
248
+ ], { duration: 'short4', easing: 'emphasizedDecelerate' });
249
+ snapAnim.finished.then(() => {
250
+ try { snapAnim.cancel(); } catch {}
227
251
  if (el) el.style.transform = '';
228
252
  });
229
253
  }
@@ -237,8 +261,16 @@ define('ui-side-sheet', {
237
261
  sync();
238
262
  };
239
263
 
264
+ const overlayRef = (el) => {
265
+ queueMicrotask(() => {
266
+ try {
267
+ if (el.isConnected) el.showPopover?.();
268
+ } catch {}
269
+ });
270
+ };
271
+
240
272
  const modalView = () => html`
241
- <div class="overlay">
273
+ <div class="overlay" popover="manual" ref=${overlayRef}>
242
274
  <div class="scrim" part="scrim" aria-hidden="true"
243
275
  ref=${(el) => { scrimEl = el; }}
244
276
  @click=${() => requestClose('scrim')}></div>
@@ -97,6 +97,7 @@ define('ui-swipe-row', {
97
97
  { transform: targetTransform },
98
98
  ], { duration: ms, easing });
99
99
  return anim.finished.then(() => {
100
+ try { anim.cancel(); } catch {}
100
101
  if (contentEl) contentEl.style.transform = targetTransform;
101
102
  });
102
103
  };
@@ -163,6 +164,7 @@ define('ui-swipe-row', {
163
164
  return true;
164
165
  },
165
166
  onStart() {
167
+ el.getAnimations?.()?.forEach((a) => a.cancel());
166
168
  if (currentOpen === 'end') startOffset = -getEndWidth();
167
169
  else if (currentOpen === 'start') startOffset = getStartWidth();
168
170
  else startOffset = 0;