@alacris/ui 0.3.0 → 0.4.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 +4 -4
- package/package.json +1 -1
- package/src/components/ui-autocomplete.js +1 -1
- package/src/components/ui-date-picker.js +9 -8
- package/src/components/ui-drawer.js +2 -2
- package/src/components/ui-fab-menu.js +5 -4
- package/src/components/ui-menu.js +6 -5
- package/src/components/ui-search.js +6 -5
- package/src/components/ui-select.js +9 -6
- package/src/components/ui-sheet.js +2 -2
- package/src/components/ui-side-sheet.js +2 -2
- package/src/components/ui-text-field.js +33 -2
- package/src/components/ui-time-picker.js +6 -5
- package/src/index.js +1 -1
- package/src/motion/animate.js +14 -0
- package/src/motion/index.js +1 -1
- package/src/tokens/sys.js +1 -0
- package/src/tokens/system.js +1 -0
- package/src/util/popup.js +5 -0
- package/types/index.d.ts +1 -0
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.
|
|
50
|
-
"@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.
|
|
51
|
-
"@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.
|
|
52
|
-
"@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.
|
|
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/"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
</script>
|
package/package.json
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
// @prop {string} placeholder=''
|
|
27
27
|
// @event change — committed; detail: { value } or { start, end, value } when range
|
|
28
28
|
// @event input — field keystroke; detail: { value } (the raw text)
|
|
29
|
-
// @event open — calendar visible (after the enter animation)
|
|
30
|
-
// @event close — calendar removed (after the exit animation)
|
|
29
|
+
// @event open — calendar visible (after the enter animation); does not bubble
|
|
30
|
+
// @event close — calendar removed (after the exit animation); does not bubble
|
|
31
31
|
// @part field, input, label, panel, day
|
|
32
32
|
// @vars see `t` below (`themeVars.names`)
|
|
33
33
|
|
|
@@ -39,6 +39,7 @@ import { presence } from '../motion/presence.js';
|
|
|
39
39
|
import { animate, fx } from '../motion/animate.js';
|
|
40
40
|
import { autoUpdate } from '../util/position.js';
|
|
41
41
|
import { escapeLayer } from '../util/keys.js';
|
|
42
|
+
import { popupEvent } from '../util/popup.js';
|
|
42
43
|
import { focusTrap, scrollLock } from '../util/focus.js';
|
|
43
44
|
import './ui-icon-button.js';
|
|
44
45
|
import './ui-button.js';
|
|
@@ -262,7 +263,7 @@ const styles = css`
|
|
|
262
263
|
|
|
263
264
|
.panel {
|
|
264
265
|
position: fixed;
|
|
265
|
-
z-index: ${sys.z.
|
|
266
|
+
z-index: ${sys.z.popup};
|
|
266
267
|
padding: ${sys.space(3)} ${sys.space(3)} ${sys.space(4)};
|
|
267
268
|
background: ${t.panelBg};
|
|
268
269
|
border-radius: ${t.panelRadius};
|
|
@@ -273,7 +274,7 @@ const styles = css`
|
|
|
273
274
|
.overlay {
|
|
274
275
|
position: fixed;
|
|
275
276
|
inset: 0;
|
|
276
|
-
z-index: ${sys.z.
|
|
277
|
+
z-index: ${sys.z.popup};
|
|
277
278
|
display: grid;
|
|
278
279
|
place-items: center;
|
|
279
280
|
}
|
|
@@ -758,16 +759,16 @@ define('ui-date-picker', {
|
|
|
758
759
|
exit: fx.scaleOut,
|
|
759
760
|
enterDuration: 'short4',
|
|
760
761
|
exitDuration: 'short4',
|
|
761
|
-
onEntered: () => host.emit('open'),
|
|
762
|
-
onExited: () => host.emit('close'),
|
|
762
|
+
onEntered: () => host.emit('open', null, popupEvent),
|
|
763
|
+
onExited: () => host.emit('close', null, popupEvent),
|
|
763
764
|
})}
|
|
764
765
|
${presence(() => open() && presentation() === 'modal', modalView, {
|
|
765
766
|
enter: fx.fadeIn,
|
|
766
767
|
exit: fx.fadeOut,
|
|
767
768
|
enterDuration: 'medium2',
|
|
768
769
|
exitDuration: 'short4',
|
|
769
|
-
onEntered: () => host.emit('open'),
|
|
770
|
-
onExited: () => host.emit('close'),
|
|
770
|
+
onEntered: () => host.emit('open', null, popupEvent),
|
|
771
|
+
onExited: () => host.emit('close', null, popupEvent),
|
|
771
772
|
})}
|
|
772
773
|
</div>`;
|
|
773
774
|
},
|
|
@@ -26,7 +26,7 @@ import { define, html, css, vars, effect, onCleanup } from '@alacris/core';
|
|
|
26
26
|
import { sys } from '../tokens/sys.js';
|
|
27
27
|
import { base } from './base.js';
|
|
28
28
|
import { presence } from '../motion/presence.js';
|
|
29
|
-
import { animate, fx } from '../motion/animate.js';
|
|
29
|
+
import { animate, fx, releaseFill } from '../motion/animate.js';
|
|
30
30
|
import { focusTrap, scrollLock } from '../util/focus.js';
|
|
31
31
|
|
|
32
32
|
const t = vars('ui-drawer', {
|
|
@@ -135,7 +135,7 @@ define('ui-drawer', {
|
|
|
135
135
|
|
|
136
136
|
const surfaceRef = (el) => {
|
|
137
137
|
surfaceEl = el;
|
|
138
|
-
animate(el, slideIn(), { duration: 'medium2', easing: 'emphasizedDecelerate' });
|
|
138
|
+
releaseFill(animate(el, slideIn(), { duration: 'medium2', easing: 'emphasizedDecelerate' }));
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
const overlay = () => html`
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
//
|
|
15
15
|
// @prop {boolean} open=false
|
|
16
16
|
// @prop {string} label='' — accessible name for the action list
|
|
17
|
-
// @event open — menu visible (after the enter animation)
|
|
18
|
-
// @event close — menu removed (after the exit animation)
|
|
17
|
+
// @event open — menu visible (after the enter animation); does not bubble
|
|
18
|
+
// @event close — menu removed (after the exit animation); does not bubble
|
|
19
19
|
// @slot trigger — the <ui-fab> that toggles the menu
|
|
20
20
|
// @slot (default) — related <ui-fab> actions
|
|
21
21
|
// @part actions, trigger
|
|
@@ -26,6 +26,7 @@ import { sys } from '../tokens/sys.js';
|
|
|
26
26
|
import { base } from './base.js';
|
|
27
27
|
import { presence } from '../motion/presence.js';
|
|
28
28
|
import { fx } from '../motion/animate.js';
|
|
29
|
+
import { popupEvent } from '../util/popup.js';
|
|
29
30
|
import './ui-fab.js';
|
|
30
31
|
|
|
31
32
|
const t = vars('ui-fab-menu', {
|
|
@@ -112,8 +113,8 @@ define('ui-fab-menu', {
|
|
|
112
113
|
exit: fx.scaleOut,
|
|
113
114
|
enterDuration: 'short4',
|
|
114
115
|
exitDuration: 'short2',
|
|
115
|
-
onEntered: () => host.emit('open'),
|
|
116
|
-
onExited: () => host.emit('close'),
|
|
116
|
+
onEntered: () => host.emit('open', null, popupEvent),
|
|
117
|
+
onExited: () => host.emit('close', null, popupEvent),
|
|
117
118
|
})}`;
|
|
118
119
|
},
|
|
119
120
|
});
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// @prop {boolean} open=false
|
|
17
17
|
// @prop {string} placement='bottom-start' — side[-alignment] (see util/position.js)
|
|
18
18
|
// @event select — an item was chosen; detail: { value }
|
|
19
|
-
// @event open — panel visible (after the enter animation)
|
|
20
|
-
// @event close — panel removed (after the exit animation)
|
|
19
|
+
// @event open — panel visible (after the enter animation); does not bubble
|
|
20
|
+
// @event close — panel removed (after the exit animation); does not bubble
|
|
21
21
|
// @slot anchor — the trigger element
|
|
22
22
|
// @slot (default) — <ui-menu-item> children
|
|
23
23
|
// @part panel — the floating menu surface
|
|
@@ -30,6 +30,7 @@ import { presence } from '../motion/presence.js';
|
|
|
30
30
|
import { fx } from '../motion/animate.js';
|
|
31
31
|
import { autoUpdate } from '../util/position.js';
|
|
32
32
|
import { rovingTabindex } from '../util/keys.js';
|
|
33
|
+
import { popupEvent } from '../util/popup.js';
|
|
33
34
|
import './ui-menu-item.js';
|
|
34
35
|
|
|
35
36
|
const t = vars('ui-menu', {
|
|
@@ -47,7 +48,7 @@ const styles = css`
|
|
|
47
48
|
position: fixed;
|
|
48
49
|
inset-inline-start: 0;
|
|
49
50
|
inset-block-start: 0;
|
|
50
|
-
z-index: ${sys.z.
|
|
51
|
+
z-index: ${sys.z.popup};
|
|
51
52
|
min-inline-size: ${t.minWidth};
|
|
52
53
|
max-inline-size: ${t.maxWidth};
|
|
53
54
|
padding-block: ${sys.space(2)};
|
|
@@ -182,8 +183,8 @@ define('ui-menu', {
|
|
|
182
183
|
exitDuration: 'short2',
|
|
183
184
|
enterEasing: 'emphasizedDecelerate',
|
|
184
185
|
exitEasing: 'emphasizedAccelerate',
|
|
185
|
-
onEntered: () => host.emit('open'),
|
|
186
|
-
onExited: () => host.emit('close'),
|
|
186
|
+
onEntered: () => host.emit('open', null, popupEvent),
|
|
187
|
+
onExited: () => host.emit('close', null, popupEvent),
|
|
187
188
|
})}`;
|
|
188
189
|
},
|
|
189
190
|
});
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
// @event change — committed (blur/Enter); detail: { value }
|
|
29
29
|
// @event submit — Enter pressed; detail: { value }
|
|
30
30
|
// @event clear — the clear affordance was used
|
|
31
|
-
// @event open — suggestions visible (after the enter animation)
|
|
32
|
-
// @event close — suggestions removed (after the exit animation)
|
|
31
|
+
// @event open — suggestions visible (after the enter animation); does not bubble
|
|
32
|
+
// @event close — suggestions removed (after the exit animation); does not bubble
|
|
33
33
|
// @slot leading — replaces the search icon
|
|
34
34
|
// @slot trailing — after the clear button (avatar, extra actions)
|
|
35
35
|
// @slot (default) — suggestion rows (ui-list-item, …). The panel is a list
|
|
@@ -43,6 +43,7 @@ import { base } from './base.js';
|
|
|
43
43
|
import { formBind } from '../util/form.js';
|
|
44
44
|
import { presence } from '../motion/presence.js';
|
|
45
45
|
import { fx } from '../motion/animate.js';
|
|
46
|
+
import { popupEvent } from '../util/popup.js';
|
|
46
47
|
import './ui-icon.js';
|
|
47
48
|
import './ui-icon-button.js';
|
|
48
49
|
|
|
@@ -75,7 +76,7 @@ const styles = css`
|
|
|
75
76
|
}
|
|
76
77
|
.shell.open {
|
|
77
78
|
overflow: visible;
|
|
78
|
-
z-index: ${sys.z.
|
|
79
|
+
z-index: ${sys.z.popup};
|
|
79
80
|
background: ${t.panelBg};
|
|
80
81
|
border-start-start-radius: ${t.panelRadius};
|
|
81
82
|
border-start-end-radius: ${t.panelRadius};
|
|
@@ -307,8 +308,8 @@ define('ui-search', {
|
|
|
307
308
|
enterEasing: 'emphasizedDecelerate',
|
|
308
309
|
exitDuration: 'short4',
|
|
309
310
|
exitEasing: 'emphasizedAccelerate',
|
|
310
|
-
onEntered: () => host.emit('open'),
|
|
311
|
-
onExited: () => host.emit('close'),
|
|
311
|
+
onEntered: () => host.emit('open', null, popupEvent),
|
|
312
|
+
onExited: () => host.emit('close', null, popupEvent),
|
|
312
313
|
})}
|
|
313
314
|
</div>`;
|
|
314
315
|
},
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
// arrows move the active option, Enter/Space selects it, Escape closes the
|
|
11
11
|
// panel only — an enclosing dialog keeps its own Escape for a second press,
|
|
12
12
|
// typing jumps to the next option starting with that letter. The panel closes
|
|
13
|
-
// on outside pointerdown and returns focus to the field.
|
|
13
|
+
// on outside pointerdown and returns focus to the field. `open`/`close` do
|
|
14
|
+
// not bubble: they share those names with dialogs and sheets, and a bubbling
|
|
15
|
+
// select-close looks like the sheet dismissed itself.
|
|
14
16
|
//
|
|
15
17
|
// Past a handful of options the panel gets a filter field, because scrolling
|
|
16
18
|
// is not a way to find one entry among several hundred. It takes focus when
|
|
@@ -31,8 +33,8 @@
|
|
|
31
33
|
// @prop {number} searchThreshold=8
|
|
32
34
|
// @prop {string} searchPlaceholder='Search'
|
|
33
35
|
// @event change — an option was chosen; detail: { value }
|
|
34
|
-
// @event open — panel enter animation finished
|
|
35
|
-
// @event close — panel exit animation finished
|
|
36
|
+
// @event open — panel enter animation finished; does not bubble
|
|
37
|
+
// @event close — panel exit animation finished; does not bubble
|
|
36
38
|
// @slot (default) — <ui-option> children (projected into the panel)
|
|
37
39
|
// @part control — the field button (role="combobox")
|
|
38
40
|
// @part label, panel
|
|
@@ -50,6 +52,7 @@ import { presence } from '../motion/presence.js';
|
|
|
50
52
|
import { fx } from '../motion/animate.js';
|
|
51
53
|
import { autoUpdate } from '../util/position.js';
|
|
52
54
|
import { escapeLayer } from '../util/keys.js';
|
|
55
|
+
import { popupEvent } from '../util/popup.js';
|
|
53
56
|
import './ui-icon.js';
|
|
54
57
|
import './ui-option.js';
|
|
55
58
|
|
|
@@ -210,7 +213,7 @@ const styles = css`
|
|
|
210
213
|
|
|
211
214
|
.panel {
|
|
212
215
|
position: fixed;
|
|
213
|
-
z-index: ${sys.z.
|
|
216
|
+
z-index: ${sys.z.popup};
|
|
214
217
|
min-inline-size: 112px;
|
|
215
218
|
max-block-size: 40vh;
|
|
216
219
|
/* The filter field stays put while the options scroll under it, so the
|
|
@@ -553,8 +556,8 @@ define('ui-select', {
|
|
|
553
556
|
exitDuration: 'short2',
|
|
554
557
|
enterEasing: 'emphasizedDecelerate',
|
|
555
558
|
exitEasing: 'emphasizedAccelerate',
|
|
556
|
-
onEntered: () => host.emit('open'),
|
|
557
|
-
onExited: () => host.emit('close'),
|
|
559
|
+
onEntered: () => host.emit('open', null, popupEvent),
|
|
560
|
+
onExited: () => host.emit('close', null, popupEvent),
|
|
558
561
|
})}
|
|
559
562
|
</div>`;
|
|
560
563
|
},
|
|
@@ -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-sheet', {
|
|
@@ -163,7 +163,7 @@ define('ui-sheet', {
|
|
|
163
163
|
aria-labelledby=${() => (hasHeadline() ? 'headline' : null)}
|
|
164
164
|
aria-label=${() => (hasHeadline() ? null : (label() || 'Sheet'))}
|
|
165
165
|
tabindex="-1"
|
|
166
|
-
ref=${(el) => { surfaceEl = el; animate(el, fx.sheetIn, { duration: 'medium2', easing: 'emphasizedDecelerate' }); }}>
|
|
166
|
+
ref=${(el) => { surfaceEl = el; releaseFill(animate(el, fx.sheetIn, { duration: 'medium2', easing: 'emphasizedDecelerate' })); }}>
|
|
167
167
|
<div class="handle" part="handle" aria-hidden="true"></div>
|
|
168
168
|
<div class="headline" part="headline" id="headline"
|
|
169
169
|
ref=${(el) => hasSlot(el.querySelector('slot'), (has) => {
|
|
@@ -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
|
import './ui-icon-button.js';
|
|
35
35
|
|
|
@@ -158,7 +158,7 @@ define('ui-side-sheet', {
|
|
|
158
158
|
|
|
159
159
|
const surfaceRef = (el) => {
|
|
160
160
|
surfaceEl = el;
|
|
161
|
-
animate(el, slideIn(), { duration: 'medium2', easing: 'emphasizedDecelerate' });
|
|
161
|
+
releaseFill(animate(el, slideIn(), { duration: 'medium2', easing: 'emphasizedDecelerate' }));
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
const hasSlot = (el, set) => {
|
|
@@ -12,7 +12,16 @@
|
|
|
12
12
|
// @prop {boolean} clearable=false — trailing ✕ while there is content
|
|
13
13
|
// @prop {string} name='' — form participation
|
|
14
14
|
// @prop {number} maxlength=0 — >0 shows a character counter and enforces it
|
|
15
|
-
// @prop {number} rows=3
|
|
15
|
+
// @prop {number} rows=3
|
|
16
|
+
// @prop {string} autocomplete='' — absent unless set; 'off' keeps the
|
|
17
|
+
// browser's saved-value list out of a
|
|
18
|
+
// field whose contents are not a name,
|
|
19
|
+
// an address or a password
|
|
20
|
+
// @prop {string} autocapitalize='' — 'off' for anything case-sensitive
|
|
21
|
+
// @prop {string} autocorrect='' — 'off' to stop substitutions
|
|
22
|
+
// @prop {string} spellcheck='' — 'false' for code and query syntax
|
|
23
|
+
// @prop {string} inputmode=''
|
|
24
|
+
// @prop {string} enterkeyhint='' — textarea rows
|
|
16
25
|
// @event input — every keystroke; detail: { value }
|
|
17
26
|
// @event change — committed (blur/Enter); detail: { value }
|
|
18
27
|
// @event clear — the clear affordance was used
|
|
@@ -284,10 +293,20 @@ define('ui-text-field', {
|
|
|
284
293
|
variant: 'filled', label: '', value: '', type: 'text', placeholder: '',
|
|
285
294
|
helper: '', error: '', disabled: false, required: false, clearable: false,
|
|
286
295
|
name: '', maxlength: 0, rows: 3,
|
|
296
|
+
// A field the platform must keep its hands off has to be able to say so.
|
|
297
|
+
// These are absent unless set, so nothing changes for an ordinary field.
|
|
298
|
+
autocomplete: '', autocapitalize: '', autocorrect: '', spellcheck: '',
|
|
299
|
+
inputmode: '', enterkeyhint: '',
|
|
287
300
|
},
|
|
288
301
|
styles: [base, styles],
|
|
289
302
|
setup(p, host) {
|
|
290
|
-
const { variant, label, value, type, placeholder, helper, error, disabled, required, clearable, name, maxlength, rows
|
|
303
|
+
const { variant, label, value, type, placeholder, helper, error, disabled, required, clearable, name, maxlength, rows,
|
|
304
|
+
autocomplete, autocapitalize, autocorrect, spellcheck, inputmode, enterkeyhint } = p;
|
|
305
|
+
|
|
306
|
+
// An empty prop means "say nothing", so the browser keeps its default.
|
|
307
|
+
// Writing the attribute as an empty string would not: autocomplete="" is
|
|
308
|
+
// a value, and spellcheck="" reads as spellcheck="true".
|
|
309
|
+
const attr = (sig) => () => (sig() === '' ? null : sig());
|
|
291
310
|
formBind(host, { name, value, disabled });
|
|
292
311
|
|
|
293
312
|
const focused = signal(false);
|
|
@@ -325,6 +344,12 @@ define('ui-text-field', {
|
|
|
325
344
|
maxlength=${() => (maxlength() > 0 ? maxlength() : null)}
|
|
326
345
|
?required=${required} ?disabled=${disabled}
|
|
327
346
|
aria-invalid=${() => (error() ? 'true' : null)}
|
|
347
|
+
autocomplete=${attr(autocomplete)}
|
|
348
|
+
autocapitalize=${attr(autocapitalize)}
|
|
349
|
+
autocorrect=${attr(autocorrect)}
|
|
350
|
+
spellcheck=${attr(spellcheck)}
|
|
351
|
+
inputmode=${attr(inputmode)}
|
|
352
|
+
enterkeyhint=${attr(enterkeyhint)}
|
|
328
353
|
@input=${onInput} @change=${commit}
|
|
329
354
|
@focus=${() => focused.set(true)} @blur=${() => focused.set(false)}></textarea></span>`
|
|
330
355
|
: html`<input part="input" ref=${(el) => (input = el)}
|
|
@@ -333,6 +358,12 @@ define('ui-text-field', {
|
|
|
333
358
|
maxlength=${() => (maxlength() > 0 ? maxlength() : null)}
|
|
334
359
|
?required=${required} ?disabled=${disabled}
|
|
335
360
|
aria-invalid=${() => (error() ? 'true' : null)}
|
|
361
|
+
autocomplete=${attr(autocomplete)}
|
|
362
|
+
autocapitalize=${attr(autocapitalize)}
|
|
363
|
+
autocorrect=${attr(autocorrect)}
|
|
364
|
+
spellcheck=${attr(spellcheck)}
|
|
365
|
+
inputmode=${attr(inputmode)}
|
|
366
|
+
enterkeyhint=${attr(enterkeyhint)}
|
|
336
367
|
@input=${onInput} @change=${commit}
|
|
337
368
|
@focus=${() => focused.set(true)} @blur=${() => focused.set(false)}>`)}
|
|
338
369
|
${() => (clearable() && value() !== '' && !disabled()
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
// @prop {string} placeholder=''
|
|
22
22
|
// @event change — committed; detail: { value }
|
|
23
23
|
// @event input — field keystroke; detail: { value } (the raw text)
|
|
24
|
-
// @event open — panel visible (after the enter animation)
|
|
25
|
-
// @event close — panel removed (after the exit animation)
|
|
24
|
+
// @event open — panel visible (after the enter animation); does not bubble
|
|
25
|
+
// @event close — panel removed (after the exit animation); does not bubble
|
|
26
26
|
// @part field, input, label, panel, dial
|
|
27
27
|
// @vars see `t` below (`themeVars.names`)
|
|
28
28
|
|
|
@@ -34,6 +34,7 @@ import { presence } from '../motion/presence.js';
|
|
|
34
34
|
import { fx } from '../motion/animate.js';
|
|
35
35
|
import { autoUpdate } from '../util/position.js';
|
|
36
36
|
import { rovingTabindex } from '../util/keys.js';
|
|
37
|
+
import { popupEvent } from '../util/popup.js';
|
|
37
38
|
import './ui-icon-button.js';
|
|
38
39
|
|
|
39
40
|
const TIME = /^(\d{1,2}):(\d{2})$/;
|
|
@@ -215,7 +216,7 @@ const styles = css`
|
|
|
215
216
|
|
|
216
217
|
.panel {
|
|
217
218
|
position: fixed;
|
|
218
|
-
z-index: ${sys.z.
|
|
219
|
+
z-index: ${sys.z.popup};
|
|
219
220
|
display: flex;
|
|
220
221
|
flex-direction: column;
|
|
221
222
|
gap: ${sys.space(3)};
|
|
@@ -742,8 +743,8 @@ define('ui-time-picker', {
|
|
|
742
743
|
exit: fx.scaleOut,
|
|
743
744
|
enterDuration: 'short4',
|
|
744
745
|
exitDuration: 'short4',
|
|
745
|
-
onEntered: () => host.emit('open'),
|
|
746
|
-
onExited: () => host.emit('close'),
|
|
746
|
+
onEntered: () => host.emit('open', null, popupEvent),
|
|
747
|
+
onExited: () => host.emit('close', null, popupEvent),
|
|
747
748
|
})}
|
|
748
749
|
</div>`;
|
|
749
750
|
},
|
package/src/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export {
|
|
|
20
20
|
} from './tokens/typography.js';
|
|
21
21
|
|
|
22
22
|
// Motion
|
|
23
|
-
export { animate, settled, duration, easing, fx, prefersReducedMotion } from './motion/animate.js';
|
|
23
|
+
export { animate, releaseFill, settled, duration, easing, fx, prefersReducedMotion } from './motion/animate.js';
|
|
24
24
|
export { presence } from './motion/presence.js';
|
|
25
25
|
export { withFlip } from './motion/flip.js';
|
|
26
26
|
export { ripple } from './motion/ripple.js';
|
package/src/motion/animate.js
CHANGED
|
@@ -104,6 +104,20 @@ export function animate(el, keyframes, opts = {}) {
|
|
|
104
104
|
/** Await an animation without throwing when it is cancelled mid-flight. */
|
|
105
105
|
export const settled = (anim) => anim.finished.catch(() => {});
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Drop fill:both once an enter animation has settled.
|
|
109
|
+
*
|
|
110
|
+
* A leftover transform is a containing block for `position:fixed`
|
|
111
|
+
* descendants — a select inside a side sheet is then placed in viewport
|
|
112
|
+
* pixels against the surface, so the list sits beside the sheet, under
|
|
113
|
+
* it, or clipped by the body's overflow. presence() already releases
|
|
114
|
+
* the overlay fade; surfaces animated directly need the same.
|
|
115
|
+
*/
|
|
116
|
+
export function releaseFill(anim) {
|
|
117
|
+
anim.finished.then(() => { try { anim.cancel(); } catch { /* already cancelled */ } });
|
|
118
|
+
return anim;
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
// ------------------------------------------------------------------ presets
|
|
108
122
|
//
|
|
109
123
|
// Keyframe presets tuned to the Material motion spec. Enter presets pair with
|
package/src/motion/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { animate, settled, duration, easing, fx, prefersReducedMotion } from './animate.js';
|
|
1
|
+
export { animate, releaseFill, settled, duration, easing, fx, prefersReducedMotion } from './animate.js';
|
|
2
2
|
export { presence } from './presence.js';
|
|
3
3
|
export { withFlip } from './flip.js';
|
|
4
4
|
export { ripple } from './ripple.js';
|
package/src/tokens/sys.js
CHANGED
package/src/tokens/system.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Popup lifecycle events share names with dialogs and sheets (`open` /
|
|
2
|
+
// `close`). They must not bubble: choosing a select option would otherwise
|
|
3
|
+
// look like the sheet dismissed itself. Listeners on the popup host still
|
|
4
|
+
// fire — emit dispatches on the host regardless of bubbles.
|
|
5
|
+
export const popupEvent = { bubbles: false };
|
package/types/index.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export interface AnimateOptions extends KeyframeAnimationOptions {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
export function animate(el: Element, keyframes: Keyframes, opts?: AnimateOptions): Pick<Animation, 'finished' | 'cancel' | 'finish' | 'play' | 'pause'>;
|
|
87
|
+
export function releaseFill<T extends { finished: Promise<unknown>; cancel(): void }>(anim: T): T;
|
|
87
88
|
export function settled(anim: { finished: Promise<unknown> }): Promise<void>;
|
|
88
89
|
export function duration(key: number | string): number;
|
|
89
90
|
export function easing(key: string): string;
|