@heyramzi/ui 3.0.0 → 3.2.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/charts/dual-axis.js +38 -0
- 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/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/dist/slides/deck-controller.svelte.js +62 -15
- package/dist/slides/layout.js +3 -2
- package/package.json +9 -2
- package/scripts/svelte-dts.mjs +3 -0
- package/scripts/sync-shadcn.mjs +0 -5
- 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/area-chart.css +6 -0
- package/src/charts/area-chart.svelte +115 -17
- package/src/charts/area-chart.svelte.d.ts +11 -2
- package/src/charts/chart-tokens.css +4 -3
- 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/dual-axis.test.ts +40 -0
- package/src/charts/dual-axis.ts +43 -0
- 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 +231 -193
- package/src/editor/editor.svelte +57 -39
- 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/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 +23 -10
- package/src/slides/Overview.svelte +10 -4
- package/src/slides/README.md +1 -1
- package/src/slides/Slide.svelte +17 -11
- package/src/slides/deck-controller.svelte.ts +62 -16
- package/src/slides/layout.ts +3 -2
- package/src/slides/theme.css +34 -5
- 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
package/src/slides/Slide.svelte
CHANGED
|
@@ -152,19 +152,19 @@
|
|
|
152
152
|
transition-delay: 0s;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
/*
|
|
156
|
-
|
|
155
|
+
/*
|
|
156
|
+
* The overview shows every slide. Opening and closing move them as one rigid grid, like a
|
|
157
|
+
* camera pulling back or pushing in, so no slide gets its own delay.
|
|
158
|
+
*/
|
|
159
|
+
:global(.deck[data-view="overview"]) .slide,
|
|
160
|
+
:global(.deck[data-zoom]) .slide {
|
|
157
161
|
visibility: visible;
|
|
162
|
+
transition-delay: 0s;
|
|
158
163
|
}
|
|
159
|
-
:global(.deck[data-
|
|
164
|
+
:global(.deck[data-zoom="run"]) .slide {
|
|
160
165
|
transition:
|
|
161
|
-
visibility 0s
|
|
162
|
-
transform
|
|
163
|
-
var(--deck-ease);
|
|
164
|
-
}
|
|
165
|
-
/* Opening shows the slides at once, so only closing waits before hiding them. */
|
|
166
|
-
:global(.deck[data-morphing][data-view="overview"]) .slide {
|
|
167
|
-
transition-delay: 0s;
|
|
166
|
+
visibility 0s,
|
|
167
|
+
transform var(--deck-t-zoom) var(--deck-ease);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
.slide__surface {
|
|
@@ -178,11 +178,17 @@
|
|
|
178
178
|
font-family: var(--deck-font);
|
|
179
179
|
animation: var(--deck-t-slide) var(--deck-ease) backwards;
|
|
180
180
|
}
|
|
181
|
-
|
|
181
|
+
/* Framed on one slide the grid looks like that slide, so it keeps a slide's square corners. */
|
|
182
|
+
:global(.deck[data-view="overview"]:not([data-zoom="hold"])) .slide__surface {
|
|
182
183
|
/* Design px, so divide by the scale to get the same corner on every thumbnail. */
|
|
183
184
|
border-radius: calc(10px / var(--slide-scale, 1));
|
|
184
185
|
box-shadow: 0 0 0 calc(1px / var(--slide-scale, 1)) var(--deck-line);
|
|
185
186
|
}
|
|
187
|
+
:global(.deck[data-zoom="run"]) .slide__surface {
|
|
188
|
+
transition:
|
|
189
|
+
border-radius var(--deck-t-zoom) var(--deck-ease),
|
|
190
|
+
box-shadow var(--deck-t-zoom) var(--deck-ease);
|
|
191
|
+
}
|
|
186
192
|
|
|
187
193
|
.slide__surface[data-align="center"],
|
|
188
194
|
.slide__surface[data-align="start"] {
|
|
@@ -44,16 +44,34 @@ export type ControllerOptions = {
|
|
|
44
44
|
labels?: Partial<DeckLabels>;
|
|
45
45
|
/** Wraps a position change so a View Transition can snapshot around it. */
|
|
46
46
|
transition?: (update: () => void) => void;
|
|
47
|
+
/** How long the overview zoom plays, in ms. 0, or left out, swaps at once. */
|
|
48
|
+
zoomMs?: () => number;
|
|
47
49
|
};
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The overview zoom while it plays. `index` is the slide the camera frames. `framed` puts that
|
|
53
|
+
* slide in its full-size spot with the grid around it, and `running` lets the boxes transition.
|
|
54
|
+
*/
|
|
55
|
+
export type Zoom = { index: number; framed: boolean; running: boolean };
|
|
56
|
+
|
|
49
57
|
/** Height the controls bar takes at the bottom of the overview, in px. */
|
|
50
58
|
const CONTROLS_ROOM = 56;
|
|
51
59
|
|
|
52
|
-
/** How long the overview morph flag stays up, a little past the longest stagger. */
|
|
53
|
-
const MORPH_MS = 900;
|
|
54
|
-
|
|
55
60
|
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
|
|
56
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Where `cell` lands when the camera moves `target` onto `fit`. Every box shares one scale and
|
|
64
|
+
* shift, and a transform tween is linear in both, so the grid stays rigid while it zooms.
|
|
65
|
+
*/
|
|
66
|
+
function frame(cell: Box, target: Box, fit: Box): Box {
|
|
67
|
+
const k = fit.scale / target.scale;
|
|
68
|
+
return {
|
|
69
|
+
x: fit.x + (cell.x - target.x) * k,
|
|
70
|
+
y: fit.y + (cell.y - target.y) * k,
|
|
71
|
+
scale: cell.scale * k,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
function isEditable(el: HTMLElement) {
|
|
58
76
|
return el.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
|
|
59
77
|
}
|
|
@@ -71,12 +89,13 @@ export class DeckController {
|
|
|
71
89
|
/** The highlighted thumbnail in the overview, as an index among the visible slides. */
|
|
72
90
|
cursor = $state(0);
|
|
73
91
|
direction = $state<1 | -1>(1);
|
|
74
|
-
|
|
92
|
+
zoom = $state<Zoom | null>(null);
|
|
75
93
|
size = $state<Size>({ w: 0, h: 0 });
|
|
76
94
|
|
|
77
95
|
readonly options!: ControllerOptions;
|
|
78
96
|
#listeners = new Set<Listener>();
|
|
79
97
|
#timer: ReturnType<typeof setTimeout> | undefined;
|
|
98
|
+
#frame = 0;
|
|
80
99
|
|
|
81
100
|
constructor(options: ControllerOptions) {
|
|
82
101
|
this.options = options;
|
|
@@ -116,8 +135,13 @@ export class DeckController {
|
|
|
116
135
|
|
|
117
136
|
/** Where slide `index` sits right now: centred to fit, or in its overview cell. */
|
|
118
137
|
boxOf(index: number): Box | undefined {
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
const zoom = this.zoom;
|
|
139
|
+
const cell = this.grid.boxes[this.visible.indexOf(index)];
|
|
140
|
+
if (zoom?.framed) {
|
|
141
|
+
const target = this.grid.boxes[this.visible.indexOf(zoom.index)];
|
|
142
|
+
return cell && target ? frame(cell, target, this.fit) : this.fit;
|
|
143
|
+
}
|
|
144
|
+
return this.overview || zoom ? cell : this.fit;
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
register(entry: SlideEntry): () => void {
|
|
@@ -137,7 +161,7 @@ export class DeckController {
|
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
mount(): () => void {
|
|
140
|
-
return () =>
|
|
164
|
+
return () => this.#stopZoom();
|
|
141
165
|
}
|
|
142
166
|
|
|
143
167
|
/** Which deck hears a key is the page's call: Deck passes only keys from focus inside it. */
|
|
@@ -150,14 +174,16 @@ export class DeckController {
|
|
|
150
174
|
this.options.step = position.step;
|
|
151
175
|
}
|
|
152
176
|
|
|
153
|
-
|
|
177
|
+
/** `animate: false` skips the slide transition, for a move the overview zoom already shows. */
|
|
178
|
+
commit(target: Position | null, source: Source = "local", animate = true) {
|
|
154
179
|
if (!target) return;
|
|
155
180
|
const from = this.position;
|
|
156
181
|
const to = settle(this.slides, target);
|
|
157
182
|
if (to.index === from.index && to.step === from.step) return;
|
|
158
183
|
this.direction = to.index >= from.index ? 1 : -1;
|
|
159
184
|
const apply = () => this.#write(to);
|
|
160
|
-
if (to.index !== from.index && this.options.transition)
|
|
185
|
+
if (animate && to.index !== from.index && this.options.transition)
|
|
186
|
+
this.options.transition(apply);
|
|
161
187
|
else apply();
|
|
162
188
|
for (const listener of this.#listeners) listener(to, source);
|
|
163
189
|
}
|
|
@@ -174,25 +200,45 @@ export class DeckController {
|
|
|
174
200
|
this.commit({ index, step }, source);
|
|
175
201
|
}
|
|
176
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Opening zooms the camera out from the current slide to the whole grid. Closing zooms into
|
|
205
|
+
* the current slide, so `select` moves there first.
|
|
206
|
+
*/
|
|
177
207
|
toggleOverview(force?: boolean) {
|
|
178
208
|
const open = force ?? !this.overview;
|
|
179
209
|
if (open === this.overview) return;
|
|
180
|
-
|
|
210
|
+
const index = this.position.index;
|
|
211
|
+
if (open) this.cursor = Math.max(0, this.visible.indexOf(index));
|
|
181
212
|
this.view = open ? "overview" : "slide";
|
|
182
|
-
this.#
|
|
213
|
+
this.#stopZoom();
|
|
214
|
+
const ms = this.options.zoomMs?.() ?? 0;
|
|
215
|
+
if (ms <= 0 || !this.visible.includes(index)) return;
|
|
216
|
+
|
|
217
|
+
/*
|
|
218
|
+
* Each way paints its start state first and moves on a later frame. Moved in the same
|
|
219
|
+
* flush, a style read in between (the overview handing focus back) commits the boxes
|
|
220
|
+
* before the transition rule is on, and the zoom jumps to its end.
|
|
221
|
+
*/
|
|
222
|
+
this.zoom = { index, framed: open, running: !open };
|
|
223
|
+
this.#frame = requestAnimationFrame(() => {
|
|
224
|
+
this.#frame = requestAnimationFrame(() => {
|
|
225
|
+
this.zoom = { index, framed: !open, running: true };
|
|
226
|
+
this.#timer = setTimeout(() => this.#stopZoom(), ms);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
183
229
|
}
|
|
184
230
|
|
|
185
231
|
/** Leaves the overview on slide `index`. Picking the current slide keeps its step. */
|
|
186
232
|
select(index: number) {
|
|
187
|
-
this.toggleOverview(false);
|
|
188
233
|
const keep = index === this.position.index;
|
|
189
|
-
this.
|
|
234
|
+
this.commit({ index, step: keep ? this.position.step : 0 }, "local", false);
|
|
235
|
+
this.toggleOverview(false);
|
|
190
236
|
}
|
|
191
237
|
|
|
192
|
-
#
|
|
193
|
-
this.morphing = true;
|
|
238
|
+
#stopZoom() {
|
|
194
239
|
clearTimeout(this.#timer);
|
|
195
|
-
this.#
|
|
240
|
+
cancelAnimationFrame(this.#frame);
|
|
241
|
+
this.zoom = null;
|
|
196
242
|
}
|
|
197
243
|
|
|
198
244
|
#moveCursor(to: number) {
|
package/src/slides/layout.ts
CHANGED
|
@@ -25,7 +25,8 @@ function scaleFor(
|
|
|
25
25
|
): number {
|
|
26
26
|
const rows = Math.ceil(count / cols);
|
|
27
27
|
const byWidth = (view.w - 2 * pad - (cols - 1) * gap) / cols / canvas.w;
|
|
28
|
-
|
|
28
|
+
/* Every row, the last too, keeps a gap under it for its numbers. */
|
|
29
|
+
const byHeight = (view.h - 2 * pad - rows * gap) / rows / canvas.h;
|
|
29
30
|
return Math.min(byWidth, byHeight);
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -47,7 +48,7 @@ export function gridBoxes(view: Size, canvas: Canvas, count: number): Grid {
|
|
|
47
48
|
const cellH = canvas.h * scale;
|
|
48
49
|
const rows = Math.ceil(count / cols);
|
|
49
50
|
const left = (view.w - (cols * cellW + (cols - 1) * gap)) / 2;
|
|
50
|
-
const top = (view.h -
|
|
51
|
+
const top = (view.h - rows * (cellH + gap)) / 2;
|
|
51
52
|
|
|
52
53
|
const boxes = Array.from({ length: count }, (_, i) => ({
|
|
53
54
|
x: left + (i % cols) * (cellW + gap),
|
package/src/slides/theme.css
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* Everything global to a deck, in design px since the whole canvas scales, and in flat
|
|
5
|
-
* hex because Chromium's PDF pipeline shifts oklch.
|
|
5
|
+
* hex because Chromium's PDF pipeline shifts oklch. The accent comes from the host app's
|
|
6
|
+
* `--primary`, which is often oklch, so it goes through `color-mix(in srgb)` to land as sRGB.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
@layer components {
|
|
@@ -12,8 +13,30 @@
|
|
|
12
13
|
|
|
13
14
|
--deck-bg: light-dark(#f2f1ec, #0e1014);
|
|
14
15
|
--deck-fg: light-dark(#15171b, #f3f1ea);
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
/* Without a `--primary` the accent is the deck's own ink, so nothing ships a colour the app never chose. */
|
|
17
|
+
/*
|
|
18
|
+
* `--primary` follows the page's theme and the deck sets its own scheme: a dark deck on a
|
|
19
|
+
* light page got the page's dark primary on a dark slide (2.7:1). The brand pair is picked
|
|
20
|
+
* by the deck's scheme; `--primary` is the fallback for an app without the kit palette.
|
|
21
|
+
*/
|
|
22
|
+
--deck-accent: color-mix(
|
|
23
|
+
in srgb,
|
|
24
|
+
light-dark(
|
|
25
|
+
var(--brand-primary, var(--primary, #15171b)),
|
|
26
|
+
var(--brand-primary-on-dark, var(--primary, #f3f1ea))
|
|
27
|
+
)
|
|
28
|
+
100%,
|
|
29
|
+
#000
|
|
30
|
+
);
|
|
31
|
+
--deck-on-accent: color-mix(
|
|
32
|
+
in srgb,
|
|
33
|
+
light-dark(
|
|
34
|
+
var(--brand-primary-foreground, var(--primary-foreground, #f6f6f0)),
|
|
35
|
+
var(--brand-primary-foreground-on-dark, var(--primary-foreground, #0e1014))
|
|
36
|
+
)
|
|
37
|
+
100%,
|
|
38
|
+
#000
|
|
39
|
+
);
|
|
17
40
|
--deck-muted: color-mix(in srgb, var(--deck-fg) 66%, transparent);
|
|
18
41
|
--deck-line: color-mix(in srgb, var(--deck-fg) 14%, transparent);
|
|
19
42
|
--deck-surface: color-mix(in srgb, var(--deck-fg) 6%, transparent);
|
|
@@ -32,6 +55,8 @@
|
|
|
32
55
|
|
|
33
56
|
--deck-t-slide: var(--duration-fast, 250ms);
|
|
34
57
|
--deck-t-morph: var(--duration-slow, 300ms);
|
|
58
|
+
/* The overview zoom moves the whole canvas, so it takes the overlay length, not the UI ceiling. */
|
|
59
|
+
--deck-t-zoom: var(--duration-overlay, 500ms);
|
|
35
60
|
--deck-t-quick: var(--duration-quick, 150ms);
|
|
36
61
|
--deck-stagger: var(--duration-stagger, 40ms);
|
|
37
62
|
--deck-ease: var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
|
|
@@ -124,8 +149,12 @@
|
|
|
124
149
|
animation-name: deck-zoom;
|
|
125
150
|
}
|
|
126
151
|
|
|
127
|
-
|
|
128
|
-
|
|
152
|
+
/*
|
|
153
|
+
* The zoom already shows the slide arriving, so its enter animation finishes at once. A
|
|
154
|
+
* 0s duration, not `animation: none`, which would replay the enter when the zoom ends.
|
|
155
|
+
*/
|
|
156
|
+
.deck[data-zoom] .slide__surface {
|
|
157
|
+
animation-duration: 0s !important;
|
|
129
158
|
}
|
|
130
159
|
|
|
131
160
|
/* A morph runs on the whole page, so it gets the deck's timing while it plays. */
|
package/src/thread/thread.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
+
import { Toggle } from "bits-ui";
|
|
3
4
|
import { ArrowBendDownRight, Check, MapPin, PencilSimple, Trash } from "phosphor-svelte";
|
|
4
5
|
import { cn } from "../lib/utils.js";
|
|
5
6
|
import { focusAfterRender } from "../lib/focus.js";
|
|
@@ -338,14 +339,20 @@
|
|
|
338
339
|
{labels.title}
|
|
339
340
|
</h2>
|
|
340
341
|
{#if resolved > 0}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
342
|
+
<!-- A filter on the list, so it reports pressed, not expanded. The wash it had as an
|
|
343
|
+
expanded ghost button moves to aria-pressed. -->
|
|
344
|
+
<Toggle.Root bind:pressed={showResolved}>
|
|
345
|
+
{#snippet child({ props })}
|
|
346
|
+
<Button
|
|
347
|
+
{...props}
|
|
348
|
+
variant="ghost"
|
|
349
|
+
size="sm"
|
|
350
|
+
class="aria-pressed:bg-foreground/6 aria-pressed:text-foreground"
|
|
351
|
+
>
|
|
352
|
+
{showResolved ? labels.hideResolved : fillCount(labels.showResolved, resolved)}
|
|
353
|
+
</Button>
|
|
354
|
+
{/snippet}
|
|
355
|
+
</Toggle.Root>
|
|
349
356
|
{/if}
|
|
350
357
|
</div>
|
|
351
358
|
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
variant="text"
|
|
160
160
|
onclick={startTitle}
|
|
161
161
|
disabled={!onTitleChange}
|
|
162
|
-
aria-label={labels.editTitle}
|
|
162
|
+
aria-label="{labels.editTitle}: {title}"
|
|
163
163
|
class={cn(
|
|
164
164
|
"group -mx-2 -my-1 px-2 py-1 text-2xl leading-tight font-semibold tracking-tight disabled:opacity-100",
|
|
165
165
|
!onTitleChange && "hover:bg-transparent",
|
|
@@ -140,9 +140,8 @@
|
|
|
140
140
|
</button>
|
|
141
141
|
{:else if !row.own}
|
|
142
142
|
<!-- A parent with no dates of its own: the span of what is below it. Not draggable. -->
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
tabindex="0"
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
146
145
|
aria-label="{title}, {describe(span)}"
|
|
147
146
|
title="{title}, {describe(span)}"
|
|
148
147
|
class={cn(
|
|
@@ -155,11 +154,9 @@
|
|
|
155
154
|
style:width="{barWidth(span, layout)}px"
|
|
156
155
|
style:height="{Math.max(layout.rowHeight - 24, 10)}px"
|
|
157
156
|
onclick={() => onSelect?.(row.id)}
|
|
158
|
-
onkeydown={(e) =>
|
|
159
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), onSelect?.(row.id))}
|
|
160
157
|
onpointerenter={() => onHover(row.id, true)}
|
|
161
158
|
onpointerleave={() => onHover(row.id, false)}
|
|
162
|
-
></
|
|
159
|
+
></button>
|
|
163
160
|
{:else}
|
|
164
161
|
<GanttBar
|
|
165
162
|
id={row.id}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import InboxTraceView from "./inbox-trace.svelte";
|
|
29
29
|
import { TagChip } from "../../components/chip/index.js";
|
|
30
30
|
import { workLooks } from "../../components/work-parts/looks.js";
|
|
31
|
+
import { measureClamp } from "../../record/measure-clamp.js";
|
|
31
32
|
import { FolderSimple } from "phosphor-svelte";
|
|
32
33
|
import { Copied } from "../../components/copy/copied.svelte.js";
|
|
33
34
|
import type { InboxDraftEdits, InboxItem, InboxVerdict } from "./inbox-types.js";
|
|
@@ -68,10 +69,7 @@
|
|
|
68
69
|
// for a link the host already shows.
|
|
69
70
|
let hasTrace = $derived(item.trace.evidence.length > 0 || !!why);
|
|
70
71
|
let expanded = $state(false);
|
|
71
|
-
let
|
|
72
|
-
!!facts.command &&
|
|
73
|
-
(facts.command.text.length > 240 || facts.command.text.split("\n").length > 3),
|
|
74
|
-
);
|
|
72
|
+
let commandCut = $state(false);
|
|
75
73
|
|
|
76
74
|
let pickedDraft = $state<string>();
|
|
77
75
|
let edits = $state<InboxDraftEdits>({});
|
|
@@ -210,13 +208,17 @@
|
|
|
210
208
|
<!-- The padding sits on a wrapper: on the clamped box itself, the 4th line showed through it. -->
|
|
211
209
|
<div class={cn(workLooks.inset, "w-full px-2.5 py-2")}>
|
|
212
210
|
<pre
|
|
211
|
+
{@attach measureClamp(
|
|
212
|
+
() => expanded,
|
|
213
|
+
(cut) => (commandCut = cut),
|
|
214
|
+
)}
|
|
213
215
|
class={cn(
|
|
214
216
|
"text-foreground font-mono text-xs leading-5 break-words whitespace-pre-wrap",
|
|
215
217
|
!expanded && "line-clamp-3",
|
|
216
218
|
)}><span class="text-muted-foreground select-none"
|
|
217
219
|
>{facts.command.tool} </span>{facts.command.text}</pre>
|
|
218
220
|
</div>
|
|
219
|
-
{#if
|
|
221
|
+
{#if commandCut || expanded}
|
|
220
222
|
<button
|
|
221
223
|
type="button"
|
|
222
224
|
class="text-muted-foreground hover:text-foreground text-xs underline-offset-2 hover:underline"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { Brain } from "phosphor-svelte";
|
|
3
|
+
import * as Trace from "../../shadcn/trace/index.js";
|
|
3
4
|
import type { InboxTrace } from "./inbox-types.js";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
@@ -18,13 +19,16 @@
|
|
|
18
19
|
);
|
|
19
20
|
</script>
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
<Trace.Collapsible class="text-sm">
|
|
23
|
+
<Trace.CollapsibleTrigger class="gap-2">
|
|
24
|
+
<Trace.Line>
|
|
25
|
+
<Trace.Icon><Brain aria-hidden="true" /></Trace.Icon>
|
|
26
|
+
<Trace.Title
|
|
27
|
+
>Trace{count > 0 ? ` · ${count} ${count === 1 ? "quote" : "quotes"}` : ""}</Trace.Title
|
|
28
|
+
>
|
|
29
|
+
</Trace.Line>
|
|
30
|
+
</Trace.CollapsibleTrigger>
|
|
31
|
+
<Trace.CollapsibleContent class="mt-3 mb-0 ml-0 border-l-2 pr-0 pl-3">
|
|
28
32
|
<div class="flex flex-col gap-3">
|
|
29
33
|
{#if label || trace.source.href}
|
|
30
34
|
<p class="text-foreground">
|
|
@@ -54,5 +58,5 @@
|
|
|
54
58
|
<p><span class="text-foreground font-medium">Why:</span> {reasoning}</p>
|
|
55
59
|
{/if}
|
|
56
60
|
</div>
|
|
57
|
-
</
|
|
58
|
-
</
|
|
61
|
+
</Trace.CollapsibleContent>
|
|
62
|
+
</Trace.Collapsible>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
type KanbanColumnView,
|
|
15
15
|
} from "./kanban-board-state.svelte.js";
|
|
16
16
|
import { Button } from "../shadcn/button/index.js";
|
|
17
|
+
import * as Collapsible from "../shadcn/collapsible/index.js";
|
|
17
18
|
import { workLooks } from "../components/work-parts/looks.js";
|
|
18
19
|
import type {
|
|
19
20
|
KanbanCardContext,
|
|
@@ -278,33 +279,47 @@
|
|
|
278
279
|
</div>
|
|
279
280
|
{/if}
|
|
280
281
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
282
|
+
<!-- The root renders no element, so the fold and its button stay items of the column's flex well. -->
|
|
283
|
+
<Collapsible.Root
|
|
284
|
+
bind:open={() => open, (next) => (next ? board.expand(column.id) : board.collapse(column.id))}
|
|
285
|
+
>
|
|
286
|
+
{#snippet child()}
|
|
287
|
+
{#if showTail}
|
|
288
|
+
<!-- The tail sits shut in a 0fr row, so opening and closing animate its height. -->
|
|
289
|
+
<Collapsible.Content forceMount>
|
|
290
|
+
{#snippet child({ props })}
|
|
291
|
+
<div {...props} class="kanban-fold" data-open={open} inert={!open}>
|
|
292
|
+
<div class="kanban-fold-inner" role="list">
|
|
293
|
+
{#each tail as item, index (getId(item))}
|
|
294
|
+
{@render card(column, item, head.length + index, list)}
|
|
295
|
+
{/each}
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
{/snippet}
|
|
299
|
+
</Collapsible.Content>
|
|
300
|
+
{/if}
|
|
291
301
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
302
|
+
{#if dnd.showIndicatorEnd(column.id) && board.idAt(shown, shown.length - 1) !== dnd.draggedId}
|
|
303
|
+
{@render dropSlot()}
|
|
304
|
+
{/if}
|
|
295
305
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
{#if tail.length > 0 || open}
|
|
307
|
+
<Collapsible.Trigger>
|
|
308
|
+
{#snippet child({ props })}
|
|
309
|
+
<Button
|
|
310
|
+
{...props}
|
|
311
|
+
variant="ghost"
|
|
312
|
+
size="sm"
|
|
313
|
+
class="shrink-0 justify-start gap-1.5 px-2 text-xs font-normal text-muted-foreground"
|
|
314
|
+
>
|
|
315
|
+
<KanbanFoldChevron {open} class="size-2.5" />
|
|
316
|
+
<span class="tabular-nums">{open ? lessLabel : moreLabel(tail.length)}</span>
|
|
317
|
+
</Button>
|
|
318
|
+
{/snippet}
|
|
319
|
+
</Collapsible.Trigger>
|
|
320
|
+
{/if}
|
|
321
|
+
{/snippet}
|
|
322
|
+
</Collapsible.Root>
|
|
308
323
|
|
|
309
324
|
{#if columnFooter}
|
|
310
325
|
{@render columnFooter(column, context)}
|