@alacris/ui 0.5.0 → 0.5.1

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.1/src/index.js",
51
+ "@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.1/src/theme/index.js",
52
+ "@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.5.1/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.1",
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';
@@ -279,12 +279,7 @@ const styles = css`
279
279
  place-items: center;
280
280
  }
281
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; }
285
- }
286
282
  .modal-surface {
287
- animation: ui-date-picker-in ${sys.duration.medium2} ${sys.easing.emphasizedDecelerate};
288
283
  position: relative;
289
284
  display: flex;
290
285
  flex-direction: column;
@@ -714,12 +709,17 @@ define('ui-date-picker', {
714
709
  ${calGrid}
715
710
  </div>`;
716
711
 
712
+ const modalSurfaceRef = (el) => {
713
+ modalSurfaceEl = el;
714
+ releaseFill(animate(el, fx.scaleIn, { duration: 'medium2', easing: 'emphasizedDecelerate' }));
715
+ };
716
+
717
717
  const modalView = () => html`
718
718
  <div class="overlay">
719
719
  <div class="scrim" aria-hidden="true" @click=${closePanel}></div>
720
720
  <div class="modal-surface" part="panel" role="dialog" aria-modal="true"
721
721
  aria-label=${() => label() || 'Choose date'}
722
- ref=${(el) => (modalSurfaceEl = el)}>
722
+ ref=${modalSurfaceRef}>
723
723
  <div class="headline">${() => (range() ? 'Select dates' : 'Select date')}</div>
724
724
  <div class="picked">${pickedLabel}</div>
725
725
  <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', {
@@ -51,12 +51,7 @@ const styles = css`
51
51
  place-items: center;
52
52
  }
53
53
  .scrim { position: absolute; inset: 0; background: ${t.scrim}; }
54
- @keyframes ui-dialog-in {
55
- from { transform: scale(0.8); }
56
- to { transform: none; }
57
- }
58
54
  .surface {
59
- animation: ui-dialog-in ${sys.duration.medium2} ${sys.easing.emphasizedDecelerate};
60
55
  position: relative;
61
56
  display: flex;
62
57
  flex-direction: column;
@@ -146,13 +141,18 @@ define('ui-dialog', {
146
141
  sync();
147
142
  };
148
143
 
144
+ const surfaceRef = (el) => {
145
+ surfaceEl = el;
146
+ releaseFill(animate(el, fx.scaleIn, { duration: 'medium2', easing: 'emphasizedDecelerate' }));
147
+ };
148
+
149
149
  const view = () => html`
150
150
  <div class="overlay">
151
151
  <div class="scrim" part="scrim" aria-hidden="true" @click=${() => requestClose('scrim')}></div>
152
152
  <div class="surface" part="surface" role="dialog" aria-modal="true"
153
153
  aria-labelledby=${() => (hasHeadline() ? 'headline' : null)}
154
154
  aria-label=${() => (hasHeadline() ? null : (label() || 'Dialog'))}
155
- tabindex="-1" ref=${(el) => (surfaceEl = el)}>
155
+ tabindex="-1" ref=${surfaceRef}>
156
156
  <div class="headline" part="headline" id="headline"
157
157
  ref=${(el) => hasSlot(el.querySelector('slot'), (has) => {
158
158
  hasHeadline.set(has);
@@ -116,9 +116,11 @@ define('ui-drawer', {
116
116
  };
117
117
 
118
118
  // Trap focus + lock scroll exactly while the modal drawer is open.
119
+ let closingViaSwipe = false;
119
120
  let prevActive = null;
120
121
  effect(() => {
121
122
  if (open() && variant() === 'modal') {
123
+ closingViaSwipe = false;
122
124
  prevActive = document.activeElement;
123
125
  document.addEventListener('keydown', onDocKeydown, true);
124
126
  if (!unlock) unlock = scrollLock();
@@ -143,7 +145,7 @@ define('ui-drawer', {
143
145
 
144
146
  // The panel slides out while the presence overlay (scrim included) fades.
145
147
  effect(() => {
146
- if (!open() && surfaceEl?.isConnected) {
148
+ if (!open() && surfaceEl?.isConnected && !closingViaSwipe) {
147
149
  animate(surfaceEl, slideOut(), { duration: 'short4', easing: 'emphasizedAccelerate' });
148
150
  }
149
151
  });
@@ -161,6 +163,7 @@ define('ui-drawer', {
161
163
  return true;
162
164
  },
163
165
  onStart() {
166
+ el.getAnimations?.()?.forEach((a) => a.cancel());
164
167
  el.style.transition = 'none';
165
168
  },
166
169
  onMove({ dx }) {
@@ -183,29 +186,34 @@ define('ui-drawer', {
183
186
  const shouldDismiss = !cancelled && dismissDirection;
184
187
 
185
188
  if (shouldDismiss) {
189
+ closingViaSwipe = true;
186
190
  const targetTransform = isEnd ? 'translateX(100%)' : 'translateX(-100%)';
187
191
  const remaining = Math.max(0, w - Math.abs(dx));
188
192
  const ms = Math.min(300, Math.max(120, Math.round(remaining / (Math.max(Math.abs(vx), 0.8)))));
189
193
  if (scrimEl) {
190
194
  animate(scrimEl, fx.fadeOut, { duration: ms, easing: 'emphasizedAccelerate' });
191
195
  }
192
- const anim = animate(el, [
196
+ animate(el, [
193
197
  { transform: el.style.transform || `translateX(${dx}px)` },
194
198
  { transform: targetTransform },
195
- ], { duration: ms, easing: 'emphasizedAccelerate' });
196
- anim.finished.then(() => {
197
- requestClose('swipe');
198
- });
199
+ ], { duration: ms, easing: 'emphasizedAccelerate', fill: 'forwards' });
200
+ requestClose('swipe');
199
201
  } else {
200
202
  if (scrimEl) {
201
- animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
203
+ const scrimSnap = animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
202
204
  duration: 'short4', easing: 'emphasizedDecelerate',
203
- }).finished.then(() => { if (scrimEl) scrimEl.style.opacity = ''; });
205
+ });
206
+ scrimSnap.finished.then(() => {
207
+ try { scrimSnap.cancel(); } catch {}
208
+ if (scrimEl) scrimEl.style.opacity = '';
209
+ });
204
210
  }
205
- animate(el, [
211
+ const snapAnim = animate(el, [
206
212
  { transform: el.style.transform || `translateX(${dx}px)` },
207
213
  { transform: 'translateX(0)' },
208
- ], { duration: 'short4', easing: 'emphasizedDecelerate' }).finished.then(() => {
214
+ ], { duration: 'short4', easing: 'emphasizedDecelerate' });
215
+ snapAnim.finished.then(() => {
216
+ try { snapAnim.cancel(); } catch {}
209
217
  if (el) el.style.transform = '';
210
218
  });
211
219
  }
@@ -134,9 +134,11 @@ define('ui-sheet', {
134
134
  if (e.key === 'Escape') requestClose('esc');
135
135
  };
136
136
 
137
+ let closingViaSwipe = false;
137
138
  let prevActive = null;
138
139
  effect(() => {
139
140
  if (open() && variant() !== 'standard') {
141
+ closingViaSwipe = false;
140
142
  prevActive = document.activeElement;
141
143
  document.addEventListener('keydown', onDocKeydown, true);
142
144
  unlock = scrollLock();
@@ -150,7 +152,7 @@ define('ui-sheet', {
150
152
  prevActive = null;
151
153
  unlock?.();
152
154
  unlock = null;
153
- if (surfaceEl?.isConnected) {
155
+ if (surfaceEl?.isConnected && !closingViaSwipe) {
154
156
  animate(surfaceEl, fx.sheetOut, { duration: 'short4', easing: 'emphasizedAccelerate' });
155
157
  }
156
158
  }
@@ -185,6 +187,7 @@ define('ui-sheet', {
185
187
  return true;
186
188
  },
187
189
  onStart() {
190
+ el.getAnimations?.()?.forEach((a) => a.cancel());
188
191
  el.style.transition = 'none';
189
192
  },
190
193
  onMove({ dy }) {
@@ -204,28 +207,33 @@ define('ui-sheet', {
204
207
  const shouldDismiss = !cancelled && (vy > 0.4 || dy > h * 0.35);
205
208
 
206
209
  if (shouldDismiss) {
210
+ closingViaSwipe = true;
207
211
  const remaining = Math.max(0, h - dy);
208
212
  const ms = Math.min(300, Math.max(120, Math.round(remaining / (Math.max(vy, 0.8)))));
209
213
  if (scrimEl) {
210
214
  animate(scrimEl, fx.fadeOut, { duration: ms, easing: 'emphasizedAccelerate' });
211
215
  }
212
- const anim = animate(el, [
216
+ animate(el, [
213
217
  { transform: el.style.transform || `translateY(${dy}px)` },
214
218
  { transform: 'translateY(100%)' },
215
- ], { duration: ms, easing: 'emphasizedAccelerate' });
216
- anim.finished.then(() => {
217
- requestClose('swipe');
218
- });
219
+ ], { duration: ms, easing: 'emphasizedAccelerate', fill: 'forwards' });
220
+ requestClose('swipe');
219
221
  } else {
220
222
  if (scrimEl) {
221
- animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
223
+ const scrimSnap = animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
222
224
  duration: 'short4', easing: 'emphasizedDecelerate',
223
- }).finished.then(() => { if (scrimEl) scrimEl.style.opacity = ''; });
225
+ });
226
+ scrimSnap.finished.then(() => {
227
+ try { scrimSnap.cancel(); } catch {}
228
+ if (scrimEl) scrimEl.style.opacity = '';
229
+ });
224
230
  }
225
- animate(el, [
231
+ const snapAnim = animate(el, [
226
232
  { transform: el.style.transform || `translateY(${dy}px)` },
227
233
  { transform: 'translateY(0)' },
228
- ], { duration: 'short4', easing: 'emphasizedDecelerate' }).finished.then(() => {
234
+ ], { duration: 'short4', easing: 'emphasizedDecelerate' });
235
+ snapAnim.finished.then(() => {
236
+ try { snapAnim.cancel(); } catch {}
229
237
  if (el) el.style.transform = '';
230
238
  });
231
239
  }
@@ -136,9 +136,11 @@ define('ui-side-sheet', {
136
136
  if (e.key === 'Escape') requestClose('esc');
137
137
  };
138
138
 
139
+ let closingViaSwipe = false;
139
140
  let prevActive = null;
140
141
  effect(() => {
141
142
  if (open() && variant() !== 'standard') {
143
+ closingViaSwipe = false;
142
144
  prevActive = document.activeElement;
143
145
  document.addEventListener('keydown', onDocKeydown, true);
144
146
  unlock = scrollLock();
@@ -155,7 +157,7 @@ define('ui-side-sheet', {
155
157
  }
156
158
  });
157
159
  effect(() => {
158
- if (!open() && surfaceEl?.isConnected) {
160
+ if (!open() && surfaceEl?.isConnected && !closingViaSwipe) {
159
161
  animate(surfaceEl, slideOut(), { duration: 'short4', easing: 'emphasizedAccelerate' });
160
162
  }
161
163
  });
@@ -179,6 +181,7 @@ define('ui-side-sheet', {
179
181
  return true;
180
182
  },
181
183
  onStart() {
184
+ el.getAnimations?.()?.forEach((a) => a.cancel());
182
185
  el.style.transition = 'none';
183
186
  },
184
187
  onMove({ dx }) {
@@ -201,29 +204,34 @@ define('ui-side-sheet', {
201
204
  const shouldDismiss = !cancelled && dismissDirection;
202
205
 
203
206
  if (shouldDismiss) {
207
+ closingViaSwipe = true;
204
208
  const targetTransform = isEnd ? 'translateX(100%)' : 'translateX(-100%)';
205
209
  const remaining = Math.max(0, w - Math.abs(dx));
206
210
  const ms = Math.min(300, Math.max(120, Math.round(remaining / (Math.max(Math.abs(vx), 0.8)))));
207
211
  if (scrimEl) {
208
212
  animate(scrimEl, fx.fadeOut, { duration: ms, easing: 'emphasizedAccelerate' });
209
213
  }
210
- const anim = animate(el, [
214
+ animate(el, [
211
215
  { transform: el.style.transform || `translateX(${dx}px)` },
212
216
  { transform: targetTransform },
213
- ], { duration: ms, easing: 'emphasizedAccelerate' });
214
- anim.finished.then(() => {
215
- requestClose('swipe');
216
- });
217
+ ], { duration: ms, easing: 'emphasizedAccelerate', fill: 'forwards' });
218
+ requestClose('swipe');
217
219
  } else {
218
220
  if (scrimEl) {
219
- animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
221
+ const scrimSnap = animate(scrimEl, [{ opacity: scrimEl.style.opacity || '0.5' }, { opacity: 1 }], {
220
222
  duration: 'short4', easing: 'emphasizedDecelerate',
221
- }).finished.then(() => { if (scrimEl) scrimEl.style.opacity = ''; });
223
+ });
224
+ scrimSnap.finished.then(() => {
225
+ try { scrimSnap.cancel(); } catch {}
226
+ if (scrimEl) scrimEl.style.opacity = '';
227
+ });
222
228
  }
223
- animate(el, [
229
+ const snapAnim = animate(el, [
224
230
  { transform: el.style.transform || `translateX(${dx}px)` },
225
231
  { transform: 'translateX(0)' },
226
- ], { duration: 'short4', easing: 'emphasizedDecelerate' }).finished.then(() => {
232
+ ], { duration: 'short4', easing: 'emphasizedDecelerate' });
233
+ snapAnim.finished.then(() => {
234
+ try { snapAnim.cancel(); } catch {}
227
235
  if (el) el.style.transform = '';
228
236
  });
229
237
  }
@@ -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;