@heyramzi/ui 3.1.0 → 3.3.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/dist/lib/sliding-pill.js +2 -2
- package/dist/record/measure-clamp.js +23 -0
- package/dist/shadcn/button-group/index.js +6 -0
- package/dist/shadcn/checkbox/checkbox-box.js +3 -0
- package/dist/shadcn/drawer/index.js +14 -0
- package/dist/shadcn/input-group/index.js +9 -0
- package/dist/shadcn/item/index.js +13 -0
- package/dist/shadcn/native-select/index.js +6 -0
- package/dist/shadcn/pagination/index.js +12 -0
- package/package.json +7 -2
- package/scripts/sync-shadcn.mjs +0 -6
- package/src/ai/generation-progress/GenerationProgress.svelte +14 -12
- package/src/canvas/pen/PenOverlay.svelte +1 -30
- package/src/canvas/screen/PartView.svelte +13 -8
- package/src/canvas/screen/ScreenBody.svelte +18 -11
- package/src/canvas/ui/BarTooltip.svelte +46 -0
- package/src/canvas/ui/BarTooltip.svelte.d.ts +17 -0
- package/src/canvas/ui/CanvasToolbar.svelte +170 -156
- package/src/canvas/ui/StylePanel.svelte +179 -102
- package/src/canvas/ui/canvas-ui.css +12 -76
- package/src/charts/dashboard/data-table.svelte +80 -80
- package/src/charts/dashboard/hint.svelte +34 -0
- package/src/charts/dashboard/hint.svelte.d.ts +10 -0
- package/src/charts/dashboard/rating-chip.svelte +4 -3
- package/src/charts/legend.css +0 -8
- package/src/charts/legend.svelte +26 -30
- package/src/components/copy/copy-button.svelte +10 -2
- package/src/components/copy/copy-button.test.ts +7 -0
- package/src/components/data-table/table-skeleton.svelte +15 -7
- package/src/components/data-table/table-skeleton.svelte.d.ts +4 -0
- package/src/components/docs/on-this-page.svelte +2 -2
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +2 -1
- package/src/components/list-page/filter-item.svelte +20 -7
- package/src/components/list-page/filter-popover.svelte +41 -16
- package/src/components/list-page/filter-popover.svelte.d.ts +1 -1
- package/src/components/list-page/grouped-list.svelte +41 -23
- package/src/components/picker/picker.svelte +8 -3
- package/src/components/picker/picker.svelte.d.ts +2 -1
- package/src/components/segmented/segmented-filter.svelte +19 -10
- package/src/components/segmented/segmented-track.svelte +11 -3
- package/src/components/table-view/table-view.svelte +22 -6
- package/src/components/tags-input/tags-input.svelte +94 -77
- package/src/components/toolbar/toolbar.svelte +2 -1
- package/src/components/toolbar/toolbar.test.ts +5 -4
- package/src/components/work-parts/group-head.svelte +28 -9
- package/src/components/work-parts/group-head.svelte.d.ts +5 -2
- package/src/editor/editor-toolbar.svelte +223 -193
- package/src/editor/editor.svelte +56 -38
- package/src/editor/image-toolbar.svelte +99 -37
- package/src/editor/image-toolbar.svelte.d.ts +4 -5
- package/src/editor/url-dialog.svelte +96 -0
- package/src/editor/url-dialog.svelte.d.ts +15 -0
- package/src/effects/theme-toggle/ThemeToggle.css +8 -8
- package/src/effects/theme-toggle/ThemeToggle.svelte +20 -6
- package/src/inbox/thread-inbox.svelte +9 -1
- package/src/lib/live-region.svelte +12 -4
- package/src/lib/live-region.svelte.d.ts +5 -1
- package/src/lib/sliding-pill.ts +3 -2
- package/src/record/info-list.svelte +33 -59
- package/src/record/measure-clamp.ts +31 -0
- package/src/record/record-shell.svelte +38 -39
- package/src/record/record-timeline.svelte +10 -5
- package/src/record/steps-rail.svelte +50 -36
- package/src/record/where-we-are.svelte +5 -19
- package/src/shadcn/button-group/button-group-separator.svelte +23 -0
- package/src/shadcn/button-group/button-group-separator.svelte.d.ts +13 -0
- package/src/shadcn/button-group/button-group-text.svelte +28 -0
- package/src/shadcn/button-group/button-group-text.svelte.d.ts +11 -0
- package/src/shadcn/button-group/button-group.svelte +46 -0
- package/src/shadcn/button-group/button-group.svelte.d.ts +16 -0
- package/src/shadcn/button-group/index.ts +15 -0
- package/src/shadcn/checkbox/checkbox-box.ts +4 -0
- package/src/shadcn/checkbox/checkbox.svelte +2 -4
- package/src/shadcn/drawer/drawer-close.svelte +7 -0
- package/src/shadcn/drawer/drawer-close.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-content.svelte +36 -0
- package/src/shadcn/drawer/drawer-content.svelte.d.ts +10 -0
- package/src/shadcn/drawer/drawer-description.svelte +17 -0
- package/src/shadcn/drawer/drawer-description.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-footer.svelte +20 -0
- package/src/shadcn/drawer/drawer-footer.svelte.d.ts +5 -0
- package/src/shadcn/drawer/drawer-header.svelte +23 -0
- package/src/shadcn/drawer/drawer-header.svelte.d.ts +5 -0
- package/src/shadcn/drawer/drawer-nested.svelte +12 -0
- package/src/shadcn/drawer/drawer-nested.svelte.d.ts +3 -0
- package/src/shadcn/drawer/drawer-overlay.svelte +20 -0
- package/src/shadcn/drawer/drawer-overlay.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-portal.svelte +7 -0
- package/src/shadcn/drawer/drawer-portal.svelte.d.ts +3 -0
- package/src/shadcn/drawer/drawer-title.svelte +17 -0
- package/src/shadcn/drawer/drawer-title.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer-trigger.svelte +7 -0
- package/src/shadcn/drawer/drawer-trigger.svelte.d.ts +4 -0
- package/src/shadcn/drawer/drawer.svelte +12 -0
- package/src/shadcn/drawer/drawer.svelte.d.ts +3 -0
- package/src/shadcn/drawer/index.ts +38 -0
- package/src/shadcn/input-group/index.ts +22 -0
- package/src/shadcn/input-group/input-group-addon.svelte +51 -0
- package/src/shadcn/input-group/input-group-addon.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-button.svelte +49 -0
- package/src/shadcn/input-group/input-group-button.svelte.d.ts +18 -0
- package/src/shadcn/input-group/input-group-input.svelte +20 -0
- package/src/shadcn/input-group/input-group-input.svelte.d.ts +11 -0
- package/src/shadcn/input-group/input-group-text.svelte +19 -0
- package/src/shadcn/input-group/input-group-text.svelte.d.ts +5 -0
- package/src/shadcn/input-group/input-group-textarea.svelte +20 -0
- package/src/shadcn/input-group/input-group-textarea.svelte.d.ts +3 -0
- package/src/shadcn/input-group/input-group.svelte +24 -0
- package/src/shadcn/input-group/input-group.svelte.d.ts +5 -0
- package/src/shadcn/item/index.ts +34 -0
- package/src/shadcn/item/item-actions.svelte +20 -0
- package/src/shadcn/item/item-actions.svelte.d.ts +5 -0
- package/src/shadcn/item/item-content.svelte +23 -0
- package/src/shadcn/item/item-content.svelte.d.ts +5 -0
- package/src/shadcn/item/item-description.svelte +23 -0
- package/src/shadcn/item/item-description.svelte.d.ts +5 -0
- package/src/shadcn/item/item-footer.svelte +20 -0
- package/src/shadcn/item/item-footer.svelte.d.ts +5 -0
- package/src/shadcn/item/item-group.svelte +21 -0
- package/src/shadcn/item/item-group.svelte.d.ts +5 -0
- package/src/shadcn/item/item-header.svelte +20 -0
- package/src/shadcn/item/item-header.svelte.d.ts +5 -0
- package/src/shadcn/item/item-media.svelte +42 -0
- package/src/shadcn/item/item-media.svelte.d.ts +17 -0
- package/src/shadcn/item/item-separator.svelte +19 -0
- package/src/shadcn/item/item-separator.svelte.d.ts +13 -0
- package/src/shadcn/item/item-title.svelte +20 -0
- package/src/shadcn/item/item-title.svelte.d.ts +5 -0
- package/src/shadcn/item/item.svelte +61 -0
- package/src/shadcn/item/item.svelte.d.ts +28 -0
- package/src/shadcn/native-select/index.ts +13 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte +20 -0
- package/src/shadcn/native-select/native-select-opt-group.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select-option.svelte +20 -0
- package/src/shadcn/native-select/native-select-option.svelte.d.ts +5 -0
- package/src/shadcn/native-select/native-select.svelte +45 -0
- package/src/shadcn/native-select/native-select.svelte.d.ts +8 -0
- package/src/shadcn/pagination/index.ts +31 -0
- package/src/shadcn/pagination/pagination-content.svelte +20 -0
- package/src/shadcn/pagination/pagination-content.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte +22 -0
- package/src/shadcn/pagination/pagination-ellipsis.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-item.svelte +14 -0
- package/src/shadcn/pagination/pagination-item.svelte.d.ts +5 -0
- package/src/shadcn/pagination/pagination-link.svelte +41 -0
- package/src/shadcn/pagination/pagination-link.svelte.d.ts +9 -0
- package/src/shadcn/pagination/pagination-next-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-next-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-next.svelte +26 -0
- package/src/shadcn/pagination/pagination-next.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte +34 -0
- package/src/shadcn/pagination/pagination-prev-button.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination-previous.svelte +30 -0
- package/src/shadcn/pagination/pagination-previous.svelte.d.ts +4 -0
- package/src/shadcn/pagination/pagination.svelte +27 -0
- package/src/shadcn/pagination/pagination.svelte.d.ts +4 -0
- package/src/slides/Controls.svelte +39 -36
- package/src/slides/Deck.svelte +11 -8
- package/src/thread/thread.svelte +15 -8
- package/src/work/detail/task-detail.svelte +1 -1
- package/src/work/gantt/gantt-task-row.svelte +3 -6
- package/src/work/inbox/inbox-card.svelte +7 -5
- package/src/work/inbox/inbox-trace.svelte +14 -10
- package/src/work/kanban-board.svelte +40 -25
- package/src/work/kanban-hidden-rail.svelte +67 -65
- package/src/work/task-list.svelte +145 -124
- package/src/work/task-list.svelte.test.ts +5 -2
- package/src/work/time/time-tracker.svelte +35 -10
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { createKanbanDnd } from "./use-kanban-dnd.svelte.js";
|
|
12
12
|
import { CaretRight } from "phosphor-svelte";
|
|
13
13
|
import { Button } from "../shadcn/button/index.js";
|
|
14
|
+
import * as Collapsible from "../shadcn/collapsible/index.js";
|
|
14
15
|
import GroupHead from "../components/work-parts/group-head.svelte";
|
|
15
16
|
import { workLooks } from "../components/work-parts/looks.js";
|
|
16
17
|
import type {
|
|
@@ -182,9 +183,10 @@
|
|
|
182
183
|
isDroppable: canDrop,
|
|
183
184
|
});
|
|
184
185
|
|
|
185
|
-
function
|
|
186
|
+
function setOpen(groupId: string, open: boolean): void {
|
|
187
|
+
if (open !== folded.has(groupId)) return;
|
|
186
188
|
const next = new Set(folded);
|
|
187
|
-
if (
|
|
189
|
+
if (open) next.delete(groupId);
|
|
188
190
|
else next.add(groupId);
|
|
189
191
|
folded = next;
|
|
190
192
|
onCollapsedChange?.([...next]);
|
|
@@ -255,134 +257,153 @@
|
|
|
255
257
|
{@const open = !folded.has(group.id)}
|
|
256
258
|
{@const hue = group.hue ?? "var(--color-primary)"}
|
|
257
259
|
{@const context = { count, active, open, depth, hasChildren: children.length > 0 }}
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
variant="ghost"
|
|
282
|
-
size="sm"
|
|
283
|
-
icon
|
|
284
|
-
aria-expanded={open}
|
|
285
|
-
aria-label="{open ? 'Fold' : 'Unfold'} {group.label}"
|
|
286
|
-
onclick={() => toggle(group.id)}
|
|
287
|
-
class="-ml-1.5 text-muted-foreground"
|
|
260
|
+
<Collapsible.Root bind:open={() => open, (next) => setOpen(group.id, next)}>
|
|
261
|
+
{#snippet child({ props })}
|
|
262
|
+
<section
|
|
263
|
+
{...props}
|
|
264
|
+
aria-label={group.ariaLabel ?? group.label}
|
|
265
|
+
class={cn(
|
|
266
|
+
"tl-group group/tl",
|
|
267
|
+
depth === 0 ? "border-b border-border last:border-b-0" : "border-t border-border/60",
|
|
268
|
+
)}
|
|
269
|
+
class:tl-group-active={active}
|
|
270
|
+
style:--tl-hue={hue}
|
|
271
|
+
ondragover={children.length ? undefined : (e) => dnd.onColumnDragOver(e, group.id)}
|
|
272
|
+
ondragleave={children.length ? undefined : dnd.onDragLeave}
|
|
273
|
+
ondrop={children.length ? undefined : (e) => dnd.onDrop(e, group.id)}
|
|
274
|
+
>
|
|
275
|
+
<div
|
|
276
|
+
class={cn(
|
|
277
|
+
"hover-row sticky z-10 flex h-10 items-center gap-2 px-3 backdrop-blur-sm",
|
|
278
|
+
flat ? "bg-card/90" : "bg-card/90",
|
|
279
|
+
depth === 0 && (head ? "top-9" : "top-0"),
|
|
280
|
+
)}
|
|
281
|
+
style:top={depth > 0 ? `${(head ? 2.25 : 0) + depth * 2.5}rem` : undefined}
|
|
282
|
+
style:padding-inline-start={depth > 0 ? `${0.75 + depth * 1.25}rem` : undefined}
|
|
288
283
|
>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
<div class="tl-actions row-action ms-auto flex shrink-0 items-center gap-1">
|
|
311
|
-
{@render actions(group, context)}
|
|
312
|
-
</div>
|
|
313
|
-
{/if}
|
|
314
|
-
</div>
|
|
315
|
-
|
|
316
|
-
<!-- inert: a folded group's rows leave the Tab order and the accessibility tree. -->
|
|
317
|
-
<div class="tl-fold" class:tl-fold-open={open} inert={!open}>
|
|
318
|
-
<div class="tl-fold-inner">
|
|
319
|
-
<div role="list" aria-label={group.label} class="flex flex-col pb-1.5">
|
|
320
|
-
{#each rows as item, index (getId(item))}
|
|
321
|
-
{@const id = getId(item)}
|
|
322
|
-
{@const rowId = `${uid}-row-${encodeURIComponent(id)}`}
|
|
323
|
-
{@const dropBefore =
|
|
324
|
-
dnd.showIndicatorBefore(group.id, id) &&
|
|
325
|
-
!dnd.isDragSource(id) &&
|
|
326
|
-
idAt(rows, index - 1) !== dnd.draggedId}
|
|
327
|
-
<!-- WHY: a row is a list item that takes focus for the Alt+arrow move; making it a button would hide the list from screen readers. -->
|
|
328
|
-
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
329
|
-
<div
|
|
330
|
-
role="listitem"
|
|
331
|
-
tabindex={draggable ? 0 : undefined}
|
|
332
|
-
id={rowId}
|
|
333
|
-
data-task-row={id}
|
|
334
|
-
aria-label={getLabel?.(item)}
|
|
335
|
-
aria-labelledby={draggable && !getLabel ? rowId : undefined}
|
|
336
|
-
aria-describedby={draggable ? hintId : undefined}
|
|
337
|
-
aria-keyshortcuts={draggable ? "Alt+ArrowUp Alt+ArrowDown" : undefined}
|
|
338
|
-
draggable={draggable ? "true" : undefined}
|
|
339
|
-
animate:flip={{ duration: () => durationMs("fast"), easing: cubicOut }}
|
|
340
|
-
ondragstart={draggable
|
|
341
|
-
? (e) => {
|
|
342
|
-
liftDragImage(e);
|
|
343
|
-
dnd.onDragStart(e, id);
|
|
344
|
-
}
|
|
345
|
-
: undefined}
|
|
346
|
-
ondragend={draggable ? () => dnd.resetDrag() : undefined}
|
|
347
|
-
ondragover={draggable
|
|
348
|
-
? (e) => dnd.onItemDragOver(e, group.id, id, idAt(rows, index + 1))
|
|
349
|
-
: undefined}
|
|
350
|
-
onkeydown={draggable ? (e) => void onRowKeyDown(e, id) : undefined}
|
|
351
|
-
style:--tl-i={Math.min(index, STAGGER_CAP)}
|
|
352
|
-
class={cn(
|
|
353
|
-
"tl-row relative mx-1.5 outline-none focus-visible:shadow-(--ring-focus)",
|
|
354
|
-
workLooks.row,
|
|
355
|
-
workLooks.rowHeight,
|
|
356
|
-
draggable && "cursor-grab active:cursor-grabbing",
|
|
357
|
-
dropBefore && "tl-drop-before",
|
|
358
|
-
dnd.isDragSource(id) && "opacity-40",
|
|
359
|
-
)}
|
|
360
|
-
>
|
|
361
|
-
{@render renderRow(item, { groupId: group.id, index })}
|
|
362
|
-
</div>
|
|
363
|
-
{/each}
|
|
364
|
-
|
|
365
|
-
{#if dnd.showIndicatorEnd(group.id) && idAt(rows, rows.length - 1) !== dnd.draggedId}
|
|
366
|
-
<div class="tl-drop-end relative mx-1.5 h-2" aria-hidden="true"></div>
|
|
284
|
+
{#if collapsible}
|
|
285
|
+
<Collapsible.Trigger>
|
|
286
|
+
{#snippet child({ props })}
|
|
287
|
+
<Button
|
|
288
|
+
{...props}
|
|
289
|
+
variant="ghost"
|
|
290
|
+
size="sm"
|
|
291
|
+
icon
|
|
292
|
+
aria-label="{open ? 'Fold' : 'Unfold'} {group.label}"
|
|
293
|
+
class="-ml-1.5 text-muted-foreground"
|
|
294
|
+
>
|
|
295
|
+
<span
|
|
296
|
+
class="tl-chevron size-3.5 [&>svg]:size-full"
|
|
297
|
+
class:tl-chevron-open={open}
|
|
298
|
+
aria-hidden="true"
|
|
299
|
+
>
|
|
300
|
+
<CaretRight aria-hidden="true" weight="bold" />
|
|
301
|
+
</span>
|
|
302
|
+
</Button>
|
|
303
|
+
{/snippet}
|
|
304
|
+
</Collapsible.Trigger>
|
|
367
305
|
{/if}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
306
|
+
{#if groupHeader}
|
|
307
|
+
{@render groupHeader(group, context)}
|
|
308
|
+
{:else}
|
|
309
|
+
<GroupHead
|
|
310
|
+
label={group.label}
|
|
311
|
+
{hue}
|
|
312
|
+
{count}
|
|
313
|
+
onAdd={onAdd ? () => onAdd(group.id) : undefined}
|
|
314
|
+
{addLabel}
|
|
315
|
+
/>
|
|
371
316
|
{/if}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
</p>
|
|
317
|
+
{#if actions}
|
|
318
|
+
<div class="tl-actions row-action ms-auto flex shrink-0 items-center gap-1">
|
|
319
|
+
{@render actions(group, context)}
|
|
320
|
+
</div>
|
|
377
321
|
{/if}
|
|
378
322
|
</div>
|
|
379
323
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
324
|
+
<!-- inert: a folded group's rows leave the Tab order and the accessibility tree. -->
|
|
325
|
+
<Collapsible.Content forceMount>
|
|
326
|
+
{#snippet child({ props })}
|
|
327
|
+
<div {...props} class="tl-fold" class:tl-fold-open={open}>
|
|
328
|
+
<!-- On the inner box: beside the spread Svelte writes inert as an attribute, and
|
|
329
|
+
clearing it leaves the rows inert in the unit tests' DOM. -->
|
|
330
|
+
<div class="tl-fold-inner" inert={!open}>
|
|
331
|
+
<div role="list" aria-label={group.label} class="flex flex-col pb-1.5">
|
|
332
|
+
{#each rows as item, index (getId(item))}
|
|
333
|
+
{@const id = getId(item)}
|
|
334
|
+
{@const rowId = `${uid}-row-${encodeURIComponent(id)}`}
|
|
335
|
+
{@const dropBefore =
|
|
336
|
+
dnd.showIndicatorBefore(group.id, id) &&
|
|
337
|
+
!dnd.isDragSource(id) &&
|
|
338
|
+
idAt(rows, index - 1) !== dnd.draggedId}
|
|
339
|
+
<!-- WHY: a row is a list item that takes focus for the Alt+arrow move; making it a button would hide the list from screen readers. -->
|
|
340
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
341
|
+
<div
|
|
342
|
+
role="listitem"
|
|
343
|
+
tabindex={draggable ? 0 : undefined}
|
|
344
|
+
id={rowId}
|
|
345
|
+
data-task-row={id}
|
|
346
|
+
aria-label={getLabel?.(item)}
|
|
347
|
+
aria-labelledby={draggable && !getLabel ? rowId : undefined}
|
|
348
|
+
aria-describedby={draggable ? hintId : undefined}
|
|
349
|
+
aria-keyshortcuts={draggable ? "Alt+ArrowUp Alt+ArrowDown" : undefined}
|
|
350
|
+
draggable={draggable ? "true" : undefined}
|
|
351
|
+
animate:flip={{ duration: () => durationMs("fast"), easing: cubicOut }}
|
|
352
|
+
ondragstart={draggable
|
|
353
|
+
? (e) => {
|
|
354
|
+
liftDragImage(e);
|
|
355
|
+
dnd.onDragStart(e, id);
|
|
356
|
+
}
|
|
357
|
+
: undefined}
|
|
358
|
+
ondragend={draggable ? () => dnd.resetDrag() : undefined}
|
|
359
|
+
ondragover={draggable
|
|
360
|
+
? (e) => dnd.onItemDragOver(e, group.id, id, idAt(rows, index + 1))
|
|
361
|
+
: undefined}
|
|
362
|
+
onkeydown={draggable ? (e) => void onRowKeyDown(e, id) : undefined}
|
|
363
|
+
style:--tl-i={Math.min(index, STAGGER_CAP)}
|
|
364
|
+
class={cn(
|
|
365
|
+
"tl-row relative mx-1.5 outline-none focus-visible:shadow-(--ring-focus)",
|
|
366
|
+
workLooks.row,
|
|
367
|
+
workLooks.rowHeight,
|
|
368
|
+
draggable && "cursor-grab active:cursor-grabbing",
|
|
369
|
+
dropBefore && "tl-drop-before",
|
|
370
|
+
dnd.isDragSource(id) && "opacity-40",
|
|
371
|
+
)}
|
|
372
|
+
>
|
|
373
|
+
{@render renderRow(item, { groupId: group.id, index })}
|
|
374
|
+
</div>
|
|
375
|
+
{/each}
|
|
376
|
+
|
|
377
|
+
{#if dnd.showIndicatorEnd(group.id) && idAt(rows, rows.length - 1) !== dnd.draggedId}
|
|
378
|
+
<div class="tl-drop-end relative mx-1.5 h-2" aria-hidden="true"></div>
|
|
379
|
+
{/if}
|
|
380
|
+
|
|
381
|
+
{#if groupFooter}
|
|
382
|
+
{@render groupFooter(group, context)}
|
|
383
|
+
{/if}
|
|
384
|
+
|
|
385
|
+
{#if rows.length === 0 && children.length === 0 && !active}
|
|
386
|
+
<p
|
|
387
|
+
class={cn(
|
|
388
|
+
workLooks.rowHole,
|
|
389
|
+
"mx-1.5 px-3 py-3 text-xs text-muted-foreground",
|
|
390
|
+
)}
|
|
391
|
+
>
|
|
392
|
+
{emptyLabel}
|
|
393
|
+
</p>
|
|
394
|
+
{/if}
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
{#each children as sub (sub.id)}
|
|
398
|
+
{@render section(sub, depth + 1)}
|
|
399
|
+
{/each}
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
{/snippet}
|
|
403
|
+
</Collapsible.Content>
|
|
404
|
+
</section>
|
|
405
|
+
{/snippet}
|
|
406
|
+
</Collapsible.Root>
|
|
386
407
|
{/snippet}
|
|
387
408
|
|
|
388
409
|
<div
|
|
@@ -54,9 +54,12 @@ async function settle() {
|
|
|
54
54
|
flushSync();
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// jsdom doesn't reflect inert between property and attribute, and Svelte sets the attribute
|
|
58
|
+
// when the element also takes a spread (the Collapsible's props), so read both.
|
|
58
59
|
function insideInert(el: HTMLElement | null): boolean {
|
|
59
|
-
for (let node = el; node; node = node.parentElement)
|
|
60
|
+
for (let node = el; node; node = node.parentElement) {
|
|
61
|
+
if (node.inert || node.hasAttribute("inert")) return true;
|
|
62
|
+
}
|
|
60
63
|
return false;
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts" generics="T">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
+
import { Meter } from "bits-ui";
|
|
3
4
|
import { cn } from "../../lib/utils.js";
|
|
4
5
|
import { initial } from "../../lib/initials.js";
|
|
5
6
|
import { TONE_COLOR, TONE_TEXT } from "../../lib/tone.js";
|
|
@@ -84,6 +85,10 @@
|
|
|
84
85
|
|
|
85
86
|
let labels = $derived({ ...DEFAULT_TIME_LABELS, ...labelOverrides });
|
|
86
87
|
|
|
88
|
+
const uid = $props.id();
|
|
89
|
+
const invalidId = `${uid}-invalid`;
|
|
90
|
+
const loggedId = `${uid}-logged`;
|
|
91
|
+
|
|
87
92
|
// ---- timer
|
|
88
93
|
let now = $state(Date.now());
|
|
89
94
|
let open = $state(false);
|
|
@@ -143,7 +148,7 @@
|
|
|
143
148
|
let note = $state("");
|
|
144
149
|
let saving = $state(false);
|
|
145
150
|
let invalid = $state(false);
|
|
146
|
-
let durationInput = $state<HTMLInputElement>();
|
|
151
|
+
let durationInput = $state<HTMLInputElement | null>(null);
|
|
147
152
|
let parsed = $derived(parseMinutes(duration));
|
|
148
153
|
let preview = $derived(
|
|
149
154
|
parsed !== null && parsed >= 1 && parsed <= 1440 ? formatMinutes(parsed) : "",
|
|
@@ -170,6 +175,14 @@
|
|
|
170
175
|
|
|
171
176
|
// ---- summary
|
|
172
177
|
let total = $derived(entries.reduce((sum, entry) => sum + getMinutes(entry), 0));
|
|
178
|
+
// What the trigger shows, so its name holds it (WCAG 2.5.3, label in name).
|
|
179
|
+
let shown = $derived(
|
|
180
|
+
running
|
|
181
|
+
? `${clock.hours}:${clock.minutes}:${clock.seconds}`
|
|
182
|
+
: total > 0
|
|
183
|
+
? `${formatMinutes(total)}${estimate ? ` / ${formatMinutes(estimate)}` : ""}`
|
|
184
|
+
: labels.timer,
|
|
185
|
+
);
|
|
173
186
|
let liveTotal = $derived(total + elapsedMinutes);
|
|
174
187
|
let tone = $derived(budgetTone(liveTotal, estimate));
|
|
175
188
|
let percent = $derived(estimate ? Math.round((liveTotal / estimate) * 100) : 0);
|
|
@@ -245,7 +258,7 @@
|
|
|
245
258
|
buttonVariants({ variant: "ghost", size: "sm" }),
|
|
246
259
|
"time-open gap-1.5 ps-1 pe-2 text-(length:--text-control) tabular-nums data-[state=open]:bg-muted",
|
|
247
260
|
)}
|
|
248
|
-
aria-label={labels.title}
|
|
261
|
+
aria-label="{labels.title}: {shown}"
|
|
249
262
|
>
|
|
250
263
|
{#if running}
|
|
251
264
|
<TimeDot running size={6} />
|
|
@@ -278,7 +291,7 @@
|
|
|
278
291
|
placeholder={labels.durationPlaceholder}
|
|
279
292
|
aria-label={labels.duration}
|
|
280
293
|
aria-invalid={invalid}
|
|
281
|
-
aria-describedby={invalid ?
|
|
294
|
+
aria-describedby={invalid ? invalidId : undefined}
|
|
282
295
|
autocomplete="off"
|
|
283
296
|
inputmode="text"
|
|
284
297
|
class={cn(
|
|
@@ -313,20 +326,33 @@
|
|
|
313
326
|
</Button>
|
|
314
327
|
</form>
|
|
315
328
|
{#if invalid}
|
|
316
|
-
<p id=
|
|
329
|
+
<p id={invalidId} class="text-xs text-destructive-ink" role="alert">{labels.invalid}</p>
|
|
317
330
|
{/if}
|
|
318
331
|
|
|
319
332
|
{#if entries.length > 0 || estimate}
|
|
320
333
|
<div class="flex flex-col gap-2 border-t border-border/60 pt-3">
|
|
321
334
|
<div class="flex items-baseline justify-between gap-2">
|
|
322
|
-
<span class="text-xs font-medium text-muted-foreground"
|
|
335
|
+
<span id={loggedId} class="text-xs font-medium text-muted-foreground"
|
|
336
|
+
>{labels.logged}</span
|
|
337
|
+
>
|
|
323
338
|
{#key total}
|
|
324
339
|
<span class="time-pop font-medium tabular-nums">{formatMinutes(total)}</span>
|
|
325
340
|
{/key}
|
|
326
341
|
</div>
|
|
327
342
|
{#if estimate}
|
|
343
|
+
{@const budget =
|
|
344
|
+
liveTotal > estimate
|
|
345
|
+
? labels.over(formatMinutes(liveTotal - estimate), formatMinutes(estimate))
|
|
346
|
+
: labels.remaining(formatMinutes(estimate - liveTotal), formatMinutes(estimate))}
|
|
328
347
|
<div class="flex flex-col gap-1">
|
|
329
|
-
|
|
348
|
+
<!-- The meter speaks the budget line, so the line below stays out of the reading order. -->
|
|
349
|
+
<Meter.Root
|
|
350
|
+
value={Math.min(liveTotal, estimate)}
|
|
351
|
+
max={estimate}
|
|
352
|
+
aria-labelledby={loggedId}
|
|
353
|
+
aria-valuetext={budget}
|
|
354
|
+
class={cn("h-1 overflow-hidden bg-muted", ganttLooks.track)}
|
|
355
|
+
>
|
|
330
356
|
<div
|
|
331
357
|
class={cn("time-segment h-full", ganttLooks.track)}
|
|
332
358
|
style:background={tone === "neutral"
|
|
@@ -334,16 +360,15 @@
|
|
|
334
360
|
: TONE_COLOR[tone]}
|
|
335
361
|
style:width="{Math.min(100, percent)}%"
|
|
336
362
|
></div>
|
|
337
|
-
</
|
|
363
|
+
</Meter.Root>
|
|
338
364
|
<p
|
|
339
365
|
class={cn(
|
|
340
366
|
"text-xs tabular-nums",
|
|
341
367
|
tone === "neutral" ? "text-muted-foreground" : TONE_TEXT[tone],
|
|
342
368
|
)}
|
|
369
|
+
aria-hidden="true"
|
|
343
370
|
>
|
|
344
|
-
{
|
|
345
|
-
? labels.over(formatMinutes(liveTotal - estimate), formatMinutes(estimate))
|
|
346
|
-
: labels.remaining(formatMinutes(estimate - liveTotal), formatMinutes(estimate))}
|
|
371
|
+
{budget}
|
|
347
372
|
</p>
|
|
348
373
|
</div>
|
|
349
374
|
{/if}
|