paraframe 0.1.0 → 0.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/README.md +108 -11
- package/dist/Canvas.svelte +9 -1
- package/dist/Player.svelte +15 -12
- package/dist/internal/Menu.svelte +80 -7
- package/dist/internal/Menu.svelte.d.ts +3 -1
- package/dist/internal/MenuItem.svelte +5 -3
- package/dist/internal/MenuItem.svelte.d.ts +1 -1
- package/dist/messages.d.ts +2 -0
- package/dist/messages.js +7 -0
- package/dist/prototype.js +1 -0
- package/dist/styles.css +2 -133
- package/dist/types.d.ts +3 -0
- package/package.json +97 -92
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
+
<a href="https://paraframe.convex.works">Showcase</a> ·
|
|
4
5
|
<a href="docs/SETUP.md">Setup</a> ·
|
|
5
6
|
<a href="docs/COMMENTS.md">Comments</a> ·
|
|
6
7
|
<a href="docs/DEVELOPMENT.md">Development</a>
|
|
@@ -11,6 +12,8 @@ prototype preview. You write each frame as a Svelte component, and paraframe
|
|
|
11
12
|
lays them out on a canvas and plays them one at a time. Frames are real
|
|
12
13
|
components, so anything inside them stays interactive.
|
|
13
14
|
|
|
15
|
+
Try the showcase at <https://paraframe.convex.works>.
|
|
16
|
+
|
|
14
17
|
- **Canvas.** Every frame in sections on a board you can pan and zoom.
|
|
15
18
|
- **Player.** One frame at a time, scaled to the window; named flows give you starting points.
|
|
16
19
|
- **Real components in iframes.** Each frame renders at its own size, with its own styles, media queries and fixed elements, isolated from paraframe's chrome.
|
|
@@ -19,11 +22,20 @@ components, so anything inside them stays interactive.
|
|
|
19
22
|
- **Comments.** Optional pinned threads on elements inside frames, through [visual-comments](https://github.com/Convex-Works/visual-comments).
|
|
20
23
|
- **Theming.** `--pf-*` custom properties for the chrome.
|
|
21
24
|
|
|
22
|
-
Requires SvelteKit 3
|
|
25
|
+
Requires SvelteKit 3 and Svelte 5. The chrome ships as plain CSS, so your app
|
|
26
|
+
needs no Tailwind.
|
|
23
27
|
|
|
24
28
|
## Quickstart
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
In a SvelteKit app (a new one from `npx sv create --template minimal --types ts`
|
|
31
|
+
works), install paraframe and delete the template's root
|
|
32
|
+
`src/routes/+page.svelte`: the canvas takes its place at `/`.
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm i -D paraframe
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Define the prototype once:
|
|
27
39
|
|
|
28
40
|
```ts
|
|
29
41
|
// src/lib/prototype.ts
|
|
@@ -49,20 +61,105 @@ export const prototype = definePrototype({
|
|
|
49
61
|
});
|
|
50
62
|
```
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
Each frame is an ordinary Svelte component:
|
|
65
|
+
|
|
66
|
+
```svelte
|
|
67
|
+
<!-- src/lib/frames/Welcome.svelte -->
|
|
68
|
+
<main>
|
|
69
|
+
<h1>Welcome</h1>
|
|
70
|
+
<p>Your first frame.</p>
|
|
71
|
+
</main>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then add the routes. The canvas and player share a layout that loads
|
|
53
75
|
paraframe's styles; the frame route loads your product's styles instead.
|
|
54
76
|
|
|
55
77
|
```
|
|
56
78
|
src/routes/
|
|
57
|
-
+layout.ts
|
|
58
|
-
(viewer)/+layout.svelte
|
|
59
|
-
(viewer)/+page.svelte
|
|
60
|
-
(viewer)/play/[frame]/+page.
|
|
61
|
-
|
|
79
|
+
+layout.ts
|
|
80
|
+
(viewer)/+layout.svelte
|
|
81
|
+
(viewer)/+page.svelte
|
|
82
|
+
(viewer)/play/[frame]/+page.ts
|
|
83
|
+
(viewer)/play/[frame]/+page.svelte
|
|
84
|
+
frame/[frame]/+page.ts
|
|
85
|
+
frame/[frame]/+page.svelte
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// src/routes/+layout.ts
|
|
90
|
+
export const prerender = true;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```svelte
|
|
94
|
+
<!-- src/routes/(viewer)/+layout.svelte -->
|
|
95
|
+
<script lang="ts">
|
|
96
|
+
import 'paraframe/styles.css';
|
|
97
|
+
import type { LayoutProps } from './$types';
|
|
98
|
+
|
|
99
|
+
let { children }: LayoutProps = $props();
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
{@render children()}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```svelte
|
|
106
|
+
<!-- src/routes/(viewer)/+page.svelte -->
|
|
107
|
+
<script lang="ts">
|
|
108
|
+
import { Canvas } from 'paraframe';
|
|
109
|
+
import { prototype } from '#lib/prototype.js';
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<Canvas {prototype} />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// src/routes/(viewer)/play/[frame]/+page.ts
|
|
117
|
+
import { frameEntries } from 'paraframe';
|
|
118
|
+
import { prototype } from '#lib/prototype.js';
|
|
119
|
+
|
|
120
|
+
export const entries = () => frameEntries(prototype);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```svelte
|
|
124
|
+
<!-- src/routes/(viewer)/play/[frame]/+page.svelte -->
|
|
125
|
+
<script lang="ts">
|
|
126
|
+
import { page } from '$app/state';
|
|
127
|
+
import { Player } from 'paraframe';
|
|
128
|
+
import { prototype } from '#lib/prototype.js';
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<Player {prototype} frameId={page.params.frame ?? ''} />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// src/routes/frame/[frame]/+page.ts
|
|
136
|
+
import { frameEntries, resolveFrame } from 'paraframe';
|
|
137
|
+
import { prototype } from '#lib/prototype.js';
|
|
138
|
+
import type { PageLoad } from './$types';
|
|
139
|
+
|
|
140
|
+
export const entries = () => frameEntries(prototype);
|
|
141
|
+
|
|
142
|
+
export const load: PageLoad = async ({ params }) => ({
|
|
143
|
+
resolved: await resolveFrame(prototype, params.frame)
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```svelte
|
|
148
|
+
<!-- src/routes/frame/[frame]/+page.svelte -->
|
|
149
|
+
<script lang="ts">
|
|
150
|
+
// Import your product's own CSS here, never paraframe/styles.css.
|
|
151
|
+
import { FrameHost } from 'paraframe';
|
|
152
|
+
import { prototype } from '#lib/prototype.js';
|
|
153
|
+
import type { PageProps } from './$types';
|
|
154
|
+
|
|
155
|
+
let { data }: PageProps = $props();
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<FrameHost {prototype} resolved={data.resolved} />
|
|
62
159
|
```
|
|
63
160
|
|
|
64
|
-
|
|
65
|
-
|
|
161
|
+
[Setup](https://github.com/Convex-Works/paraframe/blob/main/docs/SETUP.md) covers
|
|
162
|
+
frame sizes and props, static previews and theming.
|
|
66
163
|
|
|
67
164
|
To see a complete working prototype, clone this repository and run
|
|
68
165
|
`pnpm install && pnpm showcase`.
|
|
@@ -95,7 +192,7 @@ clicks inside the frame (a selectable table row, say), add `data-pf-interactive`
|
|
|
95
192
|
| View | Keys |
|
|
96
193
|
| --- | --- |
|
|
97
194
|
| Player | ← → previous and next frame, R restart the flow (while one is playing), C comment mode, Esc close the open comment, then leave comment mode |
|
|
98
|
-
| Canvas | Scroll or drag to pan, ⌘/Ctrl + scroll to zoom, ⇧1 fit all, ⇧0 100%, F or ⇧2 center and fit the selection, ← → select previous and next frame, Enter play, C comment mode, Esc close the open comment, then leave comment mode |
|
|
195
|
+
| Canvas | Scroll or drag to pan, ⌘/Ctrl + scroll to zoom, ⇧1 fit all, ⇧0 100%, F or ⇧2 center and fit the selection, ← → select previous and next frame, Enter play (when no button or link has focus), C comment mode, Esc close the open comment, then leave comment mode |
|
|
99
196
|
|
|
100
197
|
On the canvas, hover live frames to inspect tooltips and hover states, and
|
|
101
198
|
double-click a frame to play it.
|
package/dist/Canvas.svelte
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import Menu from './internal/Menu.svelte';
|
|
16
16
|
import MenuItem from './internal/MenuItem.svelte';
|
|
17
17
|
import { readStored, storageKey, writeStored } from './internal/storage.js';
|
|
18
|
-
import { isEditable } from './messages.js';
|
|
18
|
+
import { activatesOnEnter, isEditable } from './messages.js';
|
|
19
19
|
import { describeFrame, useCommentsHolder, type CommentThreadPin } from './internal/comments.js';
|
|
20
20
|
import type { Flow, Frame, Prototype, Section } from './types.js';
|
|
21
21
|
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
const LOAD_IDLE_MS = 50;
|
|
40
40
|
// Below this on-screen width, frame titles are hidden.
|
|
41
41
|
const MIN_LABEL_WIDTH = 72;
|
|
42
|
+
// Below this on-screen width, a frame's path is hidden and its title keeps the room.
|
|
43
|
+
const PATH_LABEL_WIDTH = 360;
|
|
42
44
|
// The comments panel floats over the right edge; fitting keeps frames clear of it.
|
|
43
45
|
const PANEL_INSET = 320 + 12 * 2;
|
|
44
46
|
const TOOLBAR_HEIGHT = 56;
|
|
@@ -369,6 +371,8 @@
|
|
|
369
371
|
function onkeydown(event: KeyboardEvent) {
|
|
370
372
|
if (isEditable(event.target) || document.querySelector(':popover-open')) return;
|
|
371
373
|
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
374
|
+
// A focused button or link handles Enter itself; Enter elsewhere plays the selection.
|
|
375
|
+
if (event.code === 'Enter' && activatesOnEnter(event.target)) return;
|
|
372
376
|
const actions: Record<string, () => void> = {
|
|
373
377
|
'shift+Digit1': () => fit(layout.bounds),
|
|
374
378
|
'shift+Digit0': () => zoomTo(1),
|
|
@@ -486,6 +490,8 @@
|
|
|
486
490
|
{entry.label}
|
|
487
491
|
</MenuItem>
|
|
488
492
|
{/each}
|
|
493
|
+
{/snippet}
|
|
494
|
+
{#snippet footer()}
|
|
489
495
|
<p class="max-w-60 border-t border-pf-line px-2 pt-2 pb-1 text-xs leading-relaxed text-pf-muted">
|
|
490
496
|
Scroll or drag to pan. Hold ⌘ or Ctrl and scroll to zoom. Double-click a frame to play it.
|
|
491
497
|
</p>
|
|
@@ -633,6 +639,8 @@
|
|
|
633
639
|
ondblclick={() => play(frame.id)}
|
|
634
640
|
>
|
|
635
641
|
{frame.title}
|
|
642
|
+
<!-- The path only once the title has room beside it. -->
|
|
643
|
+
{#if frame.path && s.width >= PATH_LABEL_WIDTH}<span class="pl-1 text-pf-canvas-muted">{frame.path}</span>{/if}
|
|
636
644
|
</button>
|
|
637
645
|
{#if frame.id === selected || frame.id === hovered}
|
|
638
646
|
<button
|
package/dist/Player.svelte
CHANGED
|
@@ -181,6 +181,7 @@
|
|
|
181
181
|
<span class="hidden text-pf-island-muted sm:inline" aria-hidden="true">/</span>
|
|
182
182
|
{/if}
|
|
183
183
|
<span class="truncate font-medium">{frame?.title ?? frameId}</span>
|
|
184
|
+
{#if frame?.path}<span class="hidden truncate text-pf-island-muted md:inline">{frame.path}</span>{/if}
|
|
184
185
|
</p>
|
|
185
186
|
</div>
|
|
186
187
|
|
|
@@ -268,18 +269,20 @@
|
|
|
268
269
|
{/snippet}
|
|
269
270
|
{#snippet children(close)}
|
|
270
271
|
{#each prototype.sections as section (section.id)}
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
272
|
+
<div role="group" aria-label={section.title}>
|
|
273
|
+
<p class="px-2 pt-2 pb-1 text-xs text-pf-muted" aria-hidden="true">{section.title}</p>
|
|
274
|
+
{#each section.frames as item (item.id)}
|
|
275
|
+
<MenuItem
|
|
276
|
+
checked={item.id === frameId}
|
|
277
|
+
onselect={() => {
|
|
278
|
+
close();
|
|
279
|
+
show(item.id);
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
{item.title}
|
|
283
|
+
</MenuItem>
|
|
284
|
+
{/each}
|
|
285
|
+
</div>
|
|
283
286
|
{/each}
|
|
284
287
|
{/snippet}
|
|
285
288
|
</Menu>
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
<!--
|
|
1
|
+
<!--
|
|
2
|
+
A button that opens a popover menu below it. Items close the menu with `close()`.
|
|
3
|
+
Follows the ARIA menu button pattern: opening moves focus to the checked item
|
|
4
|
+
(or the first), Up/Down/Home/End move between items, and Escape, Tab or
|
|
5
|
+
choosing an item closes the menu and returns focus to the trigger.
|
|
6
|
+
-->
|
|
2
7
|
<script lang="ts">
|
|
3
8
|
import type { Snippet } from 'svelte';
|
|
4
9
|
|
|
@@ -6,14 +11,17 @@
|
|
|
6
11
|
label,
|
|
7
12
|
trigger,
|
|
8
13
|
children,
|
|
14
|
+
footer,
|
|
9
15
|
align = 'start',
|
|
10
16
|
tone = 'light',
|
|
11
17
|
class: className = ''
|
|
12
18
|
}: {
|
|
13
|
-
/** Accessible name for the trigger. */
|
|
19
|
+
/** Accessible name for the trigger and the menu. */
|
|
14
20
|
label: string;
|
|
15
21
|
trigger: Snippet;
|
|
16
22
|
children: Snippet<[close: () => void]>;
|
|
23
|
+
/** Non-interactive content shown below the items, outside the menu role. */
|
|
24
|
+
footer?: Snippet;
|
|
17
25
|
align?: 'start' | 'end';
|
|
18
26
|
tone?: 'light' | 'dark';
|
|
19
27
|
class?: string;
|
|
@@ -22,16 +30,67 @@
|
|
|
22
30
|
const id = $props.id();
|
|
23
31
|
let button = $state<HTMLButtonElement>();
|
|
24
32
|
let popover = $state<HTMLDivElement>();
|
|
33
|
+
let menu = $state<HTMLDivElement>();
|
|
25
34
|
let open = $state(false);
|
|
35
|
+
// Which item gets focus when the menu opens: the checked one (or first) unless a key asked otherwise.
|
|
36
|
+
let initial: 'first' | 'last' | undefined;
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
const items = () => [...(menu?.querySelectorAll<HTMLElement>('[role^=menuitem]') ?? [])];
|
|
39
|
+
|
|
40
|
+
function beforetoggle(event: ToggleEvent) {
|
|
41
|
+
// Closing with focus inside the menu (Escape, Tab, an item was chosen): hand focus back to the trigger.
|
|
42
|
+
if (event.newState === 'closed' && popover?.contains(document.activeElement)) button?.focus();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ontoggle(event: ToggleEvent) {
|
|
28
46
|
open = event.newState === 'open';
|
|
29
|
-
if (
|
|
47
|
+
if (!open || !button || !popover) return;
|
|
30
48
|
const rect = button.getBoundingClientRect();
|
|
31
49
|
const gutter = 12;
|
|
32
50
|
const left = align === 'start' ? rect.left : rect.right - popover.offsetWidth;
|
|
33
51
|
popover.style.left = `${Math.max(gutter, Math.min(left, window.innerWidth - popover.offsetWidth - gutter))}px`;
|
|
34
52
|
popover.style.top = `${Math.max(gutter, Math.min(rect.bottom + 6, window.innerHeight - popover.offsetHeight - gutter))}px`;
|
|
53
|
+
|
|
54
|
+
focusItem(initial);
|
|
55
|
+
initial = undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function focusItem(which?: 'first' | 'last') {
|
|
59
|
+
const all = items();
|
|
60
|
+
const checked = which ? undefined : all.find((item) => item.getAttribute('aria-checked') === 'true');
|
|
61
|
+
(checked ?? (which === 'last' ? all.at(-1) : all[0]))?.focus();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ontriggerkeydown(event: KeyboardEvent) {
|
|
65
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
const which = event.key === 'ArrowUp' ? 'last' : 'first';
|
|
68
|
+
if (open) return focusItem(which);
|
|
69
|
+
initial = which;
|
|
70
|
+
popover?.showPopover();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function onmenukeydown(event: KeyboardEvent) {
|
|
74
|
+
const all = items();
|
|
75
|
+
const index = all.indexOf(document.activeElement as HTMLElement);
|
|
76
|
+
let next: HTMLElement | undefined;
|
|
77
|
+
if (event.key === 'ArrowDown') next = all[(index + 1) % all.length];
|
|
78
|
+
else if (event.key === 'ArrowUp') next = all.at(index - 1);
|
|
79
|
+
else if (event.key === 'Home') next = all[0];
|
|
80
|
+
else if (event.key === 'End') next = all.at(-1);
|
|
81
|
+
else if (event.key === 'Escape') {
|
|
82
|
+
// Only the menu closes; the canvas and comments don't see this Escape.
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
event.stopPropagation();
|
|
85
|
+
close();
|
|
86
|
+
return;
|
|
87
|
+
} else if (event.key === 'Tab') {
|
|
88
|
+
// Close and let Tab move on from the trigger.
|
|
89
|
+
close();
|
|
90
|
+
return;
|
|
91
|
+
} else return;
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
next?.focus();
|
|
35
94
|
}
|
|
36
95
|
|
|
37
96
|
const close = () => popover?.hidePopover();
|
|
@@ -40,12 +99,15 @@
|
|
|
40
99
|
<button
|
|
41
100
|
bind:this={button}
|
|
42
101
|
type="button"
|
|
102
|
+
id="pf-menu-button-{id}"
|
|
43
103
|
popovertarget="pf-menu-{id}"
|
|
44
104
|
aria-label={label}
|
|
105
|
+
aria-haspopup="menu"
|
|
45
106
|
aria-expanded={open}
|
|
46
|
-
aria-controls="pf-menu-{id}"
|
|
107
|
+
aria-controls="pf-menu-items-{id}"
|
|
47
108
|
title={label}
|
|
48
109
|
class={className}
|
|
110
|
+
onkeydown={ontriggerkeydown}
|
|
49
111
|
>
|
|
50
112
|
{@render trigger()}
|
|
51
113
|
</button>
|
|
@@ -53,7 +115,8 @@
|
|
|
53
115
|
bind:this={popover}
|
|
54
116
|
id="pf-menu-{id}"
|
|
55
117
|
popover="auto"
|
|
56
|
-
|
|
118
|
+
onbeforetoggle={beforetoggle}
|
|
119
|
+
{ontoggle}
|
|
57
120
|
class={[
|
|
58
121
|
'pf-menu fixed inset-auto m-0 max-h-[min(70vh,560px)] w-max min-w-52 max-w-[calc(100vw-24px)] overflow-y-auto rounded border p-1 text-[13px] shadow-lg',
|
|
59
122
|
tone === 'dark'
|
|
@@ -61,5 +124,15 @@
|
|
|
61
124
|
: 'border-pf-line bg-pf-surface text-pf-ink'
|
|
62
125
|
]}
|
|
63
126
|
>
|
|
64
|
-
|
|
127
|
+
<div
|
|
128
|
+
bind:this={menu}
|
|
129
|
+
id="pf-menu-items-{id}"
|
|
130
|
+
role="menu"
|
|
131
|
+
tabindex="-1"
|
|
132
|
+
aria-labelledby="pf-menu-button-{id}"
|
|
133
|
+
onkeydown={onmenukeydown}
|
|
134
|
+
>
|
|
135
|
+
{@render children(close)}
|
|
136
|
+
</div>
|
|
137
|
+
{@render footer?.()}
|
|
65
138
|
</div>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
/** Accessible name for the trigger. */
|
|
3
|
+
/** Accessible name for the trigger and the menu. */
|
|
4
4
|
label: string;
|
|
5
5
|
trigger: Snippet;
|
|
6
6
|
children: Snippet<[close: () => void]>;
|
|
7
|
+
/** Non-interactive content shown below the items, outside the menu role. */
|
|
8
|
+
footer?: Snippet;
|
|
7
9
|
align?: 'start' | 'end';
|
|
8
10
|
tone?: 'light' | 'dark';
|
|
9
11
|
class?: string;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
tone = 'light'
|
|
11
11
|
}: {
|
|
12
12
|
onselect: () => void;
|
|
13
|
-
/**
|
|
13
|
+
/** Makes this a radio item (`menuitemradio` with `aria-checked`) with a check mark column when not undefined. */
|
|
14
14
|
checked?: boolean;
|
|
15
15
|
children: Snippet;
|
|
16
16
|
hint?: string;
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
<button
|
|
22
22
|
type="button"
|
|
23
|
-
role=
|
|
23
|
+
role={checked === undefined ? 'menuitem' : 'menuitemradio'}
|
|
24
|
+
aria-checked={checked}
|
|
25
|
+
tabindex="-1"
|
|
24
26
|
onclick={onselect}
|
|
25
27
|
class={[
|
|
26
28
|
'flex w-full items-center gap-2 rounded-[2px] px-2 py-1.5 text-left',
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
]}
|
|
30
32
|
>
|
|
31
33
|
{#if checked !== undefined}
|
|
32
|
-
<span class="w-4 shrink-0 text-pf-accent">
|
|
34
|
+
<span class="w-4 shrink-0 text-pf-accent" aria-hidden="true">
|
|
33
35
|
{#if checked}<Icon name="check" size={14} />{/if}
|
|
34
36
|
</span>
|
|
35
37
|
{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
onselect: () => void;
|
|
4
|
-
/**
|
|
4
|
+
/** Makes this a radio item (`menuitemradio` with `aria-checked`) with a check mark column when not undefined. */
|
|
5
5
|
checked?: boolean;
|
|
6
6
|
children: Snippet;
|
|
7
7
|
hint?: string;
|
package/dist/messages.d.ts
CHANGED
|
@@ -22,3 +22,5 @@ export declare function postToHost(message: FrameMessage): void;
|
|
|
22
22
|
*/
|
|
23
23
|
export declare const PLAYER_KEYS: Set<string>;
|
|
24
24
|
export declare function isEditable(target: EventTarget | null): boolean;
|
|
25
|
+
/** Whether Enter on this target belongs to a focused control rather than to a global shortcut. */
|
|
26
|
+
export declare function activatesOnEnter(target: EventTarget | null): boolean;
|
package/dist/messages.js
CHANGED
|
@@ -18,3 +18,10 @@ export function isEditable(target) {
|
|
|
18
18
|
return (target instanceof HTMLElement &&
|
|
19
19
|
(target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)));
|
|
20
20
|
}
|
|
21
|
+
// Controls that act on Enter themselves.
|
|
22
|
+
const ACTIVATES_ON_ENTER = 'a[href],button,summary,[role=button],[role=link],[role=tab],[role=checkbox],[role=switch],' +
|
|
23
|
+
'[role=radio],[role=option],[role=menuitem],[role=menuitemradio],[role=menuitemcheckbox]';
|
|
24
|
+
/** Whether Enter on this target belongs to a focused control rather than to a global shortcut. */
|
|
25
|
+
export function activatesOnEnter(target) {
|
|
26
|
+
return target instanceof Element && target.closest(ACTIVATES_ON_ENTER) !== null;
|
|
27
|
+
}
|
package/dist/prototype.js
CHANGED
package/dist/styles.css
CHANGED
|
@@ -1,133 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
paraframe's chrome. Import it once in the layout of the canvas and player
|
|
3
|
-
routes, not in the frame route: frames carry the host app's own styles.
|
|
4
|
-
|
|
5
|
-
Override the --pf-* custom properties to theme the chrome.
|
|
6
|
-
*/
|
|
7
|
-
@import 'tailwindcss' source(none);
|
|
8
|
-
@source './';
|
|
9
|
-
|
|
10
|
-
:root {
|
|
11
|
-
--pf-font: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
12
|
-
--pf-ink: #292524;
|
|
13
|
-
--pf-muted: #78716c;
|
|
14
|
-
--pf-line: #d6d3d1;
|
|
15
|
-
--pf-wash: #e7e5e4;
|
|
16
|
-
--pf-surface: #f5f5f4;
|
|
17
|
-
--pf-canvas: #1c1917;
|
|
18
|
-
--pf-canvas-ink: #e7e5e4;
|
|
19
|
-
--pf-canvas-muted: #a8a29e;
|
|
20
|
-
--pf-selection: #f5f5f4;
|
|
21
|
-
--pf-section: #292524;
|
|
22
|
-
--pf-grid: rgb(168 162 158 / 0.2);
|
|
23
|
-
--pf-stage: #1c1917;
|
|
24
|
-
--pf-accent: #57534e;
|
|
25
|
-
--pf-avatar: #009aaa;
|
|
26
|
-
--pf-comment: #0d99ff;
|
|
27
|
-
--pf-comment-soft: #e5f4ff;
|
|
28
|
-
/* Comments (visual-comments) take paraframe's colours and font. */
|
|
29
|
-
--vc-comment: var(--pf-comment);
|
|
30
|
-
--vc-comment-soft: var(--pf-comment-soft);
|
|
31
|
-
--vc-avatar: var(--pf-avatar);
|
|
32
|
-
--vc-font: var(--pf-font);
|
|
33
|
-
--pf-island: rgb(50 50 50 / 0.57);
|
|
34
|
-
--pf-island-ink: #fff;
|
|
35
|
-
--pf-island-muted: rgb(255 255 255 / 0.6);
|
|
36
|
-
--pf-island-hover: rgb(255 255 255 / 0.12);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@theme inline {
|
|
40
|
-
--font-pf: var(--pf-font);
|
|
41
|
-
--color-pf-ink: var(--pf-ink);
|
|
42
|
-
--color-pf-muted: var(--pf-muted);
|
|
43
|
-
--color-pf-line: var(--pf-line);
|
|
44
|
-
--color-pf-wash: var(--pf-wash);
|
|
45
|
-
--color-pf-surface: var(--pf-surface);
|
|
46
|
-
--color-pf-canvas: var(--pf-canvas);
|
|
47
|
-
--color-pf-canvas-ink: var(--pf-canvas-ink);
|
|
48
|
-
--color-pf-canvas-muted: var(--pf-canvas-muted);
|
|
49
|
-
--color-pf-selection: var(--pf-selection);
|
|
50
|
-
--color-pf-section: var(--pf-section);
|
|
51
|
-
--color-pf-stage: var(--pf-stage);
|
|
52
|
-
--color-pf-accent: var(--pf-accent);
|
|
53
|
-
--color-pf-avatar: var(--pf-avatar);
|
|
54
|
-
--color-pf-comment: var(--pf-comment);
|
|
55
|
-
--color-pf-comment-soft: var(--pf-comment-soft);
|
|
56
|
-
--color-pf-island: var(--pf-island);
|
|
57
|
-
--color-pf-island-ink: var(--pf-island-ink);
|
|
58
|
-
--color-pf-island-muted: var(--pf-island-muted);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@layer components {
|
|
62
|
-
.pf-toolbar {
|
|
63
|
-
display: flex;
|
|
64
|
-
flex-wrap: wrap;
|
|
65
|
-
align-items: flex-start;
|
|
66
|
-
justify-content: space-between;
|
|
67
|
-
gap: 0.5rem;
|
|
68
|
-
pointer-events: none;
|
|
69
|
-
}
|
|
70
|
-
/* Floating chrome: a frosted pill of pill-shaped tools. */
|
|
71
|
-
.pf-island {
|
|
72
|
-
display: flex;
|
|
73
|
-
min-width: 0;
|
|
74
|
-
max-width: 100%;
|
|
75
|
-
align-items: center;
|
|
76
|
-
gap: 0.125rem;
|
|
77
|
-
padding: 0.25rem;
|
|
78
|
-
border-radius: 9999px;
|
|
79
|
-
background: var(--pf-island);
|
|
80
|
-
backdrop-filter: blur(5px);
|
|
81
|
-
-webkit-backdrop-filter: blur(5px);
|
|
82
|
-
color: var(--pf-island-ink);
|
|
83
|
-
pointer-events: auto;
|
|
84
|
-
}
|
|
85
|
-
.pf-tool {
|
|
86
|
-
display: inline-flex;
|
|
87
|
-
height: 2rem;
|
|
88
|
-
min-width: 2rem;
|
|
89
|
-
align-items: center;
|
|
90
|
-
justify-content: center;
|
|
91
|
-
border-radius: 9999px;
|
|
92
|
-
color: var(--pf-island-ink);
|
|
93
|
-
outline: none;
|
|
94
|
-
}
|
|
95
|
-
.pf-tool:hover,
|
|
96
|
-
.pf-tool[aria-expanded='true'] {
|
|
97
|
-
background: var(--pf-island-hover);
|
|
98
|
-
}
|
|
99
|
-
.pf-tool:disabled {
|
|
100
|
-
opacity: 0.35;
|
|
101
|
-
pointer-events: none;
|
|
102
|
-
}
|
|
103
|
-
.pf-tool[aria-pressed='true'],
|
|
104
|
-
.pf-tool-primary {
|
|
105
|
-
background: var(--pf-island-ink);
|
|
106
|
-
color: var(--pf-ink);
|
|
107
|
-
}
|
|
108
|
-
.pf-tool-primary {
|
|
109
|
-
gap: 0.375rem;
|
|
110
|
-
padding-inline: 0.875rem 1rem;
|
|
111
|
-
font-weight: 500;
|
|
112
|
-
}
|
|
113
|
-
.pf-tool-primary:hover { background: rgb(255 255 255 / 0.85); }
|
|
114
|
-
.pf-tool:focus-visible {
|
|
115
|
-
box-shadow: 0 0 0 2px var(--pf-island-muted);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.pf-tool-light {
|
|
119
|
-
display: inline-flex;
|
|
120
|
-
height: 1.75rem;
|
|
121
|
-
min-width: 1.75rem;
|
|
122
|
-
align-items: center;
|
|
123
|
-
justify-content: center;
|
|
124
|
-
border-radius: 0.125rem;
|
|
125
|
-
outline: none;
|
|
126
|
-
}
|
|
127
|
-
.pf-tool-light:hover {
|
|
128
|
-
background: var(--pf-wash);
|
|
129
|
-
}
|
|
130
|
-
.pf-tool-light:focus-visible {
|
|
131
|
-
box-shadow: 0 0 0 2px var(--pf-accent);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--font-weight-semibold:600;--leading-relaxed:1.625;--radius-md:.375rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.pf-toolbar{pointer-events:none;flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:.5rem;display:flex}.pf-island{background:var(--pf-island);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);min-width:0;max-width:100%;color:var(--pf-island-ink);pointer-events:auto;border-radius:9999px;align-items:center;gap:.125rem;padding:.25rem;display:flex}.pf-tool{min-width:2rem;height:2rem;color:var(--pf-island-ink);border-radius:9999px;outline:none;justify-content:center;align-items:center;display:inline-flex}.pf-tool:hover,.pf-tool[aria-expanded=true]{background:var(--pf-island-hover)}.pf-tool:disabled{opacity:.35;pointer-events:none}.pf-tool[aria-pressed=true],.pf-tool-primary{background:var(--pf-island-ink);color:var(--pf-ink)}.pf-tool-primary{gap:.375rem;padding-inline:.875rem 1rem;font-weight:500}.pf-tool-primary:hover{background:#ffffffd9}.pf-tool:focus-visible{box-shadow:0 0 0 2px var(--pf-island-muted)}.pf-tool-light{border-radius:.125rem;outline:none;justify-content:center;align-items:center;min-width:1.75rem;height:1.75rem;display:inline-flex}.pf-tool-light:hover{background:var(--pf-wash)}.pf-tool-light:focus-visible{box-shadow:0 0 0 2px var(--pf-accent)}}@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:0}.inset-auto{inset:auto}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.top-0{top:0}.top-1{top:var(--spacing)}.top-3{top:calc(var(--spacing) * 3)}.top-\[60px\]{top:60px}.right-1{right:var(--spacing)}.right-3{right:calc(var(--spacing) * 3)}.bottom-3{bottom:calc(var(--spacing) * 3)}.left-0{left:0}.z-10{z-index:10}.z-20{z-index:20}.m-0{margin:0}.m-3{margin:calc(var(--spacing) * 3)}.m-auto{margin:auto}.my-3{margin-block:calc(var(--spacing) * 3)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mb-0{margin-bottom:0}.ml-auto{margin-left:auto}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-full{width:100%;height:100%}.h-5{height:calc(var(--spacing) * 5)}.h-dvh{height:100dvh}.max-h-\[min\(70vh\,560px\)\]{max-height:min(70vh,560px)}.min-h-0{min-height:0}.min-h-10{min-height:calc(var(--spacing) * 10)}.w-4{width:calc(var(--spacing) * 4)}.w-20{width:calc(var(--spacing) * 20)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.w-max{width:max-content}.max-w-48{max-width:calc(var(--spacing) * 48)}.max-w-60{max-width:calc(var(--spacing) * 60)}.max-w-\[calc\(100vw-24px\)\]{max-width:calc(100vw - 24px)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-16{min-width:calc(var(--spacing) * 16)}.min-w-52{min-width:calc(var(--spacing) * 52)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.origin-top-left{transform-origin:0 0}.cursor-grabbing{cursor:grabbing}.touch-none{touch-action:none}.resize{resize:both}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[16px\]{border-radius:16px}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-pf-line{border-color:var(--pf-line)}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.bg-pf-accent{background-color:var(--pf-accent)}.bg-pf-canvas{background-color:var(--pf-canvas)}.bg-pf-comment{background-color:var(--pf-comment)}.bg-pf-ink{background-color:var(--pf-ink)}.bg-pf-section{background-color:var(--pf-section)}.bg-pf-stage{background-color:var(--pf-stage)}.bg-pf-surface{background-color:var(--pf-surface)}.bg-white{background-color:var(--color-white)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.px-1{padding-inline:var(--spacing)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-5{padding-right:calc(var(--spacing) * 5)}.pb-1{padding-bottom:var(--spacing)}.pl-1{padding-left:var(--spacing)}.pl-1\.5{padding-left:calc(var(--spacing) * 1.5)}.pl-3\.5{padding-left:calc(var(--spacing) * 3.5)}.text-center{text-align:center}.text-left{text-align:left}.font-pf{font-family:var(--pf-font)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-pf-accent{color:var(--pf-accent)}.text-pf-canvas-ink{color:var(--pf-canvas-ink)}.text-pf-canvas-muted{color:var(--pf-canvas-muted)}.text-pf-ink{color:var(--pf-ink)}.text-pf-island-muted{color:var(--pf-island-muted)}.text-pf-muted{color:var(--pf-muted)}.text-pf-selection{color:var(--pf-selection)}.text-pf-surface{color:var(--pf-surface)}.text-white\/90{color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.text-white\/90{color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.lowercase{text-transform:lowercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.opacity-50{opacity:.5}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-pf-selection,.ring-pf-selection\/60{--tw-ring-color:var(--pf-selection)}@supports (color:color-mix(in lab, red, red)){.ring-pf-selection\/60{--tw-ring-color:color-mix(in oklab, var(--pf-selection) 60%, transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:bg-pf-selection\/10:hover{background-color:var(--pf-selection)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-pf-selection\/10:hover{background-color:color-mix(in oklab, var(--pf-selection) 10%, transparent)}}.hover\:bg-pf-wash:hover{background-color:var(--pf-wash)}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.hover\:text-pf-canvas-ink:hover{color:var(--pf-canvas-ink)}.hover\:brightness-110:hover{--tw-brightness:brightness(110%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}.focus-visible\:bg-pf-wash:focus-visible{background-color:var(--pf-wash)}.focus-visible\:bg-white\/10:focus-visible{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-white\/10:focus-visible{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.data-\[pf-hover\=true\]\:pointer-events-auto[data-pf-hover=true]{pointer-events:auto}@media (min-width:40rem){.sm\:inline{display:inline}}@media (min-width:48rem){.md\:inline{display:inline}}}:root{--pf-font:ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--pf-ink:#292524;--pf-muted:#78716c;--pf-line:#d6d3d1;--pf-wash:#e7e5e4;--pf-surface:#f5f5f4;--pf-canvas:#1c1917;--pf-canvas-ink:#e7e5e4;--pf-canvas-muted:#a8a29e;--pf-selection:#f5f5f4;--pf-section:#292524;--pf-grid:#a8a29e33;--pf-stage:#1c1917;--pf-accent:#57534e;--pf-avatar:#009aaa;--pf-comment:#0d99ff;--pf-comment-soft:#e5f4ff;--vc-comment:var(--pf-comment);--vc-comment-soft:var(--pf-comment-soft);--vc-avatar:var(--pf-avatar);--vc-font:var(--pf-font);--pf-island:#32323291;--pf-island-ink:#fff;--pf-island-muted:#fff9;--pf-island-hover:#ffffff1f}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface FrameDefinition {
|
|
|
9
9
|
/** URL-safe identifier: lowercase letters, digits and single dashes. */
|
|
10
10
|
id: string;
|
|
11
11
|
title: string;
|
|
12
|
+
/** Where the screen lives in the product, e.g. `/{account}/projects`. Shown beside the title. */
|
|
13
|
+
path?: string;
|
|
12
14
|
/** Imports the frame's component, e.g. `() => import('./Overview.svelte')`. */
|
|
13
15
|
load: () => Promise<{
|
|
14
16
|
default: FrameComponent;
|
|
@@ -57,6 +59,7 @@ export interface PrototypeDefinition {
|
|
|
57
59
|
export interface Frame {
|
|
58
60
|
id: string;
|
|
59
61
|
title: string;
|
|
62
|
+
path: string | undefined;
|
|
60
63
|
load: FrameDefinition['load'];
|
|
61
64
|
props: Record<string, unknown>;
|
|
62
65
|
size: FrameSize;
|
package/package.json
CHANGED
|
@@ -1,93 +1,98 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
2
|
+
"name": "paraframe",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Click through Svelte prototypes like a Figma file: real Svelte components as frames, on a zoomable canvas and in a flow player.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"svelte",
|
|
7
|
+
"sveltekit",
|
|
8
|
+
"svelte5",
|
|
9
|
+
"prototype",
|
|
10
|
+
"prototyping",
|
|
11
|
+
"prototype-viewer",
|
|
12
|
+
"canvas",
|
|
13
|
+
"infinite-canvas",
|
|
14
|
+
"design-tools",
|
|
15
|
+
"figma",
|
|
16
|
+
"frames"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://paraframe.convex.works",
|
|
19
|
+
"bugs": "https://github.com/Convex-Works/paraframe/issues",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Convex-Works/paraframe.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Convex Works",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"packageManager": "pnpm@12.4.1",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.17"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"dev": "svelte-kit sync && svelte-package && pnpm css && (svelte-package --watch --preserve-output & pnpm css --watch)",
|
|
33
|
+
"package": "svelte-kit sync && svelte-package && pnpm css && publint",
|
|
34
|
+
"css": "tailwindcss -i src/styles.css -o dist/styles.css --minify",
|
|
35
|
+
"showcase": "vite dev",
|
|
36
|
+
"build": "vite build && pnpm package",
|
|
37
|
+
"preview": "vite preview",
|
|
38
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
39
|
+
"prepack": "pnpm package",
|
|
40
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --tsgo",
|
|
41
|
+
"test": "vitest run"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"!dist/**/*.test.*",
|
|
46
|
+
"!dist/**/*.spec.*"
|
|
47
|
+
],
|
|
48
|
+
"sideEffects": [
|
|
49
|
+
"**/*.css"
|
|
50
|
+
],
|
|
51
|
+
"svelte": "./dist/index.js",
|
|
52
|
+
"types": "./dist/index.d.ts",
|
|
53
|
+
"exports": {
|
|
54
|
+
".": {
|
|
55
|
+
"types": "./dist/index.d.ts",
|
|
56
|
+
"svelte": "./dist/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./comments": {
|
|
59
|
+
"types": "./dist/comments.d.ts",
|
|
60
|
+
"svelte": "./dist/comments.js"
|
|
61
|
+
},
|
|
62
|
+
"./styles.css": "./dist/styles.css"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@sveltejs/kit": "^3.0.0",
|
|
66
|
+
"svelte": "^5.57.1",
|
|
67
|
+
"visual-comments": "^0.1.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"visual-comments": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@convex-works/sign-in": "link:../sign-in",
|
|
76
|
+
"@sveltejs/adapter-static": "^4.0.0",
|
|
77
|
+
"@sveltejs/kit": "^3.0.0",
|
|
78
|
+
"@sveltejs/package": "^3.0.0",
|
|
79
|
+
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
|
80
|
+
"@tailwindcss/cli": "^4.3.3",
|
|
81
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
82
|
+
"@types/node": "^26.6.5",
|
|
83
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
84
|
+
"happy-dom": "^21.0.3",
|
|
85
|
+
"publint": "^0.3.21",
|
|
86
|
+
"svelte": "^5.57.1",
|
|
87
|
+
"svelte-check": "^4.7.6",
|
|
88
|
+
"tailwindcss": "^4.3.0",
|
|
89
|
+
"typescript": "~6.0.3",
|
|
90
|
+
"visual-comments": "^0.1.0",
|
|
91
|
+
"vite": "^8.3.0",
|
|
92
|
+
"vitest": "^5.0.5"
|
|
93
|
+
},
|
|
94
|
+
"imports": {
|
|
95
|
+
"#lib": "./src/lib/index.js",
|
|
96
|
+
"#lib/*": "./src/lib/*"
|
|
97
|
+
}
|
|
98
|
+
}
|