@heyramzi/ui 2.0.1 → 2.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/package.json +48 -24
- package/scripts/sync-shadcn.mjs +3 -4
- package/src/agents/agent-avatar.svelte +604 -575
- package/src/agents/agent-cast.svelte +179 -183
- package/src/agents/demo.svelte +125 -113
- package/src/agents/helpers.ts +23 -17
- package/src/agents/index.ts +4 -4
- package/src/agents/page-state.ts +6 -6
- package/src/agents/types.ts +6 -6
- package/src/ai/ai-button/AiButton.svelte +4 -12
- package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
- package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
- package/src/ai/response/Response.css +2 -3
- package/src/animations/avatar-group-hover.css +2 -16
- package/src/animations/card-tilt.css +3 -6
- package/src/animations/digit-pop-in.css +2 -15
- package/src/animations/dropdown.css +4 -17
- package/src/animations/input-clear-dissolve.css +5 -18
- package/src/animations/input-shake.css +9 -41
- package/src/animations/learn-more-hover.css +17 -9
- package/src/animations/like-button.css +2 -4
- package/src/animations/page-side-by-side.css +2 -10
- package/src/animations/plus-menu-morph.css +2 -5
- package/src/animations/shimmer-text.css +5 -18
- package/src/animations/spinning-counter.css +2 -6
- package/src/animations/success-check.css +2 -15
- package/src/animations/thinking-states.css +2 -7
- package/src/brand/brand.test.ts +21 -5
- package/src/brand/index.ts +4 -10
- package/src/canvas/README.md +9 -12
- package/src/canvas/card/card-frame.svelte +43 -39
- package/src/canvas/card/resize-handles.svelte +89 -80
- package/src/canvas/chrome/chrome-zoom.svelte +101 -100
- package/src/canvas/chrome/lock-badge.svelte +8 -8
- package/src/canvas/chrome/side-handles.svelte +24 -19
- package/src/canvas/chrome/size-chip.svelte +30 -30
- package/src/canvas/chrome/snap-guides.svelte +20 -20
- package/src/canvas/components/icons/ToolIcon.svelte +8 -8
- package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
- package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
- package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
- package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
- package/src/canvas/edges/bend-handle.svelte +46 -46
- package/src/canvas/edges/edge-gradient.svelte +10 -10
- package/src/canvas/edges/gradient-edge.svelte +45 -29
- package/src/canvas/editor/lock.test.ts +6 -1
- package/src/canvas/editor/style.ts +7 -1
- package/src/canvas/headless/svg-figure.test.ts +5 -1
- package/src/canvas/lod/lod-switch.svelte +111 -106
- package/src/canvas/pen/PenField.svelte +87 -81
- package/src/canvas/pen/PenOverlay.svelte +127 -127
- package/src/canvas/pen/ShapeNode.svelte +31 -32
- package/src/canvas/pen/TextNode.svelte +54 -53
- package/src/canvas/pen/host-field.test.svelte +7 -7
- package/src/canvas/pen/shapes.ts +7 -1
- package/src/canvas/screen/GlyphIcon.svelte +22 -17
- package/src/canvas/screen/PartView.svelte +138 -72
- package/src/canvas/screen/ScreenBody.svelte +81 -60
- package/src/canvas/screen/ScreenCard.svelte +20 -20
- package/src/canvas/ui/CanvasToolbar.svelte +173 -85
- package/src/canvas/ui/FrameLayer.svelte +59 -59
- package/src/canvas/ui/PenLayer.svelte +39 -30
- package/src/canvas/ui/StylePanel.svelte +119 -102
- package/src/canvas/ui/canvas-grid.svelte +137 -129
- package/src/charts/animated-value.svelte +1 -1
- package/src/charts/area-chart.css +2 -5
- package/src/charts/area-chart.svelte +1 -1
- package/src/charts/bar-chart.css +2 -3
- package/src/charts/bar-chart.svelte +1 -1
- package/src/charts/charts.css +5 -17
- package/src/charts/crosshair.svelte +1 -1
- package/src/charts/dashboard/dashboard-empty.svelte +25 -25
- package/src/charts/dashboard/dashboard-section.svelte +24 -24
- package/src/charts/dashboard/data-panel.svelte +50 -50
- package/src/charts/dashboard/data-sort-head.svelte +56 -56
- package/src/charts/dashboard/data-table.svelte +138 -138
- package/src/charts/dashboard/panel-grid.svelte +30 -30
- package/src/charts/dashboard/period-filter.svelte +25 -25
- package/src/charts/dashboard/rating-chip.svelte +19 -19
- package/src/charts/dashboard/stat-band.svelte +32 -32
- package/src/charts/dashboard/stat-tile.svelte +106 -99
- package/src/charts/donut-chart.svelte +1 -1
- package/src/charts/edge-fade.svelte +1 -1
- package/src/charts/figure.svelte +1 -1
- package/src/charts/gauge-chart.css +4 -7
- package/src/charts/gauge-chart.svelte +1 -1
- package/src/charts/hover-focus.svelte +1 -1
- package/src/charts/legend.css +1 -3
- package/src/charts/legend.svelte +1 -1
- package/src/charts/line-chart.svelte +1 -1
- package/src/charts/number-chart.css +6 -9
- package/src/charts/number-chart.svelte +1 -1
- package/src/charts/reference-line.svelte +19 -19
- package/src/charts/reveal.svelte +1 -1
- package/src/charts/ring-chart.css +2 -4
- package/src/charts/ring-chart.svelte +1 -1
- package/src/charts/sparkline.svelte +5 -2
- package/src/charts/stat-card.svelte +1 -1
- package/src/charts/time-series-chart.css +2 -5
- package/src/charts/trend-pill.css +2 -4
- package/src/charts/trend-pill.svelte +1 -1
- package/src/components/copy/copy-button.svelte +93 -98
- package/src/components/copy/copy-button.test.ts +10 -2
- package/src/components/data-table/infinite-scroll.svelte +42 -42
- package/src/components/data-table/pagination.svelte +34 -22
- package/src/components/data-table/row-arrow.svelte +19 -16
- package/src/components/data-table/row-context-menu.svelte +52 -52
- package/src/components/data-table/sortable-head.svelte +25 -25
- package/src/components/data-table/table-skeleton.svelte +41 -36
- package/src/components/docs/doc-page.svelte +18 -9
- package/src/components/docs/on-this-page.svelte +16 -3
- package/src/components/docs/scroll-spy.svelte.ts +2 -3
- package/src/components/field-row/field-row.svelte +10 -8
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
- package/src/components/icon/icon.svelte +14 -14
- package/src/components/language-flag/language-flag.svelte +3 -1
- package/src/components/list-page/detail-empty.svelte +34 -34
- package/src/components/list-page/detail-header.svelte +55 -47
- package/src/components/list-page/detail-panel.svelte +51 -59
- package/src/components/list-page/detail-row.svelte +19 -19
- package/src/components/list-page/detail-stack.svelte +21 -21
- package/src/components/list-page/empty-state.svelte +30 -30
- package/src/components/list-page/filter-item.svelte +20 -20
- package/src/components/list-page/filter-popover.svelte +60 -55
- package/src/components/list-page/grouped-list.svelte +4 -2
- package/src/components/list-page/index.ts +8 -4
- package/src/components/list-page/list-page-shell.svelte +2 -5
- package/src/components/list-page/master-detail.svelte +87 -88
- package/src/components/list-page/notice-bar.svelte +38 -38
- package/src/components/list-page/prev-next.svelte +63 -63
- package/src/components/list-page/rail-body.svelte +18 -18
- package/src/components/list-page/rail-empty.svelte +23 -30
- package/src/components/list-page/rail-head.svelte +24 -24
- package/src/components/list-page/rail-row.svelte +77 -73
- package/src/components/list-page/section-host.ts +2 -3
- package/src/components/locale-switch/locale-switch.svelte +41 -41
- package/src/components/mark/mark.svelte +34 -38
- package/src/components/open-in/index.ts +1 -5
- package/src/components/open-in/open-in.svelte +40 -40
- package/src/components/picker/mark-picker.svelte +49 -49
- package/src/components/picker/picker.svelte +5 -3
- package/src/components/section-header/index.ts +1 -0
- package/src/components/section-header/section-header.svelte +1 -1
- package/src/components/section-header/section-header.test.ts +7 -1
- package/src/components/section-header/section-label.svelte +39 -0
- package/src/components/segmented/animated-tabs.svelte +31 -2
- package/src/components/segmented/collapse.test.ts +2 -1
- package/src/components/segmented/index.ts +2 -0
- package/src/components/segmented/segment.svelte +52 -49
- package/src/components/segmented/segmented-filter.svelte +79 -80
- package/src/components/segmented/segmented-nav.svelte +34 -40
- package/src/components/segmented/segmented-overflow.svelte +53 -50
- package/src/components/segmented/segmented-track.svelte +62 -46
- package/src/components/segmented/segmented.ts +2 -3
- package/src/components/star-rating-input/star-rating-input.svelte +103 -103
- package/src/components/svg/svg.svelte +4 -4
- package/src/components/table-view/table-view.svelte +17 -2
- package/src/components/table-view/table-view.test.ts +7 -1
- package/src/components/table-view/types.ts +11 -11
- package/src/components/tags-input/index.ts +13 -0
- package/src/components/tags-input/tags-input.svelte +305 -0
- package/src/components/tags-input/tags.test.ts +64 -0
- package/src/components/tags-input/tags.ts +142 -0
- package/src/components/theme/index.ts +2 -4
- package/src/components/theme/theme-provider.svelte +1 -3
- package/src/components/toolbar/index.ts +2 -0
- package/src/components/{list-page → toolbar}/toolbar-search.svelte +25 -25
- package/src/components/toolbar/toolbar.svelte +13 -9
- package/src/components/toolbar/toolbar.test.ts +10 -1
- package/src/design/index.css +21 -78
- package/src/design/polish.css +26 -35
- package/src/editor/editor-toolbar.svelte +104 -99
- package/src/editor/editor.svelte +296 -266
- package/src/editor/image-toolbar.svelte +20 -22
- package/src/editor/index.ts +1 -4
- package/src/editor/inline-editor.svelte +280 -238
- package/src/editor/markdown.ts +15 -15
- package/src/editor/renderer.svelte +71 -71
- package/src/editor/slash-menu.svelte +24 -27
- package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
- package/src/effects/image-comparison/ImageComparison.svelte +11 -1
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
- package/src/effects/magic-card/MagicCard.css +14 -7
- package/src/effects/marquee/Marquee.svelte +3 -5
- package/src/effects/reveal/observe.ts +0 -3
- package/src/effects/spotlight/Spotlight.svelte +1 -1
- package/src/effects/theme-toggle/ThemeToggle.css +25 -22
- package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
- package/src/gallery.test.ts +14 -4
- package/src/inbox/README.md +27 -0
- package/src/inbox/inbox.test.ts +214 -0
- package/src/inbox/index.ts +31 -0
- package/src/inbox/logic.ts +169 -0
- package/src/inbox/thread-inbox.svelte +439 -0
- package/src/inbox/types.ts +161 -0
- package/src/lib/theme-transition.ts +2 -4
- package/src/lib/tone.ts +2 -4
- package/src/lib/utils.ts +2 -2
- package/src/modules.test.ts +105 -0
- package/src/onboarding/define-tours.test.ts +55 -40
- package/src/onboarding/define-tours.ts +23 -23
- package/src/onboarding/demo.svelte +24 -5
- package/src/onboarding/index.ts +1 -8
- package/src/onboarding/onboarding-provider.svelte +19 -7
- package/src/onboarding/onboarding.svelte.ts +16 -16
- package/src/onboarding/pick.test.ts +61 -52
- package/src/onboarding/pick.ts +16 -13
- package/src/onboarding/stores.test.ts +80 -80
- package/src/onboarding/stores.ts +41 -39
- package/src/onboarding/tour-card.svelte +30 -5
- package/src/onboarding/tour-runner.svelte +12 -3
- package/src/onboarding/types.ts +20 -12
- package/src/onboarding/wait.ts +22 -22
- package/src/panel/README.md +5 -0
- package/src/panel/index.ts +2 -0
- package/src/panel/side-panel.svelte +194 -0
- package/src/record/README.md +3 -0
- package/src/record/demo-data.ts +352 -0
- package/src/record/field-section.svelte +57 -0
- package/src/record/fields.ts +194 -0
- package/src/record/format.ts +94 -0
- package/src/{tasks/record → record}/index.ts +49 -35
- package/src/{tasks/record → record}/info-list.svelte +145 -114
- package/src/record/kinds.ts +42 -0
- package/src/record/logic.ts +326 -0
- package/src/record/next-up.svelte +81 -0
- package/src/{tasks/record → record}/project-list.svelte +56 -50
- package/src/{tasks/record → record}/record-demo.svelte +130 -110
- package/src/{tasks/record → record}/record-header.svelte +37 -28
- package/src/{tasks/record → record}/record-shell.svelte +54 -54
- package/src/record/record-stats.svelte +48 -0
- package/src/record/record-timeline.svelte +251 -0
- package/src/record/record.test.ts +471 -0
- package/src/{tasks/record → record}/steps-rail.svelte +225 -216
- package/src/record/types.ts +299 -0
- package/src/record/where-we-are.svelte +125 -0
- package/src/shadcn/button/button.svelte +1 -1
- package/src/shadcn/card/card-title.svelte +1 -5
- package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
- package/src/shadcn/sheet/sheet-content.svelte +2 -4
- package/src/shadcn/table/table-cell.svelte +2 -3
- package/src/shadcn/utilities.css +3 -8
- package/src/slides/Controls.svelte +150 -124
- package/src/slides/Deck.svelte +243 -238
- package/src/slides/Fragment.svelte +64 -57
- package/src/slides/Overview.svelte +106 -101
- package/src/slides/Presenter.svelte +300 -266
- package/src/slides/Slide.svelte +185 -170
- package/src/slides/SlideList.svelte +16 -12
- package/src/slides/ambient.d.ts +1 -1
- package/src/slides/bridges.ts +42 -23
- package/src/slides/deck-controller.svelte.ts +47 -35
- package/src/slides/demo-slides.svelte +330 -306
- package/src/slides/demo.svelte +164 -160
- package/src/slides/export/capture.ts +20 -16
- package/src/slides/export/ffmpeg.ts +43 -38
- package/src/slides/export/index.ts +5 -5
- package/src/slides/export/launch.ts +3 -1
- package/src/slides/export/pdf.ts +30 -27
- package/src/slides/export/types.ts +10 -8
- package/src/slides/index.ts +11 -11
- package/src/slides/labels.ts +22 -19
- package/src/slides/layout.ts +9 -2
- package/src/slides/model.ts +4 -4
- package/src/slides/navigation.ts +43 -31
- package/src/slides/theme.css +5 -7
- package/src/slides/types.ts +9 -10
- package/src/status/README.md +18 -0
- package/src/status/define-statuses.ts +25 -0
- package/src/status/index.ts +11 -0
- package/src/{tasks → status}/status-badge.svelte +3 -1
- package/src/{tasks → status}/status-dot.svelte +1 -3
- package/src/{tasks → status}/status-icon.svelte +1 -1
- package/src/status/status-types.ts +40 -0
- package/src/tasks/README.md +1 -1
- package/src/tasks/board-order.ts +2 -4
- package/src/tasks/demo.svelte +192 -34
- package/src/tasks/detail/detail-demo.svelte +75 -15
- package/src/tasks/detail/detail-types.ts +45 -45
- package/src/tasks/detail/task-detail.svelte +40 -10
- package/src/tasks/detail/task-property.svelte +8 -2
- package/src/tasks/detail/task-thread.svelte +53 -12
- package/src/tasks/gantt/gantt-bar.svelte +17 -6
- package/src/tasks/gantt/gantt-grid.svelte +40 -9
- package/src/tasks/gantt/gantt-group-row.svelte +8 -2
- package/src/tasks/gantt/gantt-header.svelte +9 -3
- package/src/tasks/gantt/gantt-task-row.svelte +26 -6
- package/src/tasks/gantt/gantt.css +2 -1
- package/src/tasks/gantt/gantt.test.ts +4 -2
- package/src/tasks/gantt/logic.test.ts +110 -20
- package/src/tasks/gantt/logic.ts +42 -6
- package/src/tasks/inbox/inbox-card.svelte +15 -7
- package/src/tasks/inbox/inbox-draft.svelte +1 -1
- package/src/tasks/inbox/inbox-logic.ts +6 -40
- package/src/tasks/inbox/inbox-statuses.ts +24 -24
- package/src/tasks/inbox/inbox-trace.svelte +9 -3
- package/src/tasks/inbox/inbox-types.ts +54 -58
- package/src/tasks/inbox/index.ts +22 -21
- package/src/tasks/index.ts +52 -53
- package/src/tasks/kanban-board.svelte +16 -12
- package/src/tasks/kanban-types.ts +2 -3
- package/src/tasks/presets/sales.ts +7 -7
- package/src/tasks/task-gantt.svelte +28 -8
- package/src/tasks/task-list.svelte +41 -17
- package/src/tasks/time/duration.ts +40 -40
- package/src/tasks/time/time-chip.svelte +9 -2
- package/src/tasks/time/time-dot.svelte +1 -1
- package/src/tasks/time/time-ring.svelte +11 -3
- package/src/tasks/time/time-tracker.svelte +57 -15
- package/src/tasks/time/time-types.ts +57 -57
- package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
- package/src/thread/README.md +31 -0
- package/src/thread/index.ts +12 -0
- package/src/thread/logic.ts +61 -0
- package/src/thread/thread.svelte +432 -0
- package/src/thread/thread.test.ts +73 -0
- package/src/thread/types.ts +93 -0
- package/src/tokens/craft.css +17 -53
- package/src/tokens/index.css +3 -13
- package/src/tokens/palette.css +13 -11
- package/src/tokens/primitives.css +10 -23
- package/src/components/list-page/section-label.svelte +0 -39
- package/src/tasks/define-statuses.ts +0 -32
- package/src/tasks/inbox/inbox.svelte +0 -203
- package/src/tasks/record/demo-data.ts +0 -286
- package/src/tasks/record/field-section.svelte +0 -53
- package/src/tasks/record/fields.ts +0 -142
- package/src/tasks/record/format.ts +0 -87
- package/src/tasks/record/kinds.ts +0 -42
- package/src/tasks/record/logic.ts +0 -324
- package/src/tasks/record/next-up.svelte +0 -70
- package/src/tasks/record/record-stats.svelte +0 -48
- package/src/tasks/record/record-timeline.svelte +0 -231
- package/src/tasks/record/record.test.ts +0 -350
- package/src/tasks/record/types.ts +0 -280
- package/src/tasks/record/where-we-are.svelte +0 -121
- package/src/tasks/side-panel.svelte +0 -193
- package/src/tasks/status-types.ts +0 -40
- /package/src/components/{list-page → toolbar}/toolbar-trigger.ts +0 -0
- /package/src/{tasks → panel}/side-panel-types.ts +0 -0
- /package/src/{tasks/record → record}/gutter.ts +0 -0
package/src/slides/demo.svelte
CHANGED
|
@@ -1,176 +1,180 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import Deck from "./Deck.svelte";
|
|
3
|
+
import type { DeckController } from "./deck-controller.svelte.js";
|
|
4
|
+
import DemoSlides, { demoStyle } from "./demo-slides.svelte";
|
|
5
|
+
import type { DeckTheme, DeckTransition } from "./types.js";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Where the presenter view is mounted. Leave out to hide the button. */
|
|
9
|
+
presenter?: string;
|
|
10
|
+
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
let { presenter }: Props = $props();
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const themes: DeckTheme[] = ["ink", "paper", "auto"];
|
|
15
|
+
const transitions: DeckTransition[] = ["fade", "slide", "zoom", "morph"];
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
let theme = $state<DeckTheme>("ink");
|
|
18
|
+
let transition = $state<DeckTransition>("fade");
|
|
19
|
+
let deck = $state<DeckController>();
|
|
20
20
|
</script>
|
|
21
21
|
|
|
22
22
|
<div class="demo">
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
23
|
+
<div class="demo__bar">
|
|
24
|
+
<div class="demo__group" role="radiogroup" aria-label="Theme">
|
|
25
|
+
{#each themes as option (option)}
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
role="radio"
|
|
29
|
+
aria-checked={theme === option}
|
|
30
|
+
class="demo__option"
|
|
31
|
+
onclick={() => (theme = option)}>{option}</button
|
|
32
|
+
>
|
|
33
|
+
{/each}
|
|
34
|
+
</div>
|
|
35
|
+
<label class="demo__field">
|
|
36
|
+
<span>Transition</span>
|
|
37
|
+
<select bind:value={transition}>
|
|
38
|
+
{#each transitions as option (option)}
|
|
39
|
+
<option value={option}>{option}</option>
|
|
40
|
+
{/each}
|
|
41
|
+
</select>
|
|
42
|
+
</label>
|
|
43
|
+
<div class="demo__actions">
|
|
44
|
+
<button type="button" class="demo__action" onclick={() => deck?.toggleOverview()}
|
|
45
|
+
>Overview</button
|
|
46
|
+
>
|
|
47
|
+
{#if presenter}
|
|
48
|
+
<a class="demo__action" href={presenter} target="heyramzi-slides-demo-presenter"
|
|
49
|
+
>Presenter</a
|
|
50
|
+
>
|
|
51
|
+
{/if}
|
|
52
|
+
<button type="button" class="demo__action" onclick={() => window.print()}>Print</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
<div class="demo__stage">
|
|
57
|
+
<Deck
|
|
58
|
+
bind:controller={deck}
|
|
59
|
+
id="slides-demo"
|
|
60
|
+
title="Slides demo"
|
|
61
|
+
sync="slides-demo"
|
|
62
|
+
urlSync={false}
|
|
63
|
+
{theme}
|
|
64
|
+
{transition}
|
|
65
|
+
{presenter}
|
|
66
|
+
style={demoStyle}
|
|
67
|
+
>
|
|
68
|
+
<DemoSlides />
|
|
69
|
+
</Deck>
|
|
70
|
+
</div>
|
|
67
71
|
</div>
|
|
68
72
|
|
|
69
73
|
<style>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
.demo {
|
|
75
|
+
display: grid;
|
|
76
|
+
gap: 14px;
|
|
77
|
+
width: 100%;
|
|
78
|
+
}
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
80
|
+
.demo__bar {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: 10px 16px;
|
|
85
|
+
font-size: 13px;
|
|
86
|
+
}
|
|
87
|
+
.demo__group {
|
|
88
|
+
display: inline-flex;
|
|
89
|
+
padding: 3px;
|
|
90
|
+
background: color-mix(in srgb, currentColor 7%, transparent);
|
|
91
|
+
border-radius: 10px;
|
|
92
|
+
}
|
|
93
|
+
.demo__option {
|
|
94
|
+
padding: 5px 12px;
|
|
95
|
+
font: inherit;
|
|
96
|
+
font-weight: 500;
|
|
97
|
+
color: inherit;
|
|
98
|
+
text-transform: capitalize;
|
|
99
|
+
background: transparent;
|
|
100
|
+
border: 0;
|
|
101
|
+
border-radius: 7px;
|
|
102
|
+
opacity: 0.65;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
transition:
|
|
105
|
+
background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out),
|
|
106
|
+
opacity var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
|
|
107
|
+
}
|
|
108
|
+
@media (hover: hover) and (pointer: fine) {
|
|
109
|
+
.demo__option:hover {
|
|
110
|
+
opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
.demo__option[aria-checked="true"] {
|
|
114
|
+
background: color-mix(in srgb, currentColor 14%, transparent);
|
|
115
|
+
opacity: 1;
|
|
116
|
+
}
|
|
117
|
+
.demo__field {
|
|
118
|
+
display: inline-flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: 8px;
|
|
121
|
+
opacity: 0.85;
|
|
122
|
+
}
|
|
123
|
+
.demo__field select {
|
|
124
|
+
padding: 5px 8px;
|
|
125
|
+
font: inherit;
|
|
126
|
+
color: inherit;
|
|
127
|
+
text-transform: capitalize;
|
|
128
|
+
background: color-mix(in srgb, currentColor 7%, transparent);
|
|
129
|
+
border: 0;
|
|
130
|
+
border-radius: 8px;
|
|
131
|
+
}
|
|
132
|
+
.demo__actions {
|
|
133
|
+
display: inline-flex;
|
|
134
|
+
gap: 6px;
|
|
135
|
+
margin-left: auto;
|
|
136
|
+
}
|
|
137
|
+
.demo__action {
|
|
138
|
+
padding: 6px 12px;
|
|
139
|
+
font: inherit;
|
|
140
|
+
font-weight: 500;
|
|
141
|
+
color: inherit;
|
|
142
|
+
text-decoration: none;
|
|
143
|
+
background: transparent;
|
|
144
|
+
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
|
|
145
|
+
border-radius: 8px;
|
|
146
|
+
cursor: pointer;
|
|
147
|
+
transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
|
|
148
|
+
}
|
|
149
|
+
@media (hover: hover) and (pointer: fine) {
|
|
150
|
+
.demo__action:hover {
|
|
151
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
.demo__option:focus-visible,
|
|
155
|
+
.demo__action:focus-visible,
|
|
156
|
+
.demo__field select:focus-visible {
|
|
157
|
+
outline: 2px solid currentColor;
|
|
158
|
+
outline-offset: 2px;
|
|
159
|
+
}
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
.demo__stage {
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
border-radius: 16px;
|
|
164
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 14%, transparent);
|
|
165
|
+
}
|
|
162
166
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
@media print {
|
|
168
|
+
.demo {
|
|
169
|
+
display: block;
|
|
170
|
+
}
|
|
171
|
+
.demo__bar {
|
|
172
|
+
display: none;
|
|
173
|
+
}
|
|
174
|
+
.demo__stage {
|
|
175
|
+
overflow: visible;
|
|
176
|
+
border-radius: 0;
|
|
177
|
+
box-shadow: none;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
176
180
|
</style>
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
// Capture a page, or one element of it, to PNG at a canvas size and device scale.
|
|
2
2
|
|
|
3
|
-
import { mkdir, writeFile } from
|
|
4
|
-
import path from
|
|
5
|
-
import type {
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import type {
|
|
6
|
+
BrowserLike,
|
|
7
|
+
LocatorLike,
|
|
8
|
+
PageLike,
|
|
9
|
+
Size,
|
|
10
|
+
WaitUntil,
|
|
11
|
+
} from "./types.js";
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
|
-
* `networkidle`
|
|
9
|
-
*
|
|
10
|
-
* video. So the first attempt asks for idle and a retry falls back to `load`,
|
|
11
|
-
* which always fires; the caller's `wait` settles whatever is left.
|
|
14
|
+
* `networkidle` never fires on a page that keeps polling, like a chat widget, so a retry
|
|
15
|
+
* falls back to `load`, which always fires.
|
|
12
16
|
*/
|
|
13
|
-
export const DEFAULT_WAITS: readonly WaitUntil[] = [
|
|
17
|
+
export const DEFAULT_WAITS: readonly WaitUntil[] = ["networkidle", "load"];
|
|
14
18
|
|
|
15
19
|
export interface GotoOptions {
|
|
16
20
|
/** Tried in order; the first that resolves wins and the last failure is thrown. */
|
|
@@ -30,7 +34,7 @@ export async function gotoSettled(page: PageLike, url: string, options: GotoOpti
|
|
|
30
34
|
failure = error;
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
|
-
throw failure ?? new Error(
|
|
37
|
+
throw failure ?? new Error("gotoSettled needs at least one wait condition.");
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export type CaptureSource = { url: string } | { html: string };
|
|
@@ -64,18 +68,18 @@ export async function capturePage(
|
|
|
64
68
|
deviceScaleFactor: options.scale ?? 1,
|
|
65
69
|
});
|
|
66
70
|
try {
|
|
67
|
-
if (
|
|
68
|
-
else await page.setContent(source.html, { waitUntil:
|
|
71
|
+
if ("url" in source) await gotoSettled(page, source.url, options.goto);
|
|
72
|
+
else await page.setContent(source.html, { waitUntil: "load" });
|
|
69
73
|
if (options.style) await page.addStyleTag({ content: options.style });
|
|
70
74
|
if (options.wait) await page.waitForTimeout(options.wait);
|
|
71
75
|
const { selector } = options;
|
|
72
76
|
let bytes: Uint8Array;
|
|
73
77
|
if (selector) {
|
|
74
78
|
const target = page.locator(selector).first();
|
|
75
|
-
await target.waitFor({ state:
|
|
76
|
-
bytes = await target.screenshot({ type:
|
|
79
|
+
await target.waitFor({ state: "visible" });
|
|
80
|
+
bytes = await target.screenshot({ type: "png" });
|
|
77
81
|
} else {
|
|
78
|
-
bytes = await page.screenshot({ type:
|
|
82
|
+
bytes = await page.screenshot({ type: "png", fullPage: options.fullPage });
|
|
79
83
|
}
|
|
80
84
|
if (options.path) {
|
|
81
85
|
await mkdir(path.dirname(options.path), { recursive: true });
|
|
@@ -88,8 +92,8 @@ export async function capturePage(
|
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
/** `01.png`, `02.png` ...: the file name for the nth capture, one-based, zero-padded. */
|
|
91
|
-
export function frameName(index: number, digits = 2, extension =
|
|
92
|
-
return `${String(index + 1).padStart(digits,
|
|
95
|
+
export function frameName(index: number, digits = 2, extension = "png"): string {
|
|
96
|
+
return `${String(index + 1).padStart(digits, "0")}.${extension}`;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
/**
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
// Encode frames to an mp4 with the ffmpeg on the machine. The package ships no
|
|
2
2
|
// binary; pass `ffmpeg` to name one that is not on PATH.
|
|
3
3
|
|
|
4
|
-
import { spawn } from
|
|
5
|
-
import { mkdir, rm } from
|
|
6
|
-
import path from
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
7
|
|
|
8
8
|
/** Runs ffmpeg and resolves when it exits 0; the error carries the tail of its stderr. */
|
|
9
|
-
export function runFfmpeg(args: string[], ffmpeg =
|
|
9
|
+
export function runFfmpeg(args: string[], ffmpeg = "ffmpeg"): Promise<void> {
|
|
10
10
|
return new Promise((resolve, reject) => {
|
|
11
|
-
const child = spawn(ffmpeg, args, { stdio: [
|
|
12
|
-
let stderr =
|
|
13
|
-
child.stderr.on(
|
|
11
|
+
const child = spawn(ffmpeg, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
12
|
+
let stderr = "";
|
|
13
|
+
child.stderr.on("data", (chunk) => {
|
|
14
14
|
stderr += chunk;
|
|
15
15
|
});
|
|
16
|
-
child.on(
|
|
17
|
-
reject(
|
|
16
|
+
child.on("error", (error) =>
|
|
17
|
+
reject(
|
|
18
|
+
new Error(
|
|
19
|
+
`${ffmpeg} could not be started (${error.message}). Video export needs ffmpeg on PATH, or pass its path.`,
|
|
20
|
+
),
|
|
21
|
+
),
|
|
18
22
|
);
|
|
19
|
-
child.on(
|
|
20
|
-
code === 0
|
|
23
|
+
child.on("close", (code) =>
|
|
24
|
+
code === 0
|
|
25
|
+
? resolve()
|
|
26
|
+
: reject(new Error(`${ffmpeg} exited ${code}:\n${stderr.slice(-1500)}`)),
|
|
21
27
|
);
|
|
22
28
|
});
|
|
23
29
|
}
|
|
@@ -36,31 +42,35 @@ export interface EncodeOptions {
|
|
|
36
42
|
/** The ffmpeg arguments for a silent yuv420p H.264 mp4 from a numbered image sequence. */
|
|
37
43
|
export function encodeArgs(dir: string, outFile: string, options: EncodeOptions): string[] {
|
|
38
44
|
return [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
"-y",
|
|
46
|
+
"-hide_banner",
|
|
47
|
+
"-loglevel",
|
|
48
|
+
"error",
|
|
49
|
+
"-framerate",
|
|
44
50
|
String(options.fps),
|
|
45
|
-
|
|
46
|
-
path.join(dir, options.pattern ??
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
options.preset ??
|
|
54
|
-
|
|
51
|
+
"-i",
|
|
52
|
+
path.join(dir, options.pattern ?? "%04d.png"),
|
|
53
|
+
"-an",
|
|
54
|
+
"-vf",
|
|
55
|
+
"format=yuv420p",
|
|
56
|
+
"-c:v",
|
|
57
|
+
"libx264",
|
|
58
|
+
"-preset",
|
|
59
|
+
options.preset ?? "slow",
|
|
60
|
+
"-crf",
|
|
55
61
|
String(options.crf ?? 23),
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
"-movflags",
|
|
63
|
+
"+faststart",
|
|
58
64
|
outFile,
|
|
59
65
|
];
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
/** Encodes a folder of numbered frames. There is no audio stream at all. */
|
|
63
|
-
export async function encodeFrames(
|
|
69
|
+
export async function encodeFrames(
|
|
70
|
+
dir: string,
|
|
71
|
+
outFile: string,
|
|
72
|
+
options: EncodeOptions,
|
|
73
|
+
): Promise<void> {
|
|
64
74
|
await mkdir(path.dirname(outFile), { recursive: true });
|
|
65
75
|
await runFfmpeg(encodeArgs(dir, outFile, options), options.ffmpeg);
|
|
66
76
|
}
|
|
@@ -74,7 +84,7 @@ export interface LoopFrame {
|
|
|
74
84
|
file: string;
|
|
75
85
|
}
|
|
76
86
|
|
|
77
|
-
export interface LoopOptions extends Omit<EncodeOptions,
|
|
87
|
+
export interface LoopOptions extends Omit<EncodeOptions, "fps" | "pattern"> {
|
|
78
88
|
fps?: number;
|
|
79
89
|
seconds?: number;
|
|
80
90
|
/** Photograph the frame at `t`; called once per frame, in order. */
|
|
@@ -87,13 +97,8 @@ export function loopFrameCount(seconds: number, fps: number): number {
|
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
/**
|
|
90
|
-
* Renders a seamless loop
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* `t` never reaches 1, so the last frame is one step short of the first and
|
|
94
|
-
* the cut back to frame zero is the same step as every other. Driving all
|
|
95
|
-
* motion off one t is what makes the seam exact by construction. A captured
|
|
96
|
-
* CSS animation loops only when its last frame happens to land on its first.
|
|
100
|
+
* Renders a seamless loop from `drawFrame` at t = 0 to (n-1)/n, never 1, so the cut back
|
|
101
|
+
* to frame zero is the same step as every other.
|
|
97
102
|
*/
|
|
98
103
|
export async function renderLoop(
|
|
99
104
|
outFile: string,
|
|
@@ -109,7 +114,7 @@ export async function renderLoop(
|
|
|
109
114
|
await options.drawFrame({
|
|
110
115
|
index,
|
|
111
116
|
t: index / frames,
|
|
112
|
-
file: path.join(work, `${String(index).padStart(4,
|
|
117
|
+
file: path.join(work, `${String(index).padStart(4, "0")}.png`),
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
120
|
await encodeFrames(work, outFile, { ...options, fps });
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// depending on Playwright or bundling ffmpeg. Hand it the Playwright pages and
|
|
3
3
|
// browsers you already have.
|
|
4
4
|
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export type * from
|
|
5
|
+
export * from "./capture.js";
|
|
6
|
+
export * from "./ffmpeg.js";
|
|
7
|
+
export * from "./launch.js";
|
|
8
|
+
export * from "./pdf.js";
|
|
9
|
+
export type * from "./types.js";
|
|
@@ -6,5 +6,7 @@
|
|
|
6
6
|
export async function launchChromium<B>(chromium: {
|
|
7
7
|
launch(options?: { headless?: boolean; channel?: string }): Promise<B>;
|
|
8
8
|
}): Promise<B> {
|
|
9
|
-
return chromium
|
|
9
|
+
return chromium
|
|
10
|
+
.launch({ headless: true })
|
|
11
|
+
.catch(() => chromium.launch({ headless: true, channel: "chrome" }));
|
|
10
12
|
}
|