@dorsk/tsumikit 0.67.0 → 0.68.0
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 +104 -7
- package/dist/components/atoms/Button.svelte +118 -10
- package/dist/components/atoms/Button.svelte.d.ts +4 -0
- package/dist/components/layouts/NavBar.svelte +184 -30
- package/dist/components/layouts/NavBar.svelte.d.ts +19 -3
- package/dist/components/molecules/FileButton.svelte +2 -2
- package/dist/components/molecules/FileButton.svelte.d.ts +1 -1
- package/dist/components/molecules/GlyphStack.svelte +12 -19
- package/dist/components/molecules/IconButton.svelte +1 -1
- package/dist/components/molecules/Popover.svelte +5 -6
- package/dist/components/molecules/Popover.svelte.d.ts +1 -1
- package/dist/components/molecules/RadialMenu.svelte +579 -0
- package/dist/components/molecules/RadialMenu.svelte.d.ts +42 -0
- package/dist/components/molecules/SortableList.svelte +720 -0
- package/dist/components/molecules/SortableList.svelte.d.ts +116 -0
- package/dist/components/molecules/sortable-list-order.d.ts +68 -0
- package/dist/components/molecules/sortable-list-order.js +133 -0
- package/dist/floating.d.ts +38 -0
- package/dist/floating.js +38 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/slide-select.d.ts +26 -0
- package/dist/slide-select.js +53 -0
- package/package.json +1 -1
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
// Horizontal
|
|
3
|
-
// `aria-current="page"
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// Horizontal navigation: a `<nav>` landmark of items, one marked current.
|
|
3
|
+
// An item with `href` is an `<a aria-current="page">`; one without is a
|
|
4
|
+
// `<button>` that sets the bindable `value`. When every item is a button the
|
|
5
|
+
// row is a `tablist` (roving tabindex, ←/→/Home/End, `aria-selected`).
|
|
6
|
+
// `placement="bar"` pins it to the bottom edge over `--z-nav` with safe-area
|
|
7
|
+
// padding; `variant="floating"` makes it an inset rounded card. `center`
|
|
8
|
+
// reserves a notch in the middle of the row, items split around it.
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
7
10
|
import Icon from '../atoms/Icon.svelte';
|
|
8
11
|
import type { IconName } from '../atoms/Icon.svelte';
|
|
9
12
|
|
|
10
13
|
export interface NavBarItem {
|
|
11
|
-
|
|
14
|
+
/** Renders an `<a>`; without it the item is a `<button>`. */
|
|
15
|
+
href?: string;
|
|
16
|
+
/** Selects the item through the bar's `value`. */
|
|
17
|
+
value?: string;
|
|
12
18
|
label: string;
|
|
13
19
|
icon?: IconName;
|
|
14
20
|
/** Rendered instead of `icon` when set. */
|
|
15
21
|
emoji?: string;
|
|
16
22
|
/** Count or short text pinned to the glyph. 0 / '' render nothing. */
|
|
17
23
|
badge?: number | string;
|
|
24
|
+
/** Overrides the `value` match. */
|
|
18
25
|
current?: boolean;
|
|
26
|
+
/** `preventDefault()` skips the `value` change. */
|
|
27
|
+
onclick?: (e: MouseEvent) => void;
|
|
28
|
+
/** Id of the panel a tab shows (`aria-controls`). */
|
|
29
|
+
controls?: string;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
let {
|
|
22
33
|
items,
|
|
23
34
|
label,
|
|
35
|
+
value = $bindable(),
|
|
36
|
+
onchange,
|
|
37
|
+
center,
|
|
24
38
|
placement = 'inline',
|
|
25
39
|
orientation = 'stacked',
|
|
40
|
+
variant = 'flush',
|
|
26
41
|
maxWidth,
|
|
27
42
|
class: klass = '',
|
|
28
43
|
style: styleProp = '',
|
|
@@ -31,11 +46,18 @@
|
|
|
31
46
|
items: NavBarItem[];
|
|
32
47
|
/** Accessible name of the `<nav>` landmark. */
|
|
33
48
|
label: string;
|
|
49
|
+
/** `value` of the current item, for in-page views without routes. */
|
|
50
|
+
value?: string;
|
|
51
|
+
onchange?: (value: string) => void;
|
|
52
|
+
/** Fills a notch in the middle of the row (`--navbar-center-w`), may rise above it (`--navbar-center-raise`). */
|
|
53
|
+
center?: Snippet;
|
|
34
54
|
/** 'bar' = fixed to the bottom edge with safe-area padding. */
|
|
35
55
|
placement?: 'bar' | 'inline';
|
|
36
56
|
/** Icon over label vs icon beside label. */
|
|
37
57
|
orientation?: 'stacked' | 'inline';
|
|
38
|
-
/**
|
|
58
|
+
/** 'floating' = rounded elevated card inset from the edges. */
|
|
59
|
+
variant?: 'flush' | 'floating';
|
|
60
|
+
/** Caps and centres the row of items. */
|
|
39
61
|
maxWidth?: string;
|
|
40
62
|
class?: string;
|
|
41
63
|
style?: string;
|
|
@@ -44,38 +66,127 @@
|
|
|
44
66
|
|
|
45
67
|
const hasBadge = (badge: NavBarItem['badge']) =>
|
|
46
68
|
badge !== undefined && badge !== null && badge !== '' && badge !== 0;
|
|
69
|
+
|
|
70
|
+
const tabs = $derived(items.length > 0 && items.every((item) => !item.href));
|
|
71
|
+
const isCurrent = (item: NavBarItem) =>
|
|
72
|
+
item.current ?? (item.value !== undefined && item.value === value);
|
|
73
|
+
const focusIndex = $derived(Math.max(0, items.findIndex(isCurrent)));
|
|
74
|
+
const split = $derived(Math.ceil(items.length / 2));
|
|
75
|
+
|
|
76
|
+
function select(item: NavBarItem, e: MouseEvent) {
|
|
77
|
+
item.onclick?.(e);
|
|
78
|
+
if (e.defaultPrevented || item.value === undefined || item.value === value) return;
|
|
79
|
+
value = item.value;
|
|
80
|
+
onchange?.(item.value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function onkeydown(e: KeyboardEvent) {
|
|
84
|
+
const list = e.currentTarget as HTMLElement;
|
|
85
|
+
const all = [...list.querySelectorAll<HTMLButtonElement>('[role="tab"]')];
|
|
86
|
+
const i = all.indexOf(e.target as HTMLButtonElement);
|
|
87
|
+
if (i < 0) return;
|
|
88
|
+
let next: number;
|
|
89
|
+
if (e.key === 'ArrowRight') next = (i + 1) % all.length;
|
|
90
|
+
else if (e.key === 'ArrowLeft') next = (i - 1 + all.length) % all.length;
|
|
91
|
+
else if (e.key === 'Home') next = 0;
|
|
92
|
+
else if (e.key === 'End') next = all.length - 1;
|
|
93
|
+
else return;
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
all[next].focus();
|
|
96
|
+
all[next].click();
|
|
97
|
+
}
|
|
47
98
|
</script>
|
|
48
99
|
|
|
100
|
+
{#snippet glyph(item: NavBarItem)}
|
|
101
|
+
<span class="navbar-glyph">
|
|
102
|
+
{#if item.emoji}
|
|
103
|
+
<span class="navbar-emoji" aria-hidden="true">{item.emoji}</span>
|
|
104
|
+
{:else if item.icon}
|
|
105
|
+
<Icon name={item.icon} size={20} />
|
|
106
|
+
{/if}
|
|
107
|
+
{#if hasBadge(item.badge)}
|
|
108
|
+
<span class="navbar-badge">{item.badge}</span>
|
|
109
|
+
{/if}
|
|
110
|
+
</span>
|
|
111
|
+
<span class="navbar-label">{item.label}</span>
|
|
112
|
+
{/snippet}
|
|
113
|
+
|
|
114
|
+
{#snippet entry(item: NavBarItem, i: number)}
|
|
115
|
+
{#if item.href}
|
|
116
|
+
<a
|
|
117
|
+
href={item.href}
|
|
118
|
+
class="navbar-link"
|
|
119
|
+
class:current={isCurrent(item)}
|
|
120
|
+
aria-current={isCurrent(item) ? 'page' : undefined}
|
|
121
|
+
onclick={(e) => select(item, e)}
|
|
122
|
+
>
|
|
123
|
+
{@render glyph(item)}
|
|
124
|
+
</a>
|
|
125
|
+
{:else if tabs}
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
role="tab"
|
|
129
|
+
class="navbar-link"
|
|
130
|
+
class:current={isCurrent(item)}
|
|
131
|
+
aria-selected={isCurrent(item)}
|
|
132
|
+
aria-controls={item.controls}
|
|
133
|
+
tabindex={i === focusIndex ? 0 : -1}
|
|
134
|
+
onclick={(e) => select(item, e)}
|
|
135
|
+
>
|
|
136
|
+
{@render glyph(item)}
|
|
137
|
+
</button>
|
|
138
|
+
{:else}
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
class="navbar-link"
|
|
142
|
+
class:current={isCurrent(item)}
|
|
143
|
+
aria-current={isCurrent(item) ? 'page' : undefined}
|
|
144
|
+
onclick={(e) => select(item, e)}
|
|
145
|
+
>
|
|
146
|
+
{@render glyph(item)}
|
|
147
|
+
</button>
|
|
148
|
+
{/if}
|
|
149
|
+
{/snippet}
|
|
150
|
+
|
|
49
151
|
<nav
|
|
50
152
|
data-tsu="NavBar"
|
|
51
153
|
aria-label={label}
|
|
52
154
|
class="navbar {klass}"
|
|
53
155
|
class:bar={placement === 'bar'}
|
|
156
|
+
class:floating={variant === 'floating'}
|
|
54
157
|
class:stacked={orientation === 'stacked'}
|
|
55
158
|
style="{maxWidth ? `--navbar-max: ${maxWidth};` : ''}{styleProp}"
|
|
56
159
|
{...rest}
|
|
57
160
|
>
|
|
58
|
-
<div class="navbar-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
161
|
+
<div class="navbar-row">
|
|
162
|
+
<div
|
|
163
|
+
class="navbar-inner"
|
|
164
|
+
class:split={!!center}
|
|
165
|
+
role={tabs ? 'tablist' : undefined}
|
|
166
|
+
aria-label={tabs ? label : undefined}
|
|
167
|
+
onkeydown={tabs ? onkeydown : undefined}
|
|
168
|
+
>
|
|
169
|
+
{#if center}
|
|
170
|
+
<div class="navbar-side" role="none">
|
|
171
|
+
{#each items.slice(0, split) as item, i (`${i}:${item.href ?? item.value ?? item.label}`)}
|
|
172
|
+
{@render entry(item, i)}
|
|
173
|
+
{/each}
|
|
174
|
+
</div>
|
|
175
|
+
<span class="navbar-notch" aria-hidden="true"></span>
|
|
176
|
+
<div class="navbar-side" role="none">
|
|
177
|
+
{#each items.slice(split) as item, j (`${split + j}:${item.href ?? item.value ?? item.label}`)}
|
|
178
|
+
{@render entry(item, split + j)}
|
|
179
|
+
{/each}
|
|
180
|
+
</div>
|
|
181
|
+
{:else}
|
|
182
|
+
{#each items as item, i (`${i}:${item.href ?? item.value ?? item.label}`)}
|
|
183
|
+
{@render entry(item, i)}
|
|
184
|
+
{/each}
|
|
185
|
+
{/if}
|
|
186
|
+
</div>
|
|
187
|
+
{#if center}
|
|
188
|
+
<div class="navbar-center">{@render center()}</div>
|
|
189
|
+
{/if}
|
|
79
190
|
</div>
|
|
80
191
|
</nav>
|
|
81
192
|
|
|
@@ -84,16 +195,41 @@
|
|
|
84
195
|
--navbar-max: none;
|
|
85
196
|
min-width: 0;
|
|
86
197
|
}
|
|
198
|
+
.navbar-row {
|
|
199
|
+
position: relative;
|
|
200
|
+
min-width: 0;
|
|
201
|
+
max-width: var(--navbar-max);
|
|
202
|
+
margin-inline: auto;
|
|
203
|
+
}
|
|
87
204
|
.navbar-inner {
|
|
88
205
|
display: flex;
|
|
89
206
|
align-items: stretch;
|
|
90
207
|
justify-content: space-around;
|
|
91
208
|
gap: var(--sp-1);
|
|
92
209
|
min-width: 0;
|
|
93
|
-
max-width: var(--navbar-max);
|
|
94
|
-
margin-inline: auto;
|
|
95
210
|
min-height: var(--nav-h);
|
|
96
211
|
}
|
|
212
|
+
.navbar-inner.split {
|
|
213
|
+
display: grid;
|
|
214
|
+
grid-template-columns: minmax(0, 1fr) var(--navbar-center-w, 4.5rem) minmax(0, 1fr);
|
|
215
|
+
}
|
|
216
|
+
.navbar-side {
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: stretch;
|
|
219
|
+
gap: var(--sp-1);
|
|
220
|
+
min-width: 0;
|
|
221
|
+
}
|
|
222
|
+
.navbar-center {
|
|
223
|
+
position: absolute;
|
|
224
|
+
top: calc(-1 * var(--navbar-center-raise, 0px));
|
|
225
|
+
bottom: var(--navbar-center-raise, 0px);
|
|
226
|
+
left: 50%;
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
230
|
+
width: var(--navbar-center-w, 4.5rem);
|
|
231
|
+
transform: translateX(-50%);
|
|
232
|
+
}
|
|
97
233
|
.bar {
|
|
98
234
|
position: fixed;
|
|
99
235
|
inset-inline: 0;
|
|
@@ -105,21 +241,39 @@
|
|
|
105
241
|
backdrop-filter: blur(12px);
|
|
106
242
|
border-top: 1px solid var(--border);
|
|
107
243
|
}
|
|
244
|
+
.floating {
|
|
245
|
+
padding: var(--sp-1);
|
|
246
|
+
border: 1px solid var(--border-strong);
|
|
247
|
+
border-radius: var(--r-md);
|
|
248
|
+
background: var(--bg-elevated);
|
|
249
|
+
box-shadow: var(--shadow-md);
|
|
250
|
+
}
|
|
251
|
+
.bar.floating {
|
|
252
|
+
left: calc(var(--sp-2) + var(--safe-left));
|
|
253
|
+
right: calc(var(--sp-2) + var(--safe-right));
|
|
254
|
+
bottom: calc(var(--sp-2) + var(--safe-bottom));
|
|
255
|
+
backdrop-filter: none;
|
|
256
|
+
}
|
|
108
257
|
.navbar-link {
|
|
109
258
|
position: relative;
|
|
110
259
|
display: flex;
|
|
111
260
|
flex: 1 1 0;
|
|
112
261
|
min-width: 0;
|
|
262
|
+
min-height: var(--touch-target);
|
|
113
263
|
flex-direction: row;
|
|
114
264
|
align-items: center;
|
|
115
265
|
justify-content: center;
|
|
116
266
|
gap: var(--sp-2);
|
|
117
267
|
padding: var(--sp-2);
|
|
268
|
+
border: none;
|
|
118
269
|
border-radius: var(--r-md);
|
|
270
|
+
background: none;
|
|
119
271
|
color: var(--text-muted);
|
|
272
|
+
font: inherit;
|
|
120
273
|
font-size: var(--fs-sm);
|
|
121
274
|
font-weight: var(--fw-medium);
|
|
122
275
|
text-decoration: none;
|
|
276
|
+
cursor: pointer;
|
|
123
277
|
transition:
|
|
124
278
|
background 0.12s var(--ease),
|
|
125
279
|
color 0.12s var(--ease);
|
|
@@ -1,28 +1,44 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import type { IconName } from '../atoms/Icon.svelte';
|
|
2
3
|
export interface NavBarItem {
|
|
3
|
-
|
|
4
|
+
/** Renders an `<a>`; without it the item is a `<button>`. */
|
|
5
|
+
href?: string;
|
|
6
|
+
/** Selects the item through the bar's `value`. */
|
|
7
|
+
value?: string;
|
|
4
8
|
label: string;
|
|
5
9
|
icon?: IconName;
|
|
6
10
|
/** Rendered instead of `icon` when set. */
|
|
7
11
|
emoji?: string;
|
|
8
12
|
/** Count or short text pinned to the glyph. 0 / '' render nothing. */
|
|
9
13
|
badge?: number | string;
|
|
14
|
+
/** Overrides the `value` match. */
|
|
10
15
|
current?: boolean;
|
|
16
|
+
/** `preventDefault()` skips the `value` change. */
|
|
17
|
+
onclick?: (e: MouseEvent) => void;
|
|
18
|
+
/** Id of the panel a tab shows (`aria-controls`). */
|
|
19
|
+
controls?: string;
|
|
11
20
|
}
|
|
12
21
|
type $$ComponentProps = {
|
|
13
22
|
items: NavBarItem[];
|
|
14
23
|
/** Accessible name of the `<nav>` landmark. */
|
|
15
24
|
label: string;
|
|
25
|
+
/** `value` of the current item, for in-page views without routes. */
|
|
26
|
+
value?: string;
|
|
27
|
+
onchange?: (value: string) => void;
|
|
28
|
+
/** Fills a notch in the middle of the row (`--navbar-center-w`), may rise above it (`--navbar-center-raise`). */
|
|
29
|
+
center?: Snippet;
|
|
16
30
|
/** 'bar' = fixed to the bottom edge with safe-area padding. */
|
|
17
31
|
placement?: 'bar' | 'inline';
|
|
18
32
|
/** Icon over label vs icon beside label. */
|
|
19
33
|
orientation?: 'stacked' | 'inline';
|
|
20
|
-
/**
|
|
34
|
+
/** 'floating' = rounded elevated card inset from the edges. */
|
|
35
|
+
variant?: 'flush' | 'floating';
|
|
36
|
+
/** Caps and centres the row of items. */
|
|
21
37
|
maxWidth?: string;
|
|
22
38
|
class?: string;
|
|
23
39
|
style?: string;
|
|
24
40
|
[key: string]: unknown;
|
|
25
41
|
};
|
|
26
|
-
declare const NavBar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
42
|
+
declare const NavBar: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
27
43
|
type NavBar = ReturnType<typeof NavBar>;
|
|
28
44
|
export default NavBar;
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
control?: boolean;
|
|
46
46
|
/** Shared square box scale (`--box-xs/sm/md/lg`); implies `iconOnly`. */
|
|
47
47
|
box?: 'xs' | 'sm' | 'md' | 'lg';
|
|
48
|
-
/** Icon-only buttons grow a
|
|
48
|
+
/** Icon-only buttons grow a touch hit slab on coarse pointers; `compact` opts out. */
|
|
49
49
|
hitArea?: 'auto' | 'compact';
|
|
50
50
|
class?: string;
|
|
51
51
|
} = $props();
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
content: '';
|
|
159
159
|
position: absolute;
|
|
160
160
|
inset-block: min(0px, calc((100% - var(--touch-target)) / 2));
|
|
161
|
-
inset-inline-start: min(0px, calc(100% - var(--touch-target)));
|
|
161
|
+
inset-inline-start: min(0px, calc((100% - var(--touch-target)) / 2));
|
|
162
162
|
inset-inline-end: 0;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
@@ -24,7 +24,7 @@ type $$ComponentProps = {
|
|
|
24
24
|
control?: boolean;
|
|
25
25
|
/** Shared square box scale (`--box-xs/sm/md/lg`); implies `iconOnly`. */
|
|
26
26
|
box?: 'xs' | 'sm' | 'md' | 'lg';
|
|
27
|
-
/** Icon-only buttons grow a
|
|
27
|
+
/** Icon-only buttons grow a touch hit slab on coarse pointers; `compact` opts out. */
|
|
28
28
|
hitArea?: 'auto' | 'compact';
|
|
29
29
|
class?: string;
|
|
30
30
|
};
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { tick } from 'svelte';
|
|
21
21
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
22
22
|
import { browser } from '../../env';
|
|
23
|
+
import { slideSelect } from '../../slide-select';
|
|
23
24
|
|
|
24
25
|
type Own = {
|
|
25
26
|
/** Glyphs as `{ inline, stacked? }` pairs; alternative to `children`. */
|
|
@@ -78,7 +79,6 @@
|
|
|
78
79
|
return el.parentElement;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
const SLIDE_THRESHOLD = 12;
|
|
82
82
|
const CONTROL = 'button, a[href], input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])';
|
|
83
83
|
|
|
84
84
|
const tiles = $derived(items?.slice(0, 4) ?? []);
|
|
@@ -86,7 +86,6 @@
|
|
|
86
86
|
let panel = $state<HTMLDivElement>();
|
|
87
87
|
let open = $state(false);
|
|
88
88
|
let armed = $state(-1);
|
|
89
|
-
let origin: { x: number; y: number } | null = null;
|
|
90
89
|
|
|
91
90
|
function show() {
|
|
92
91
|
try {
|
|
@@ -129,11 +128,13 @@
|
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/** Tile under a drag from the press point: 0 top-left, 1 top-right, 2 bottom-left, 3 bottom-right. */
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
const slide = slideSelect({
|
|
132
|
+
pick: ({ dx, dy }) => {
|
|
133
|
+
const i = (dy < 0 ? 0 : 2) + (dx < 0 ? 0 : 1);
|
|
134
|
+
return i < tiles.length ? i : -1;
|
|
135
|
+
},
|
|
136
|
+
onarm: (i) => (armed = i),
|
|
137
|
+
});
|
|
137
138
|
|
|
138
139
|
async function activate(i: number) {
|
|
139
140
|
const { action } = tiles[i];
|
|
@@ -153,29 +154,21 @@
|
|
|
153
154
|
if (!expandable || expand !== 'slide' || e.button !== 0) return;
|
|
154
155
|
e.preventDefault();
|
|
155
156
|
e.stopPropagation();
|
|
156
|
-
|
|
157
|
-
armed = -1;
|
|
158
|
-
try {
|
|
159
|
-
root?.setPointerCapture(e.pointerId);
|
|
160
|
-
} catch {}
|
|
157
|
+
slide.down(e, root);
|
|
161
158
|
show();
|
|
162
159
|
}
|
|
163
160
|
|
|
164
161
|
function onPointerMove(e: PointerEvent) {
|
|
165
|
-
|
|
162
|
+
slide.move(e);
|
|
166
163
|
}
|
|
167
164
|
|
|
168
165
|
function onPointerUp() {
|
|
169
|
-
|
|
170
|
-
origin = null;
|
|
171
|
-
const i = armed;
|
|
172
|
-
armed = -1;
|
|
166
|
+
const i = slide.up();
|
|
173
167
|
if (i >= 0) activate(i);
|
|
174
168
|
}
|
|
175
169
|
|
|
176
170
|
function onPointerCancel() {
|
|
177
|
-
|
|
178
|
-
armed = -1;
|
|
171
|
+
slide.cancel();
|
|
179
172
|
}
|
|
180
173
|
|
|
181
174
|
// Pointer clicks on the folded cells open the panel instead of hitting the
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// Exact glyph size for SVG *and* text glyphs (px number or any CSS length),
|
|
35
35
|
// no emoji multiplier. Wins over `size`.
|
|
36
36
|
glyphSize?: number | string;
|
|
37
|
-
// Opt out of the
|
|
37
|
+
// Opt out of the coarse-pointer hit slab in dense rows.
|
|
38
38
|
hitArea?: 'auto' | 'compact';
|
|
39
39
|
// Borderless, compact icon affordance (chip-remove ✕, inline edit ✎) —
|
|
40
40
|
// no square box; just a muted glyph that brightens on hover. Pair with
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
/** Use the shared `--control-height` toolbar/composer contract. */
|
|
62
62
|
control?: boolean;
|
|
63
63
|
block?: boolean;
|
|
64
|
-
/** Icon-only triggers grow a
|
|
64
|
+
/** Icon-only triggers grow a touch hit slab on coarse pointers; `compact` opts out. */
|
|
65
65
|
hitArea?: 'auto' | 'compact';
|
|
66
66
|
/** Corner count indicator on the trigger, as on `Button`: hidden at
|
|
67
67
|
* 0/undefined, capped at `countMax+`, the exact number joins `label`. */
|
|
@@ -449,10 +449,9 @@
|
|
|
449
449
|
padding: var(--pop-trigger-pad, 0);
|
|
450
450
|
flex: none;
|
|
451
451
|
}
|
|
452
|
-
/* Coarse pointers: icon-only triggers extend their hit area
|
|
453
|
-
via an invisible slab; layout does not move.
|
|
454
|
-
inline
|
|
455
|
-
(see Button.svelte). */
|
|
452
|
+
/* Coarse pointers: icon-only triggers extend their hit area towards --touch-target
|
|
453
|
+
via an invisible slab; layout does not move. Same geometry as Button.svelte:
|
|
454
|
+
no inline-end growth, half the deficit towards the inline start. */
|
|
456
455
|
@media (pointer: coarse) {
|
|
457
456
|
.pop-trigger:not(.canonical, .hit-compact) {
|
|
458
457
|
position: relative;
|
|
@@ -461,7 +460,7 @@
|
|
|
461
460
|
content: '';
|
|
462
461
|
position: absolute;
|
|
463
462
|
inset-block: min(0px, calc((100% - var(--touch-target)) / 2));
|
|
464
|
-
inset-inline-start: min(0px, calc(100% - var(--touch-target)));
|
|
463
|
+
inset-inline-start: min(0px, calc((100% - var(--touch-target)) / 2));
|
|
465
464
|
inset-inline-end: 0;
|
|
466
465
|
}
|
|
467
466
|
}
|
|
@@ -46,7 +46,7 @@ type Own = {
|
|
|
46
46
|
/** Use the shared `--control-height` toolbar/composer contract. */
|
|
47
47
|
control?: boolean;
|
|
48
48
|
block?: boolean;
|
|
49
|
-
/** Icon-only triggers grow a
|
|
49
|
+
/** Icon-only triggers grow a touch hit slab on coarse pointers; `compact` opts out. */
|
|
50
50
|
hitArea?: 'auto' | 'compact';
|
|
51
51
|
/** Corner count indicator on the trigger, as on `Button`: hidden at
|
|
52
52
|
* 0/undefined, capped at `countMax+`, the exact number joins `label`. */
|