@lyeve-labs/ui-kit 0.38.4 → 0.38.5
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/dist/components/DatePicker.svelte +22 -6
- package/dist/components/Drawer.svelte +15 -1
- package/dist/components/MultiSelect.svelte +15 -6
- package/dist/components/Tag.svelte +11 -2
- package/dist/components/TreeView.svelte +11 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/panel.d.ts +1 -1
- package/dist/internal/panel.js +10 -4
- package/dist/styles/theme.css +5 -2
- package/package.json +1 -1
- package/src/lib/styles/theme.css +5 -2
|
@@ -253,8 +253,11 @@
|
|
|
253
253
|
days have to sit 44px apart or each box covers a third of its
|
|
254
254
|
neighbors'. Seven 44px columns are 308px. Inside a 1px border and 6px
|
|
255
255
|
of padding that is a 320px panel, the width of the narrowest phone,
|
|
256
|
-
and placePanel slides it back on screen from the page gutter.
|
|
257
|
-
|
|
256
|
+
and placePanel slides it back on screen from the page gutter. In a
|
|
257
|
+
modal body narrower than that, placePanel shrinks it to the body and
|
|
258
|
+
the columns close up, but never below the grid: seven 32px days, six
|
|
259
|
+
2px gaps, the padding and the border, or the days would overflow and
|
|
260
|
+
the calendar scroll sideways inside itself. The rows open a 12px gap for the same reason.
|
|
258
261
|
-->
|
|
259
262
|
<div
|
|
260
263
|
id={calendarId}
|
|
@@ -262,14 +265,17 @@
|
|
|
262
265
|
aria-label="Choose date"
|
|
263
266
|
use:placePanel
|
|
264
267
|
transition:motion.popover|global
|
|
265
|
-
class="{PANEL_SURFACE} w-68 coarse:w-80"
|
|
268
|
+
class="{PANEL_SURFACE} w-68 min-w-65.5 coarse:w-80 coarse:min-w-62.5"
|
|
266
269
|
>
|
|
267
270
|
<div
|
|
268
271
|
class="overflow-y-auto overscroll-contain p-3 coarse:p-1.5"
|
|
269
272
|
data-panel-list={PANEL_LIST_UNCAPPED}
|
|
270
273
|
>
|
|
271
|
-
<!-- Header
|
|
272
|
-
|
|
274
|
+
<!-- Header. Under a finger each 26px month step takes a 44px box,
|
|
275
|
+
9px past it, and the region's padding is 6px, so the header
|
|
276
|
+
insets itself by the other 3px at the sides and the top. The
|
|
277
|
+
boxes stay whole inside the calendar, which no longer pans. -->
|
|
278
|
+
<div class="flex items-center justify-between mb-2 coarse:px-0.75 coarse:pt-0.75">
|
|
273
279
|
<button
|
|
274
280
|
type="button"
|
|
275
281
|
aria-label="Previous month"
|
|
@@ -313,7 +319,17 @@
|
|
|
313
319
|
</div>
|
|
314
320
|
|
|
315
321
|
<!-- Day grid -->
|
|
316
|
-
|
|
322
|
+
<!--
|
|
323
|
+
Shrunk into a narrow modal, the columns close below 44px and the
|
|
324
|
+
day boxes would overlap. The grid is a container, so each box
|
|
325
|
+
takes the smaller of the control size and one column's pitch, the
|
|
326
|
+
grid's width and one gap over seven, and keeps its height. The
|
|
327
|
+
boxes meet at any width.
|
|
328
|
+
-->
|
|
329
|
+
<div
|
|
330
|
+
class="@container grid grid-cols-7 gap-0.5 coarse:gap-y-3 coarse:[--hit-width:min(var(--spacing-control),calc((100cqw_+_0.125rem)_/_7))]"
|
|
331
|
+
data-day-grid
|
|
332
|
+
>
|
|
317
333
|
{#each cells as d}
|
|
318
334
|
{#if d === null}
|
|
319
335
|
<span></span>
|
|
@@ -66,6 +66,19 @@
|
|
|
66
66
|
|
|
67
67
|
const rung = $derived(size === 'auto' ? fitOverlay(fields) : size);
|
|
68
68
|
|
|
69
|
+
let frameEl = $state<HTMLDivElement | undefined>();
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The edge the panel slides from. It docks by the start and end of the
|
|
73
|
+
* line, so `right` sits at the left edge of a right-to-left page, and a
|
|
74
|
+
* slide from the right would cross the whole screen to get there. Read when
|
|
75
|
+
* the panel moves, as AppShell reads its own drawer's.
|
|
76
|
+
*/
|
|
77
|
+
function edge(): Side {
|
|
78
|
+
const rtl = frameEl !== undefined && getComputedStyle(frameEl).direction === 'rtl';
|
|
79
|
+
return (side === 'right') !== rtl ? 'right' : 'left';
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
const headingId = $props.id();
|
|
70
83
|
const descriptionId = `${headingId}-description`;
|
|
71
84
|
|
|
@@ -86,6 +99,7 @@
|
|
|
86
99
|
|
|
87
100
|
{#if open}
|
|
88
101
|
<div
|
|
102
|
+
bind:this={frameEl}
|
|
89
103
|
data-print="hide"
|
|
90
104
|
class="fixed inset-0 z-drawer flex {side === 'right' ? 'justify-end' : 'justify-start'}"
|
|
91
105
|
>
|
|
@@ -100,7 +114,7 @@
|
|
|
100
114
|
|
|
101
115
|
<div
|
|
102
116
|
use:overlay
|
|
103
|
-
transition:motion.drawer|global={{ side }}
|
|
117
|
+
transition:motion.drawer|global={{ side: edge() }}
|
|
104
118
|
class="relative flex h-full w-full flex-col {OVERLAY_WIDTH[rung]} bg-surface shadow-2xl
|
|
105
119
|
{side === 'right' ? 'border-s' : 'border-e'} border-line"
|
|
106
120
|
role="dialog"
|
|
@@ -178,12 +178,18 @@
|
|
|
178
178
|
which a screen reader reads as one widget and a keyboard cannot sort
|
|
179
179
|
out. A press anywhere on the frame that is not a remove button opens the
|
|
180
180
|
list from the combobox, as a press on the field always did.
|
|
181
|
+
|
|
182
|
+
Under a finger each remove cross takes a 44px box on a 20px chip, which
|
|
183
|
+
reaches 12px above and below the chip and 8px past its end. The chips
|
|
184
|
+
sit 8px apart there and their rows 24px apart, so no box covers the
|
|
185
|
+
next chip or the row below, and a tap meant to open the list lands on
|
|
186
|
+
the field.
|
|
181
187
|
-->
|
|
182
188
|
<div
|
|
183
189
|
role="presentation"
|
|
184
190
|
onclick={onFrameClick}
|
|
185
|
-
class="flex min-h-control w-full cursor-pointer flex-wrap items-center gap-1.5 rounded-lg
|
|
186
|
-
border bg-surface-2
|
|
191
|
+
class="relative flex min-h-control w-full cursor-pointer flex-wrap items-center gap-1.5 coarse:gap-x-2 coarse:gap-y-6 rounded-lg
|
|
192
|
+
border bg-surface-2 py-1.5 ps-2.5 pe-7 text-start text-sm transition-colors
|
|
187
193
|
has-[[role=combobox]:focus-visible]:ring-2 has-[[role=combobox]:focus-visible]:ring-inset
|
|
188
194
|
has-[[role=combobox]:focus-visible]:ring-brand
|
|
189
195
|
{disabled ? 'cursor-not-allowed opacity-50' : ''}
|
|
@@ -193,8 +199,11 @@
|
|
|
193
199
|
A div and not an input: there is nothing to type here. The labels of
|
|
194
200
|
the chosen values are its text, so focusing it reads the label and
|
|
195
201
|
then what is chosen, as a select reads its value. It comes first in
|
|
196
|
-
the markup, so Tab reaches the field before any remove button, and
|
|
197
|
-
|
|
202
|
+
the markup, so Tab reaches the field before any remove button, and it
|
|
203
|
+
spans the frame as the field itself, so it comes first on screen too.
|
|
204
|
+
A press on a chip lands on it. The remove buttons are positioned and
|
|
205
|
+
later in the markup, so they sit above it and take their own presses.
|
|
206
|
+
The frame keeps room at its end for the chevron.
|
|
198
207
|
-->
|
|
199
208
|
<div
|
|
200
209
|
bind:this={triggerEl}
|
|
@@ -207,10 +216,10 @@
|
|
|
207
216
|
aria-describedby={describedBy(fieldId, error, hint)}
|
|
208
217
|
onkeydown={onTriggerKeydown}
|
|
209
218
|
{...box.triggerAttrs}
|
|
210
|
-
class="
|
|
219
|
+
class="absolute inset-0 flex items-center gap-1.5 rounded-lg px-2.5 outline-none"
|
|
211
220
|
>
|
|
212
221
|
{#if selected.length === 0}
|
|
213
|
-
<span class="px-0.5 text-faint">{placeholder}</span>
|
|
222
|
+
<span class="min-w-0 truncate px-0.5 text-faint">{placeholder}</span>
|
|
214
223
|
{:else}
|
|
215
224
|
<span class="sr-only">{selected.map((option) => option.label).join(', ')}</span>
|
|
216
225
|
{/if}
|
|
@@ -28,9 +28,18 @@
|
|
|
28
28
|
};
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
|
+
<!--
|
|
32
|
+
Under a finger the remove cross takes a 44px box on a 22px tag, which
|
|
33
|
+
reaches 11px above and below the tag and 8px past its end. A removable tag
|
|
34
|
+
claims that room as margin there, so the boxes in a row of tags meet
|
|
35
|
+
without overlapping, wrapped or not, whatever gap the row sets. That
|
|
36
|
+
margin makes a removable tag 44px tall in a row, so every tag centers
|
|
37
|
+
itself, and a plain tag beside it neither stretches to 44px nor sits at
|
|
38
|
+
the top of the line, whatever the row aligns.
|
|
39
|
+
-->
|
|
31
40
|
<span
|
|
32
|
-
class="inline-flex items-center gap-1 rounded-md border px-2 py-0.5
|
|
33
|
-
text-xs font-medium {tones[tone]} {cls}"
|
|
41
|
+
class="inline-flex items-center self-center gap-1 rounded-md border px-2 py-0.5
|
|
42
|
+
text-xs font-medium {removable ? 'coarse:my-2.75 coarse:me-2' : ''} {tones[tone]} {cls}"
|
|
34
43
|
>
|
|
35
44
|
{label}
|
|
36
45
|
{#if removable}
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
const expandedSet = $derived(new Set(expanded));
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Branches whose children
|
|
49
|
-
* collapse
|
|
50
|
-
* nothing, so a tree of thousands of nodes renders
|
|
51
|
-
* not the whole tree.
|
|
48
|
+
* Branches whose children are mounted. A branch stays mounted after a
|
|
49
|
+
* collapse until the collapse has played, and then unmounts. A branch nobody
|
|
50
|
+
* opened mounts nothing, so a tree of thousands of nodes renders the rows a
|
|
51
|
+
* reader has open and not the whole tree, nor every row it ever showed.
|
|
52
52
|
*/
|
|
53
53
|
const shown = $state<Record<string, boolean>>({});
|
|
54
54
|
$effect.pre(() => {
|
|
@@ -347,10 +347,16 @@
|
|
|
347
347
|
A branch opens and shuts on grid-template-rows, the AccordionItem
|
|
348
348
|
collapse, so its children slide rather than appear in one frame. Shut,
|
|
349
349
|
they are inert: out of the tab order and the accessibility tree while
|
|
350
|
-
they stay mounted for the collapse to play
|
|
350
|
+
they stay mounted for the collapse to play, and they unmount when it
|
|
351
|
+
ends. A nested branch's transition bubbles up here, so only this
|
|
352
|
+
wrapper's own counts.
|
|
351
353
|
-->
|
|
352
354
|
{#if isBranch(node)}
|
|
353
355
|
<div
|
|
356
|
+
ontransitionend={(event) => {
|
|
357
|
+
if (event.target !== event.currentTarget || open) return;
|
|
358
|
+
if (event.propertyName === 'grid-template-rows') shown[node.id] = false;
|
|
359
|
+
}}
|
|
354
360
|
class="grid transition-[grid-template-rows] duration-base {open
|
|
355
361
|
? 'grid-rows-[1fr]'
|
|
356
362
|
: 'grid-rows-[0fr]'}"
|
package/dist/index.d.ts
CHANGED
|
@@ -103,4 +103,4 @@ export { cn, type ClassValue } from './utils/cn.js';
|
|
|
103
103
|
export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, watchThemePreference, toggleTheme, watchSystemTheme, THEME_PREFERENCES, type Theme, type ThemePreference, } from './utils/theme.js';
|
|
104
104
|
export * as motion from './motion.js';
|
|
105
105
|
export type { Rung as MotionRung, Curve as MotionCurve } from './motion.js';
|
|
106
|
-
export declare const VERSION = "0.38.
|
|
106
|
+
export declare const VERSION = "0.38.5";
|
package/dist/index.js
CHANGED
|
@@ -116,4 +116,4 @@ export * as motion from './motion.js';
|
|
|
116
116
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
117
117
|
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
118
118
|
// this line. The build and the test suite fail when the two disagree.
|
|
119
|
-
export const VERSION = '0.38.
|
|
119
|
+
export const VERSION = '0.38.5';
|
package/dist/internal/panel.d.ts
CHANGED
|
@@ -160,7 +160,7 @@ export declare const PANEL_ABOVE = "bottom-full mb-1 origin-bottom";
|
|
|
160
160
|
*
|
|
161
161
|
* A panel that would run past the left or the right edge of the clipping
|
|
162
162
|
* ancestor or the viewport slides back inside both, and one wider than that
|
|
163
|
-
* room
|
|
163
|
+
* room shrinks to it.
|
|
164
164
|
*
|
|
165
165
|
* Measured on mount, on resize and on scroll of the clipping ancestor, since
|
|
166
166
|
* each moves the anchor relative to the edge. Scroll does not bubble, so the
|
package/dist/internal/panel.js
CHANGED
|
@@ -199,7 +199,7 @@ function swap(node, remove, add) {
|
|
|
199
199
|
*
|
|
200
200
|
* A panel that would run past the left or the right edge of the clipping
|
|
201
201
|
* ancestor or the viewport slides back inside both, and one wider than that
|
|
202
|
-
* room
|
|
202
|
+
* room shrinks to it.
|
|
203
203
|
*
|
|
204
204
|
* Measured on mount, on resize and on scroll of the clipping ancestor, since
|
|
205
205
|
* each moves the anchor relative to the edge. Scroll does not bubble, so the
|
|
@@ -235,12 +235,18 @@ export function placePanel(node) {
|
|
|
235
235
|
// A panel wider than the room beside its anchor slides back inside what
|
|
236
236
|
// the reader can see, which is the clip and the viewport both: the
|
|
237
237
|
// calendar under a finger is as wide as a 320px phone, and its field
|
|
238
|
-
// starts at the page gutter or inside a modal body's padding.
|
|
239
|
-
// wider than that room it
|
|
240
|
-
//
|
|
238
|
+
// starts at the page gutter or inside a modal body's padding. A panel
|
|
239
|
+
// wider than that room shrinks to it, since a modal body on a 320px phone
|
|
240
|
+
// is narrower than the calendar and scrolled sideways around it. Should
|
|
241
|
+
// it still be wider, it keeps its start edge in view. Read from the
|
|
242
|
+
// layout box, which the entrance preset's scale does not shrink, and from
|
|
243
|
+
// the width the class gives, not one an earlier measurement capped.
|
|
244
|
+
node.style.maxWidth = '';
|
|
241
245
|
const viewport = document.documentElement.clientWidth || window.innerWidth;
|
|
242
246
|
const lo = Math.max(0, clipBox?.left ?? 0);
|
|
243
247
|
const hi = Math.min(viewport, clipBox?.right ?? viewport);
|
|
248
|
+
if (node.offsetWidth > hi - lo)
|
|
249
|
+
node.style.maxWidth = `${hi - lo}px`;
|
|
244
250
|
const left = rect.left + node.offsetLeft;
|
|
245
251
|
const right = left + node.offsetWidth;
|
|
246
252
|
let shift = 0;
|
package/dist/styles/theme.css
CHANGED
|
@@ -358,7 +358,10 @@
|
|
|
358
358
|
* position it set would fight the `absolute` an input's clear button relies
|
|
359
359
|
* on. Neighbors are spaced so their boxes meet rather than overlap: the
|
|
360
360
|
* later one in document order takes an overlap, so a tap near the edge of
|
|
361
|
-
* one control lands on the next.
|
|
361
|
+
* one control lands on the next. Where the room cannot hold that spacing, a
|
|
362
|
+
* calendar shrunk into a narrow modal for instance, the row sets
|
|
363
|
+
* `--hit-width` to the width each target has, and the boxes take that width
|
|
364
|
+
* and keep the control height.
|
|
362
365
|
*/
|
|
363
366
|
@custom-variant coarse (@media (pointer: coarse));
|
|
364
367
|
|
|
@@ -377,7 +380,7 @@
|
|
|
377
380
|
top: 50%;
|
|
378
381
|
width: 100%;
|
|
379
382
|
height: 100%;
|
|
380
|
-
min-width: var(--spacing-control);
|
|
383
|
+
min-width: var(--hit-width, var(--spacing-control));
|
|
381
384
|
min-height: var(--spacing-control);
|
|
382
385
|
translate: -50% -50%;
|
|
383
386
|
}
|
package/package.json
CHANGED
package/src/lib/styles/theme.css
CHANGED
|
@@ -358,7 +358,10 @@
|
|
|
358
358
|
* position it set would fight the `absolute` an input's clear button relies
|
|
359
359
|
* on. Neighbors are spaced so their boxes meet rather than overlap: the
|
|
360
360
|
* later one in document order takes an overlap, so a tap near the edge of
|
|
361
|
-
* one control lands on the next.
|
|
361
|
+
* one control lands on the next. Where the room cannot hold that spacing, a
|
|
362
|
+
* calendar shrunk into a narrow modal for instance, the row sets
|
|
363
|
+
* `--hit-width` to the width each target has, and the boxes take that width
|
|
364
|
+
* and keep the control height.
|
|
362
365
|
*/
|
|
363
366
|
@custom-variant coarse (@media (pointer: coarse));
|
|
364
367
|
|
|
@@ -377,7 +380,7 @@
|
|
|
377
380
|
top: 50%;
|
|
378
381
|
width: 100%;
|
|
379
382
|
height: 100%;
|
|
380
|
-
min-width: var(--spacing-control);
|
|
383
|
+
min-width: var(--hit-width, var(--spacing-control));
|
|
381
384
|
min-height: var(--spacing-control);
|
|
382
385
|
translate: -50% -50%;
|
|
383
386
|
}
|