@heyramzi/ui 2.22.1 → 2.24.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/COMPONENTS.md +3 -6
- package/dist/ai/confirmation/index.js +3 -6
- package/dist/ai/index.js +1 -5
- package/dist/canvas/editor/rebase.js +26 -2
- package/dist/charts/dashboard/index.js +1 -3
- package/dist/charts/primitives/chart-utils.js +14 -0
- package/dist/components/chip/index.js +1 -0
- package/dist/components/language-flag/flags.js +5 -0
- package/dist/components/language-flag/index.js +1 -0
- package/dist/components/list-page/index.js +1 -3
- package/dist/components/work-parts/index.js +1 -1
- package/dist/editor/doc-blocks.js +2 -1
- package/dist/editor/safe-url.js +26 -0
- package/dist/editor/serializer.js +10 -4
- package/dist/editor/slash-query.js +10 -0
- package/dist/effects/animated-beam/path-calculator.svelte.js +13 -6
- package/dist/lib/dates.js +41 -0
- package/dist/lib/panel.js +2 -2
- package/dist/lib/tone.js +13 -0
- package/dist/record/format.js +2 -18
- package/dist/record/logic.js +3 -6
- package/dist/status/index.js +1 -1
- package/dist/triage/index.js +4 -0
- package/dist/triage/types.js +12 -0
- package/dist/work/dates.js +2 -3
- package/dist/work/detail/detail-types.js +0 -8
- package/dist/work/docs/edited.js +8 -7
- package/dist/work/inbox/inbox-logic.js +5 -20
- package/dist/work/index.js +0 -2
- package/dist/work/time/duration.js +2 -3
- package/package.json +7 -21
- package/scripts/sync-shadcn.mjs +4 -6
- package/src/ai/confirmation/Confirmation.svelte +2 -2
- package/src/ai/confirmation/{ConfirmationRequest.svelte → ConfirmationState.svelte} +6 -3
- package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
- package/src/ai/confirmation/confirmation-parts.test.svelte +17 -0
- package/src/ai/confirmation/confirmation-parts.test.ts +23 -0
- package/src/ai/confirmation/index.ts +6 -21
- package/src/ai/index.ts +3 -13
- package/src/ai/typing-dots/TypingDots.svelte +32 -12
- package/src/ai/typing-dots/typing-dots.test.ts +36 -0
- package/src/animations/modal.css +4 -1
- package/src/canvas/editor/rebase.test.ts +37 -0
- package/src/canvas/editor/rebase.ts +33 -1
- package/src/canvas/ui/canvas-ui.css +44 -1
- package/src/charts/README.md +2 -2
- package/src/charts/area-chart.svelte +5 -12
- package/src/charts/bar-chart.svelte +6 -14
- package/src/charts/chart-tokens.css +19 -0
- package/src/charts/dashboard/data-table.svelte +21 -21
- package/src/charts/dashboard/index.ts +1 -3
- package/src/charts/dashboard/rating-chip.svelte +9 -11
- package/src/charts/dashboard/stat-tile.svelte +4 -5
- package/src/charts/donut-chart.svelte +1 -1
- package/src/charts/gauge-chart.svelte +1 -1
- package/src/charts/line-chart.svelte +5 -12
- package/src/charts/number-chart.svelte +1 -1
- package/src/charts/primitives/chart-utils.test.ts +17 -0
- package/src/charts/primitives/chart-utils.ts +16 -0
- package/src/charts/ring-chart.svelte +2 -2
- package/src/charts/sparkline.svelte +1 -1
- package/src/charts/stat-card.svelte +1 -1
- package/src/charts/time-series-chart.svelte +9 -5
- package/src/components/chip/count-chip.svelte +35 -0
- package/src/components/chip/index.ts +1 -0
- package/src/components/data-table/data-table.ts +1 -1
- package/src/components/data-table/row-context-menu.svelte +3 -4
- package/src/components/data-table/sortable-head.svelte +45 -10
- package/src/components/language-flag/flags.ts +5 -0
- package/src/components/language-flag/index.ts +1 -0
- package/src/components/language-flag/language-flag.svelte +3 -11
- package/src/components/list-page/empty-state.svelte +90 -12
- package/src/components/list-page/filter-popover.svelte +2 -5
- package/src/components/list-page/grouped-list.svelte +2 -4
- package/src/components/list-page/index.ts +1 -3
- package/src/components/section-header/section-header.svelte +2 -6
- package/src/components/segmented/segment.svelte +13 -19
- package/src/components/segmented/segmented-filter.svelte +1 -2
- package/src/components/table-view/table-view.svelte +31 -45
- package/src/components/table-view/table-view.test.ts +6 -3
- package/src/components/work-parts/group-head.svelte +1 -1
- package/src/components/work-parts/index.ts +1 -1
- package/src/editor/doc-blocks.ts +2 -1
- package/src/editor/editor.svelte +7 -5
- package/src/editor/safe-url.ts +32 -0
- package/src/editor/serializer.test.ts +58 -0
- package/src/editor/serializer.ts +9 -6
- package/src/editor/slash-query.test.ts +19 -0
- package/src/editor/slash-query.ts +11 -0
- package/src/effects/animated-beam/path-calculator.svelte.ts +19 -5
- package/src/effects/marquee/Marquee.css +6 -3
- package/src/effects/marquee/Marquee.svelte +6 -2
- package/src/inbox/thread-inbox.svelte +6 -4
- package/src/lib/dates.test.ts +63 -0
- package/src/lib/dates.ts +63 -0
- package/src/lib/panel.svelte +116 -0
- package/src/lib/panel.ts +2 -2
- package/src/lib/tone.ts +15 -0
- package/src/modules.test.ts +13 -1
- package/src/panel/side-panel.svelte +4 -4
- package/src/record/format.ts +3 -15
- package/src/record/logic.ts +2 -6
- package/src/record/record-stats.svelte +4 -3
- package/src/record/steps-rail.svelte +7 -4
- package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +1 -1
- package/src/shadcn/button/button.svelte +1 -1
- package/src/shadcn/button/button.test.ts +2 -2
- package/src/shadcn/command/command-dialog.svelte +5 -10
- package/src/shadcn/dialog/dialog-overlay.svelte +1 -1
- package/src/shadcn/input/input.svelte +2 -2
- package/src/shadcn/sheet/sheet-overlay.svelte +1 -1
- package/src/status/index.ts +1 -1
- package/src/status/status-pill.svelte +2 -8
- package/src/thread/thread.svelte +3 -6
- package/src/tokens/craft.css +30 -0
- package/src/triage/README.md +28 -0
- package/src/triage/index.ts +10 -0
- package/src/triage/triage-plan.svelte +132 -0
- package/src/triage/triage-queue.svelte +48 -0
- package/src/triage/triage-task.svelte +95 -0
- package/src/triage/types.ts +35 -0
- package/src/work/README.md +0 -1
- package/src/work/dates.ts +3 -3
- package/src/work/demo.svelte +9 -1
- package/src/work/detail/detail-demo.svelte +30 -27
- package/src/work/detail/detail-types.ts +1 -29
- package/src/work/detail/task-detail.svelte +18 -17
- package/src/work/docs/doc-menu.svelte +2 -1
- package/src/work/docs/edited.ts +4 -7
- package/src/work/inbox/inbox-card.svelte +4 -9
- package/src/work/inbox/inbox-logic.ts +4 -16
- package/src/work/index.ts +1 -3
- package/src/work/kanban-column.svelte +5 -3
- package/src/work/time/duration.ts +2 -3
- package/src/work/time/time-chip.svelte +1 -3
- package/dist/ai/thinking-dots/index.js +0 -3
- package/dist/components/icon/index.js +0 -1
- package/dist/components/locale-switch/index.js +0 -1
- package/dist/icons/trades.js +0 -49
- package/dist/status/pill-tint.js +0 -13
- package/dist/work/presets/sales.js +0 -39
- package/src/ai/confirmation/ConfirmationAccepted.svelte +0 -23
- package/src/ai/confirmation/ConfirmationFailed.svelte +0 -23
- package/src/ai/confirmation/ConfirmationRejected.svelte +0 -23
- package/src/ai/thinking-dots/ThinkingDots.svelte +0 -42
- package/src/ai/thinking-dots/index.ts +0 -5
- package/src/charts/dashboard/dashboard-empty.svelte +0 -55
- package/src/charts/dashboard/data-panel.svelte +0 -81
- package/src/charts/dashboard/data-sort-head.svelte +0 -102
- package/src/components/icon/icon.svelte +0 -23
- package/src/components/icon/index.ts +0 -1
- package/src/components/list-page/detail-empty.svelte +0 -45
- package/src/components/list-page/detail-panel.svelte +0 -82
- package/src/components/list-page/rail-empty.svelte +0 -44
- package/src/components/locale-switch/index.ts +0 -1
- package/src/components/locale-switch/locale-switch.svelte +0 -59
- package/src/components/work-parts/count-chip.svelte +0 -20
- package/src/icons/TradeIcon.svelte +0 -16
- package/src/icons/trades.ts +0 -89
- package/src/status/pill-tint.ts +0 -16
- package/src/work/detail/task-property.svelte +0 -17
- package/src/work/detail/task-thread.svelte +0 -221
- package/src/work/presets/sales.ts +0 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyramzi/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
4
4
|
"description": "Svelte 5 UI for apps: primitives, effects and animations, plus work (tasks and docs), charts, slides, an editor, onboarding and a canvas as subpaths.",
|
|
5
5
|
"homepage": "https://ui.heyramzi.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -126,16 +126,6 @@
|
|
|
126
126
|
"node": "./dist/icons/sf-symbols.js",
|
|
127
127
|
"default": "./src/icons/sf-symbols.ts"
|
|
128
128
|
},
|
|
129
|
-
"./icons/trades": {
|
|
130
|
-
"svelte": "./src/icons/trades.ts",
|
|
131
|
-
"types": "./src/icons/trades.ts",
|
|
132
|
-
"node": "./dist/icons/trades.js",
|
|
133
|
-
"default": "./src/icons/trades.ts"
|
|
134
|
-
},
|
|
135
|
-
"./icons/TradeIcon": {
|
|
136
|
-
"svelte": "./src/icons/TradeIcon.svelte",
|
|
137
|
-
"default": "./src/icons/TradeIcon.svelte"
|
|
138
|
-
},
|
|
139
129
|
"./design": {
|
|
140
130
|
"style": "./src/design/index.css",
|
|
141
131
|
"default": "./src/design/index.css"
|
|
@@ -225,6 +215,12 @@
|
|
|
225
215
|
"node": "./dist/inbox/index.js",
|
|
226
216
|
"default": "./src/inbox/index.ts"
|
|
227
217
|
},
|
|
218
|
+
"./triage": {
|
|
219
|
+
"svelte": "./src/triage/index.ts",
|
|
220
|
+
"types": "./src/triage/index.ts",
|
|
221
|
+
"node": "./dist/triage/index.js",
|
|
222
|
+
"default": "./src/triage/index.ts"
|
|
223
|
+
},
|
|
228
224
|
"./work": {
|
|
229
225
|
"svelte": "./src/work/index.ts",
|
|
230
226
|
"types": "./src/work/index.ts",
|
|
@@ -257,12 +253,6 @@
|
|
|
257
253
|
"node": "./dist/work/inbox/index.js",
|
|
258
254
|
"default": "./src/work/inbox/index.ts"
|
|
259
255
|
},
|
|
260
|
-
"./work/presets/sales": {
|
|
261
|
-
"svelte": "./src/work/presets/sales.ts",
|
|
262
|
-
"types": "./src/work/presets/sales.ts",
|
|
263
|
-
"node": "./dist/work/presets/sales.js",
|
|
264
|
-
"default": "./src/work/presets/sales.ts"
|
|
265
|
-
},
|
|
266
256
|
"./work/detail": {
|
|
267
257
|
"svelte": "./src/work/detail/task-detail.svelte",
|
|
268
258
|
"types": "./src/work/detail/task-detail.svelte",
|
|
@@ -430,7 +420,6 @@
|
|
|
430
420
|
},
|
|
431
421
|
"peerDependencies": {
|
|
432
422
|
"@internationalized/date": "^3.12.4",
|
|
433
|
-
"@tabler/icons-svelte-runes": "^3.46.0",
|
|
434
423
|
"@xyflow/svelte": "^1.7.0",
|
|
435
424
|
"bits-ui": "^2.19.5",
|
|
436
425
|
"mode-watcher": "^1.1.0",
|
|
@@ -447,9 +436,6 @@
|
|
|
447
436
|
"@internationalized/date": {
|
|
448
437
|
"optional": true
|
|
449
438
|
},
|
|
450
|
-
"@tabler/icons-svelte-runes": {
|
|
451
|
-
"optional": true
|
|
452
|
-
},
|
|
453
439
|
"@xyflow/svelte": {
|
|
454
440
|
"optional": true
|
|
455
441
|
}
|
package/scripts/sync-shadcn.mjs
CHANGED
|
@@ -141,24 +141,22 @@ const publicImports = (demo) =>
|
|
|
141
141
|
.join("\n");
|
|
142
142
|
|
|
143
143
|
/* The section's list, generated: a primitive reaches the page by being synced,
|
|
144
|
-
* never by being typed into an array.
|
|
144
|
+
* never by being typed into an array. Names and words only: the site's navigation reads this
|
|
145
|
+
* list on every page, and each demo loads from `./<name>.svelte` on its own page. */
|
|
145
146
|
async function writeManifest(demos) {
|
|
146
147
|
const rows = demos.map(
|
|
147
148
|
(d) =>
|
|
148
|
-
` { name: ${JSON.stringify(d.name)}, title: ${JSON.stringify(d.title)}, note: ${JSON.stringify(d.note)}, path: ${JSON.stringify(d.imports)}
|
|
149
|
+
` { name: ${JSON.stringify(d.name)}, title: ${JSON.stringify(d.title)}, note: ${JSON.stringify(d.note)}, path: ${JSON.stringify(d.imports)} },`,
|
|
149
150
|
);
|
|
150
151
|
const text = [
|
|
151
152
|
"/* Generated by scripts/sync-shadcn.mjs. Do not edit: the demos are upstream's",
|
|
152
|
-
" * own, and the sync rewrites this list. */",
|
|
153
|
-
'import type { Component } from "svelte";',
|
|
154
|
-
...demos.map((d) => `import ${pascal(d.name)}Demo from "./${d.name}.svelte";`),
|
|
153
|
+
" * own, and the sync rewrites this list. Each demo loads from `./<name>.svelte`. */",
|
|
155
154
|
"",
|
|
156
155
|
"export type ShadcnDemo = {",
|
|
157
156
|
" name: string;",
|
|
158
157
|
" title: string;",
|
|
159
158
|
" note: string;",
|
|
160
159
|
" path: string;",
|
|
161
|
-
" Comp: Component;",
|
|
162
160
|
"};",
|
|
163
161
|
"",
|
|
164
162
|
"export const SHADCN_DEMOS: ShadcnDemo[] = [",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
-
import type {
|
|
4
|
+
import type { ConfirmationKind } from "./confirmation-context.svelte.js";
|
|
5
5
|
|
|
6
6
|
export interface ConfirmationProps extends HTMLAttributes<HTMLElement> {
|
|
7
|
-
kind:
|
|
7
|
+
kind: ConfirmationKind;
|
|
8
8
|
executing?: boolean;
|
|
9
9
|
class?: string;
|
|
10
10
|
children?: Snippet;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
+
import type { ConfirmationKind } from "./confirmation-context.svelte.js";
|
|
3
4
|
|
|
4
|
-
export interface
|
|
5
|
+
export interface ConfirmationStateProps {
|
|
6
|
+
/** The state whose content this shows. */
|
|
7
|
+
when: Exclude<ConfirmationKind, "idle">;
|
|
5
8
|
children?: Snippet;
|
|
6
9
|
}
|
|
7
10
|
</script>
|
|
@@ -9,12 +12,12 @@
|
|
|
9
12
|
<script lang="ts">
|
|
10
13
|
import { getConfirmationContext } from "./confirmation-context.svelte.js";
|
|
11
14
|
|
|
12
|
-
let { children }:
|
|
15
|
+
let { when, children }: ConfirmationStateProps = $props();
|
|
13
16
|
|
|
14
17
|
const ctx = getConfirmationContext();
|
|
15
18
|
</script>
|
|
16
19
|
|
|
17
|
-
{#if ctx.kind ===
|
|
20
|
+
{#if ctx.kind === when}
|
|
18
21
|
<!-- WHY: the wrapper mirrors the parent section's stack so the state swap can
|
|
19
22
|
animate as one block without changing the children's layout. -->
|
|
20
23
|
<div
|
|
@@ -2,10 +2,10 @@ import { getContext, setContext } from "svelte";
|
|
|
2
2
|
|
|
3
3
|
const CONFIRMATION_CONTEXT_KEY = Symbol("confirmation-context");
|
|
4
4
|
|
|
5
|
-
export type
|
|
5
|
+
export type ConfirmationKind = "idle" | "requested" | "accepted" | "rejected" | "failed";
|
|
6
6
|
|
|
7
7
|
export interface ConfirmationContextValue {
|
|
8
|
-
readonly kind:
|
|
8
|
+
readonly kind: ConfirmationKind;
|
|
9
9
|
readonly executing: boolean;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Every state part under one Confirmation, for the state-swap test.
|
|
3
|
+
import {
|
|
4
|
+
Confirmation,
|
|
5
|
+
ConfirmationState,
|
|
6
|
+
type ConfirmationKind,
|
|
7
|
+
} from "./index.js";
|
|
8
|
+
|
|
9
|
+
let { kind }: { kind: ConfirmationKind } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<Confirmation {kind}>
|
|
13
|
+
<ConfirmationState when="requested">request</ConfirmationState>
|
|
14
|
+
<ConfirmationState when="accepted">accepted</ConfirmationState>
|
|
15
|
+
<ConfirmationState when="rejected">rejected</ConfirmationState>
|
|
16
|
+
<ConfirmationState when="failed">failed</ConfirmationState>
|
|
17
|
+
</Confirmation>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { flushSync, mount, unmount } from "svelte";
|
|
2
|
+
import { describe, expect, it } from "vite-plus/test";
|
|
3
|
+
import type { ConfirmationKind } from "./index.js";
|
|
4
|
+
import Parts from "./confirmation-parts.test.svelte";
|
|
5
|
+
|
|
6
|
+
function shown(kind: ConfirmationKind): string {
|
|
7
|
+
const target = document.createElement("div");
|
|
8
|
+
const app = mount(Parts, { target, props: { kind } });
|
|
9
|
+
flushSync();
|
|
10
|
+
const text = target.textContent?.replace(/\s+/g, " ").trim() ?? "";
|
|
11
|
+
void unmount(app);
|
|
12
|
+
return text;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("ConfirmationState", () => {
|
|
16
|
+
it("shows only the part whose `when` matches the kind", () => {
|
|
17
|
+
expect(shown("requested")).toBe("request");
|
|
18
|
+
expect(shown("accepted")).toBe("accepted");
|
|
19
|
+
expect(shown("rejected")).toBe("rejected");
|
|
20
|
+
expect(shown("failed")).toBe("failed");
|
|
21
|
+
expect(shown("idle")).toBe("");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,50 +1,35 @@
|
|
|
1
1
|
import Confirmation from "./Confirmation.svelte";
|
|
2
2
|
import ConfirmationTitle from "./ConfirmationTitle.svelte";
|
|
3
|
-
import
|
|
4
|
-
import ConfirmationAccepted from "./ConfirmationAccepted.svelte";
|
|
5
|
-
import ConfirmationRejected from "./ConfirmationRejected.svelte";
|
|
6
|
-
import ConfirmationFailed from "./ConfirmationFailed.svelte";
|
|
3
|
+
import ConfirmationState from "./ConfirmationState.svelte";
|
|
7
4
|
import ConfirmationActions from "./ConfirmationActions.svelte";
|
|
8
5
|
import ConfirmationAction from "./ConfirmationAction.svelte";
|
|
9
6
|
|
|
10
7
|
import type { ConfirmationProps } from "./Confirmation.svelte";
|
|
11
8
|
import type { ConfirmationTitleProps } from "./ConfirmationTitle.svelte";
|
|
12
|
-
import type {
|
|
13
|
-
import type { ConfirmationAcceptedProps } from "./ConfirmationAccepted.svelte";
|
|
14
|
-
import type { ConfirmationRejectedProps } from "./ConfirmationRejected.svelte";
|
|
15
|
-
import type { ConfirmationFailedProps } from "./ConfirmationFailed.svelte";
|
|
9
|
+
import type { ConfirmationStateProps } from "./ConfirmationState.svelte";
|
|
16
10
|
import type { ConfirmationActionsProps } from "./ConfirmationActions.svelte";
|
|
17
11
|
import type { ConfirmationActionProps } from "./ConfirmationAction.svelte";
|
|
18
12
|
|
|
19
13
|
export type {
|
|
20
14
|
ConfirmationContextValue,
|
|
21
|
-
|
|
15
|
+
ConfirmationKind,
|
|
22
16
|
} from "./confirmation-context.svelte.js";
|
|
23
17
|
|
|
24
18
|
export {
|
|
25
19
|
Confirmation,
|
|
26
20
|
ConfirmationTitle,
|
|
27
|
-
|
|
28
|
-
ConfirmationAccepted,
|
|
29
|
-
ConfirmationRejected,
|
|
30
|
-
ConfirmationFailed,
|
|
21
|
+
ConfirmationState,
|
|
31
22
|
ConfirmationActions,
|
|
32
23
|
ConfirmationAction,
|
|
33
24
|
type ConfirmationProps,
|
|
34
25
|
type ConfirmationTitleProps,
|
|
35
|
-
type
|
|
36
|
-
type ConfirmationAcceptedProps,
|
|
37
|
-
type ConfirmationRejectedProps,
|
|
38
|
-
type ConfirmationFailedProps,
|
|
26
|
+
type ConfirmationStateProps,
|
|
39
27
|
type ConfirmationActionsProps,
|
|
40
28
|
type ConfirmationActionProps,
|
|
41
29
|
//
|
|
42
30
|
Confirmation as Root,
|
|
43
31
|
ConfirmationTitle as Title,
|
|
44
|
-
|
|
45
|
-
ConfirmationAccepted as Accepted,
|
|
46
|
-
ConfirmationRejected as Rejected,
|
|
47
|
-
ConfirmationFailed as Failed,
|
|
32
|
+
ConfirmationState as State,
|
|
48
33
|
ConfirmationActions as Actions,
|
|
49
34
|
ConfirmationAction as Action,
|
|
50
35
|
};
|
package/src/ai/index.ts
CHANGED
|
@@ -46,28 +46,18 @@ export {
|
|
|
46
46
|
export {
|
|
47
47
|
Confirmation,
|
|
48
48
|
ConfirmationTitle,
|
|
49
|
-
|
|
50
|
-
ConfirmationAccepted,
|
|
51
|
-
ConfirmationRejected,
|
|
49
|
+
ConfirmationState,
|
|
52
50
|
ConfirmationActions,
|
|
53
51
|
ConfirmationAction,
|
|
54
52
|
type ConfirmationProps,
|
|
55
53
|
type ConfirmationTitleProps,
|
|
56
|
-
type
|
|
57
|
-
type ConfirmationAcceptedProps,
|
|
58
|
-
type ConfirmationRejectedProps,
|
|
54
|
+
type ConfirmationStateProps,
|
|
59
55
|
type ConfirmationActionsProps,
|
|
60
56
|
type ConfirmationActionProps,
|
|
61
57
|
type ConfirmationContextValue,
|
|
62
|
-
type
|
|
58
|
+
type ConfirmationKind,
|
|
63
59
|
} from "./confirmation/index.js";
|
|
64
60
|
|
|
65
|
-
// =========================
|
|
66
|
-
// THINKING DOTS
|
|
67
|
-
// =========================
|
|
68
|
-
|
|
69
|
-
export { ThinkingDots, type ThinkingDotsProps } from "./thinking-dots/index.js";
|
|
70
|
-
|
|
71
61
|
// =========================
|
|
72
62
|
// VOICE WAVEFORM
|
|
73
63
|
// =========================
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { HTMLAttributes } from "svelte/elements";
|
|
3
3
|
|
|
4
|
-
export interface TypingDotsProps extends HTMLAttributes<
|
|
4
|
+
export interface TypingDotsProps extends HTMLAttributes<HTMLElement> {
|
|
5
5
|
/** Dot diameter in px. */
|
|
6
6
|
size?: number;
|
|
7
|
+
/** Set, even to "", the dots become a `role="status"` line; the text shows when non-empty. */
|
|
8
|
+
label?: string;
|
|
7
9
|
class?: string;
|
|
8
10
|
}
|
|
9
11
|
</script>
|
|
@@ -12,16 +14,34 @@
|
|
|
12
14
|
import "./TypingDots.css";
|
|
13
15
|
import { cn } from "../../lib/utils.js";
|
|
14
16
|
|
|
15
|
-
let { size = 4, class: className, ...rest }: TypingDotsProps = $props();
|
|
17
|
+
let { size = 4, label, class: className, ...rest }: TypingDotsProps = $props();
|
|
16
18
|
</script>
|
|
17
19
|
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
{#snippet dots(extra?: HTMLAttributes<HTMLElement>, dotsClass?: string)}
|
|
21
|
+
<span
|
|
22
|
+
class={cn("typing-dots", dotsClass)}
|
|
23
|
+
style:--typing-dot-size="{size}px"
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
{...extra}
|
|
26
|
+
>
|
|
27
|
+
<span class="typing-dot"></span>
|
|
28
|
+
<span class="typing-dot"></span>
|
|
29
|
+
<span class="typing-dot"></span>
|
|
30
|
+
</span>
|
|
31
|
+
{/snippet}
|
|
32
|
+
|
|
33
|
+
{#if label === undefined}
|
|
34
|
+
{@render dots(rest, className)}
|
|
35
|
+
{:else}
|
|
36
|
+
<div
|
|
37
|
+
class={cn(
|
|
38
|
+
"inline-flex items-center gap-2 text-sm text-[color:var(--ai-thinking-fg,inherit)]",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
role="status"
|
|
42
|
+
{...rest}
|
|
43
|
+
>
|
|
44
|
+
{#if label}<span>{label}</span>{/if}
|
|
45
|
+
{@render dots()}
|
|
46
|
+
</div>
|
|
47
|
+
{/if}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { flushSync, mount, unmount } from "svelte";
|
|
2
|
+
import { describe, expect, it } from "vite-plus/test";
|
|
3
|
+
import TypingDots, { type TypingDotsProps } from "./TypingDots.svelte";
|
|
4
|
+
|
|
5
|
+
function render(props: TypingDotsProps): HTMLElement {
|
|
6
|
+
const target = document.createElement("div");
|
|
7
|
+
const app = mount(TypingDots, { target, props });
|
|
8
|
+
flushSync();
|
|
9
|
+
const root = target.firstElementChild as HTMLElement;
|
|
10
|
+
void unmount(app);
|
|
11
|
+
return root;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("TypingDots", () => {
|
|
15
|
+
it("is three hidden dots with no label", () => {
|
|
16
|
+
const root = render({});
|
|
17
|
+
expect(root.getAttribute("aria-hidden")).toBe("true");
|
|
18
|
+
expect(root.getAttribute("role")).toBeNull();
|
|
19
|
+
expect(root.querySelectorAll(".typing-dot")).toHaveLength(3);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("becomes a status line with its text when labelled", () => {
|
|
23
|
+
const root = render({ label: "Thinking", class: "extra" });
|
|
24
|
+
expect(root.getAttribute("role")).toBe("status");
|
|
25
|
+
expect(root.textContent?.trim()).toBe("Thinking");
|
|
26
|
+
expect(root.classList.contains("extra")).toBe(true);
|
|
27
|
+
expect(root.querySelector(".typing-dots")?.getAttribute("aria-hidden")).toBe("true");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("keeps the status role and the caller's aria-label for an empty label", () => {
|
|
31
|
+
const root = render({ label: "", "aria-label": "Thinking" });
|
|
32
|
+
expect(root.getAttribute("role")).toBe("status");
|
|
33
|
+
expect(root.getAttribute("aria-label")).toBe("Thinking");
|
|
34
|
+
expect(root.textContent?.trim()).toBe("");
|
|
35
|
+
});
|
|
36
|
+
});
|
package/src/animations/modal.css
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
--modal-scale: var(--scale-large, 0.96);
|
|
8
8
|
--modal-scale-close: var(--scale-large, 0.96);
|
|
9
9
|
--modal-ease: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
|
|
10
|
-
--modal-backdrop-color: rgb(0 0 0 / 0.
|
|
10
|
+
--modal-backdrop-color: var(--scrim, rgb(0 0 0 / 0.32));
|
|
11
|
+
--modal-backdrop-blur: var(--scrim-blur, 2px);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/* === Upstream: class-based toggle === */
|
|
@@ -54,6 +55,8 @@
|
|
|
54
55
|
/* WHY: pair .t-modal-backdrop with the modal so the overlay fades in sync. */
|
|
55
56
|
.t-modal-backdrop {
|
|
56
57
|
background: var(--modal-backdrop-color);
|
|
58
|
+
-webkit-backdrop-filter: blur(var(--modal-backdrop-blur));
|
|
59
|
+
backdrop-filter: blur(var(--modal-backdrop-blur));
|
|
57
60
|
opacity: 0;
|
|
58
61
|
transition: opacity var(--modal-open-dur) var(--modal-ease);
|
|
59
62
|
}
|
|
@@ -62,6 +62,15 @@ describe("diffToOps", () => {
|
|
|
62
62
|
const replayed = applyOps({ nodes: [], edges: [] }, diffToOps(base, current));
|
|
63
63
|
expect(replayed.nodes).toHaveLength(2);
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
it("replays the delete of a connected node", () => {
|
|
67
|
+
const base = doc([n("a"), n("b")], [{ id: "e1", source: "a", target: "b" }]);
|
|
68
|
+
const current = doc([n("a")]);
|
|
69
|
+
|
|
70
|
+
const replayed = applyOps({ nodes: base.nodes, edges: base.edges }, diffToOps(base, current));
|
|
71
|
+
expect(replayed.nodes).toHaveLength(1);
|
|
72
|
+
expect(replayed.edges).toHaveLength(0);
|
|
73
|
+
});
|
|
65
74
|
});
|
|
66
75
|
|
|
67
76
|
describe("rebaseDiagram", () => {
|
|
@@ -186,6 +195,34 @@ describe("rebaseDiagram", () => {
|
|
|
186
195
|
const local = doc([n("a"), n("b", { data: { label: "edited" } })]);
|
|
187
196
|
expect(rebaseDiagram(serverDoc, lastSaved, local)).toBeNull();
|
|
188
197
|
});
|
|
198
|
+
|
|
199
|
+
describe("deletes", () => {
|
|
200
|
+
const connected = doc([n("a"), n("b")], [{ id: "e1", source: "a", target: "b" }]);
|
|
201
|
+
|
|
202
|
+
it("merges a local delete of a connected node", () => {
|
|
203
|
+
const server = doc([n("a"), n("b"), n("c")], [{ id: "e1", source: "a", target: "b" }]);
|
|
204
|
+
const local = doc([n("a")]);
|
|
205
|
+
|
|
206
|
+
const rebased = expectDefined(rebaseDiagram(server, connected, local), "rebased doc");
|
|
207
|
+
expect(rebased.nodes.map((node) => node.id)).toEqual(["a", "c"]);
|
|
208
|
+
expect(rebased.edges).toEqual([]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("merges when both sides deleted the same node", () => {
|
|
212
|
+
const server = doc([n("a"), n("c")]);
|
|
213
|
+
const local = doc([n("a")]);
|
|
214
|
+
|
|
215
|
+
const rebased = expectDefined(rebaseDiagram(server, connected, local), "rebased doc");
|
|
216
|
+
expect(rebased.nodes.map((node) => node.id)).toEqual(["a", "c"]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("merges when both sides deleted the same edge", () => {
|
|
220
|
+
const both = doc([n("a"), n("b")]);
|
|
221
|
+
|
|
222
|
+
const rebased = expectDefined(rebaseDiagram(both, connected, both), "rebased doc");
|
|
223
|
+
expect(rebased.edges).toHaveLength(0);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
189
226
|
});
|
|
190
227
|
|
|
191
228
|
describe("a new edge keeps everything it was drawn with", () => {
|
|
@@ -164,8 +164,14 @@ function patchEdgeOps(before: DocEdge, edge: DocEdge): CanvasOp[] {
|
|
|
164
164
|
function edgeOps(base: DiagramDoc, current: DiagramDoc): CanvasOp[] {
|
|
165
165
|
const baseEdges = new Map(base.edges.map((e) => [e.id, e]));
|
|
166
166
|
const currentIds = new Set(current.edges.map((e) => e.id));
|
|
167
|
+
const currentNodeIds = new Set(current.nodes.map((n) => n.id));
|
|
168
|
+
const removedNodeIds = new Set(
|
|
169
|
+
base.nodes.filter((n) => !currentNodeIds.has(n.id)).map((n) => n.id),
|
|
170
|
+
);
|
|
171
|
+
// WHY: removeNode already takes the node's edges, and removeEdge throws on a missing id.
|
|
167
172
|
const removed: CanvasOp[] = base.edges
|
|
168
173
|
.filter((e) => !currentIds.has(e.id))
|
|
174
|
+
.filter((e) => !removedNodeIds.has(String(e.source)) && !removedNodeIds.has(String(e.target)))
|
|
169
175
|
.map((e) => ({ kind: "removeEdge", edgeId: e.id }));
|
|
170
176
|
const upserted = current.edges.flatMap((edge) => {
|
|
171
177
|
const before = baseEdges.get(edge.id);
|
|
@@ -255,6 +261,32 @@ function parentsFirst(nodes: Record<string, unknown>[]): Record<string, unknown>
|
|
|
255
261
|
return sortParentsFirst(wrapped).map(({ node }) => node);
|
|
256
262
|
}
|
|
257
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Drops removals the server already made, so a delete on both sides merges. Only
|
|
266
|
+
* removeNode and removeEdge: an edit to a node the server deleted must still throw.
|
|
267
|
+
*/
|
|
268
|
+
function withoutServerRemovals(serverDoc: DiagramDoc, ops: CanvasOp[]): CanvasOp[] {
|
|
269
|
+
const serverNodeIds = new Set(serverDoc.nodes.map((n) => n.id));
|
|
270
|
+
const serverEdges = new Map(serverDoc.edges.map((e) => [e.id, e]));
|
|
271
|
+
const removedNodeIds = new Set<string>();
|
|
272
|
+
return ops.filter((op) => {
|
|
273
|
+
if (op.kind === "removeNode") {
|
|
274
|
+
if (!serverNodeIds.has(op.nodeId)) return false;
|
|
275
|
+
removedNodeIds.add(op.nodeId);
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
if (op.kind === "removeEdge") {
|
|
279
|
+
const edge = serverEdges.get(op.edgeId);
|
|
280
|
+
return (
|
|
281
|
+
edge !== undefined &&
|
|
282
|
+
!removedNodeIds.has(String(edge.source)) &&
|
|
283
|
+
!removedNodeIds.has(String(edge.target))
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
258
290
|
/**
|
|
259
291
|
* Replay local changes (lastSaved -> local) on the server document. Returns
|
|
260
292
|
* null when impossible, like a patched node deleted, so the save error shows.
|
|
@@ -264,7 +296,7 @@ export function rebaseDiagram(
|
|
|
264
296
|
lastSaved: DiagramDoc,
|
|
265
297
|
local: DiagramDoc,
|
|
266
298
|
): RawCanvasData | null {
|
|
267
|
-
const ops = diffToOps(lastSaved, local);
|
|
299
|
+
const ops = withoutServerRemovals(serverDoc, diffToOps(lastSaved, local));
|
|
268
300
|
const res = tryCatch(() => applyOps({ nodes: serverDoc.nodes, edges: serverDoc.edges }, ops));
|
|
269
301
|
if (!res.ok) return null;
|
|
270
302
|
const nodes = applyNodeFieldPatches(res.value.nodes, nodeFieldPatches(lastSaved, local));
|
|
@@ -43,6 +43,49 @@
|
|
|
43
43
|
transform: translateX(-50%);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/* A phone has no room for a side rail: at 390px a rail on the left covers the
|
|
47
|
+
board xyflow just fitted. The rail lies flat along the bottom edge instead,
|
|
48
|
+
with its style bar stacked above it. */
|
|
49
|
+
@media (max-width: 639px) {
|
|
50
|
+
.svelte-flow .svelte-flow__panel.cv-dock.left.center {
|
|
51
|
+
top: auto;
|
|
52
|
+
bottom: 0;
|
|
53
|
+
left: 50%;
|
|
54
|
+
transform: translateX(-50%);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.cv-dock > .cv-row {
|
|
58
|
+
flex-direction: column-reverse;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.cv-dock .cv-rail {
|
|
62
|
+
flex-direction: row;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* xyflow's credit is #999 on a half-white wash, which turns grey on grey on a
|
|
67
|
+
dark board. It takes the chrome's surface and muted ink in both themes. */
|
|
68
|
+
.svelte-flow .svelte-flow__attribution {
|
|
69
|
+
border-top-left-radius: var(--radius-sm, 4px);
|
|
70
|
+
background: var(--cv-surface);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* On a phone the rail runs along the bottom edge and the credit would sit under
|
|
74
|
+
it, in the frame's rounded corner. It moves to the top right, inset off the
|
|
75
|
+
corner's curve. */
|
|
76
|
+
@media (max-width: 639px) {
|
|
77
|
+
.svelte-flow .svelte-flow__panel.svelte-flow__attribution {
|
|
78
|
+
top: 0;
|
|
79
|
+
bottom: auto;
|
|
80
|
+
margin: 6px 8px;
|
|
81
|
+
border-radius: var(--radius-sm, 4px);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.svelte-flow .svelte-flow__attribution a {
|
|
86
|
+
color: var(--cv-ink-muted);
|
|
87
|
+
}
|
|
88
|
+
|
|
46
89
|
/* Beside the selection: StylePanel writes the spot, and a phone keeps its own row above the pill. */
|
|
47
90
|
@media (min-width: 640px) {
|
|
48
91
|
.svelte-flow .svelte-flow__panel.cv-dock.cv-style-dock.is-placed {
|
|
@@ -115,7 +158,7 @@
|
|
|
115
158
|
}
|
|
116
159
|
|
|
117
160
|
.cv-btn:active:not(:disabled) {
|
|
118
|
-
scale: 0.
|
|
161
|
+
scale: 0.96;
|
|
119
162
|
}
|
|
120
163
|
|
|
121
164
|
/* One active style for a tool, a size, a brush and an open popover. */
|
package/src/charts/README.md
CHANGED
|
@@ -71,8 +71,8 @@ The `@source` line matters: the chart markup carries Tailwind classes, and Tailw
|
|
|
71
71
|
| --- | --- |
|
|
72
72
|
| `DashboardSection`, `PanelGrid`, `StatBand` | A named area, a row of panels side by side, a row of number cards |
|
|
73
73
|
| `StatTile` | A number card on `NumberChart`: a delta, a share track, a trend shape behind the figure, an optional link |
|
|
74
|
-
| `DataPanel
|
|
75
|
-
| `DataTable`, `
|
|
74
|
+
| `DataPanel` | A titled panel for a table or chart. The dashed empty state is `EmptyState surface="dashed"` from `@heyramzi/ui/components/list-page` |
|
|
75
|
+
| `DataTable`, `RatingChip` | The one data table: sortable heads (bind `sort`, the `SortState` TableView takes; a new column starts descending), a row that opens, columns that hide on a phone, share bars, loading and empty states. An A to E grade chip |
|
|
76
76
|
| `PeriodFilter`, `DAY_WINDOWS` | The 7, 30 and 90 day window, as a filter or as links |
|
|
77
77
|
| `CHART_HEIGHT`, `dataTableClass` and friends | The one chart height and the table skin |
|
|
78
78
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import Figure from "./figure.svelte";
|
|
13
13
|
import HoverFocus from "./hover-focus.svelte";
|
|
14
14
|
import { DOMAIN_MOTION, TOOLTIP_GAP, TOOLTIP_TOP } from "./motion.js";
|
|
15
|
+
import { valueTick } from "./primitives/chart-utils.js";
|
|
15
16
|
import Reveal from "./reveal.svelte";
|
|
16
17
|
import ReferenceLine, { type ChartReference } from "./reference-line.svelte";
|
|
17
18
|
import {
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
xKey,
|
|
71
72
|
yKey,
|
|
72
73
|
series: seriesInput,
|
|
73
|
-
color = "var(--chart-1)",
|
|
74
|
+
color = "var(--chart-series-1)",
|
|
74
75
|
height = 240,
|
|
75
76
|
stacked = false,
|
|
76
77
|
smooth = true,
|
|
@@ -119,7 +120,7 @@
|
|
|
119
120
|
? seriesInput.map((s, i) => ({
|
|
120
121
|
key: s.key,
|
|
121
122
|
label: s.label ?? s.key,
|
|
122
|
-
color: s.color ?? `var(--chart-${(i %
|
|
123
|
+
color: s.color ?? `var(--chart-series-${(i % 6) + 1})`,
|
|
123
124
|
}))
|
|
124
125
|
: yKey
|
|
125
126
|
? [{ key: yKey, label: yKey, color }]
|
|
@@ -153,15 +154,7 @@
|
|
|
153
154
|
const fallback = $derived(data.length > 0 ? data[data.length - 1] : null);
|
|
154
155
|
const display = $derived(active ?? fallback);
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
const compact = new Intl.NumberFormat(undefined, {
|
|
158
|
-
notation: "compact",
|
|
159
|
-
maximumFractionDigits: 1,
|
|
160
|
-
});
|
|
161
|
-
function valueTick(v: number): string {
|
|
162
|
-
const full = formatValue(v);
|
|
163
|
-
return full.length > 6 && Number.isFinite(v) ? compact.format(v) : full;
|
|
164
|
-
}
|
|
157
|
+
const tick = $derived(valueTick(formatValue));
|
|
165
158
|
const figureRows = $derived(
|
|
166
159
|
display
|
|
167
160
|
? series.map((s) => ({
|
|
@@ -212,7 +205,7 @@
|
|
|
212
205
|
motion={DOMAIN_MOTION}
|
|
213
206
|
>
|
|
214
207
|
<Svg>
|
|
215
|
-
<Axis placement="left" format={(v) =>
|
|
208
|
+
<Axis placement="left" format={(v) => tick(Number(v))} ticks={4} />
|
|
216
209
|
<Axis placement="bottom" format={(v) => formatX(v)} ticks={ticksForWidth(xTicks)} />
|
|
217
210
|
<g class="hr-area-grid">
|
|
218
211
|
<Axis placement="left" grid rule={false} ticks={4} format={() => ""} />
|