@poveste/app 0.15.0 → 0.16.1
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/app/style/main.pcss +18 -44
- package/dist/bundled/App.vue_vue_type_script_setup_true_lang.js +9 -7
- package/dist/bundled/app.css +80 -17
- package/dist/bundled/components/app/AppActions.vue_vue_type_script_setup_true_lang.js +44 -27
- package/dist/bundled/components/app/ShortcutTooltip.js +5 -0
- package/dist/bundled/components/app/ShortcutTooltip.vue_vue_type_script_setup_true_lang.js +27 -0
- package/dist/bundled/components/base/BaseDropdown.js +6 -0
- package/dist/bundled/components/base/BaseDropdown.vue_vue_type_script_setup_true_lang.js +68 -0
- package/dist/bundled/components/base/BaseOverflowMenu.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/bundled/components/base/BaseSelect.js +1 -0
- package/dist/bundled/components/base/BaseSelect.vue_vue_type_script_setup_true_lang.js +80 -51
- package/dist/bundled/components/base/BaseSplitPane.js +1 -1
- package/dist/bundled/components/base/BaseSplitPane.vue_vue_type_script_setup_true_lang.js +65 -4
- package/dist/bundled/components/panel/ControlsComponentPropItem.vue_vue_type_script_setup_true_lang.js +15 -13
- package/dist/bundled/components/panel/ControlsComponentProps.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/bundled/components/panel/ControlsComponentState.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/bundled/components/panel/StatePresets.vue_vue_type_script_setup_true_lang.js +69 -54
- package/dist/bundled/components/panel/StoryEvent.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/bundled/components/panel/StorySidePanel.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/bundled/components/panel/StorySourceCode.js +1 -1
- package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +21 -16
- package/dist/bundled/components/story/StoryResponsivePreview.js +1 -1
- package/dist/bundled/components/story/StoryResponsivePreview.vue_vue_type_script_setup_true_lang.js +46 -43
- package/dist/bundled/components/story/StoryVariantGridItem.js +1 -1
- package/dist/bundled/components/story/StoryVariantGridItem.vue_vue_type_script_setup_true_lang.js +21 -19
- package/dist/bundled/components/story/StoryVariantSingle.js +1 -1
- package/dist/bundled/components/story/StoryVariantSingle.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/story/StoryView.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/bundled/components/toolbar/DevOnlyToolbarOpenInEditor.vue_vue_type_script_setup_true_lang.js +15 -12
- package/dist/bundled/components/toolbar/ToolbarBackground.vue_vue_type_script_setup_true_lang.js +20 -16
- package/dist/bundled/components/toolbar/ToolbarNewTab.vue_vue_type_script_setup_true_lang.js +16 -13
- package/dist/bundled/components/toolbar/ToolbarResponsiveSize.vue_vue_type_script_setup_true_lang.js +35 -24
- package/dist/bundled/components/toolbar/ToolbarTextDirection.vue_vue_type_script_setup_true_lang.js +15 -12
- package/dist/bundled/index.js +0 -13
- package/dist/bundled/sandbox.js +2 -1
- package/dist/bundled/util/icons.generated.js +4 -2
- package/dist/bundled/util/scroll.js +26 -5
- package/dist/bundled/util/state.js +105 -5
- package/dist/style.css +731 -357
- package/package.json +19 -15
- package/src/__tests__/base-select.spec.ts +25 -0
- package/src/__tests__/reset.spec.ts +53 -0
- package/src/__tests__/state-clone.spec.ts +55 -1
- package/src/__tests__/state-types.spec.ts +175 -0
- package/src/app/App.vue +43 -34
- package/src/app/components/app/AppActions.vue +42 -30
- package/src/app/components/app/ShortcutTooltip.vue +29 -0
- package/src/app/components/base/BaseDropdown.vue +142 -0
- package/src/app/components/base/BaseOverflowMenu.vue +10 -5
- package/src/app/components/base/BaseSelect.vue +133 -60
- package/src/app/components/base/BaseSplitPane.vue +73 -2
- package/src/app/components/panel/ControlsComponentPropItem.vue +18 -17
- package/src/app/components/panel/ControlsComponentProps.vue +7 -5
- package/src/app/components/panel/ControlsComponentState.vue +7 -5
- package/src/app/components/panel/StatePresets.vue +82 -76
- package/src/app/components/panel/StoryEvent.vue +10 -9
- package/src/app/components/panel/StorySidePanel.vue +1 -0
- package/src/app/components/panel/StorySourceCode.vue +47 -41
- package/src/app/components/story/StoryResponsivePreview.vue +55 -27
- package/src/app/components/story/StoryVariantGridItem.vue +19 -18
- package/src/app/components/story/StoryVariantSingle.vue +1 -0
- package/src/app/components/story/StoryView.vue +1 -0
- package/src/app/components/toolbar/DevOnlyToolbarOpenInEditor.vue +14 -12
- package/src/app/components/toolbar/ToolbarBackground.vue +22 -23
- package/src/app/components/toolbar/ToolbarNewTab.vue +14 -12
- package/src/app/components/toolbar/ToolbarResponsiveSize.vue +31 -25
- package/src/app/components/toolbar/ToolbarTextDirection.vue +14 -10
- package/src/app/index.ts +0 -17
- package/src/app/sandbox.ts +8 -1
- package/src/app/style/main.pcss +18 -44
- package/src/app/util/icons.generated.ts +2 -2
- package/src/app/util/scroll.ts +47 -9
- package/src/app/util/state.ts +139 -6
- package/dist/app/style/floating.pcss +0 -40
- package/dist/bundled/util/tooltip.js +0 -14
- package/src/app/style/floating.pcss +0 -40
- package/src/app/util/tooltip.ts +0 -13
package/src/app/util/state.ts
CHANGED
|
@@ -1,9 +1,43 @@
|
|
|
1
|
-
import { clone, omit } from '@poveste/shared'
|
|
1
|
+
import { clone, isMarkedRaw, isPlainObject, omit } from '@poveste/shared'
|
|
2
2
|
import { isRef, unref } from 'vue'
|
|
3
3
|
|
|
4
4
|
const isObject = (val: unknown): val is object => val !== null && typeof val === 'object'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* A value the caller must leave out rather than carry, because it cannot make
|
|
8
|
+
* the trip this copy is being made for.
|
|
9
|
+
*/
|
|
10
|
+
const DROP = Symbol('poveste.drop')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A detached copy of `val`, with refs unwrapped.
|
|
14
|
+
*
|
|
15
|
+
* `clean` means the copy leaves the realm — it is posted to the other side of
|
|
16
|
+
* the sandbox bridge, or stored as a preset — so values that cannot survive
|
|
17
|
+
* that are dropped rather than carried: functions, and anything Vue has marked
|
|
18
|
+
* raw.
|
|
19
|
+
*
|
|
20
|
+
* Marked values are dropped rather than passed through, because `__v_skip` is
|
|
21
|
+
* non-enumerable and does not cross `structuredClone`. Handing one over would
|
|
22
|
+
* strip the mark and hand the far side a plain graph to deep-watch, which is
|
|
23
|
+
* how the first attempt at #957 turned a slow story into a hanging one. A key
|
|
24
|
+
* the sandbox never sends is a key the controls panel does not show, and for
|
|
25
|
+
* the case this aims at — a template ref onto a component's exposed object —
|
|
26
|
+
* that is a control nobody could have used anyway.
|
|
27
|
+
*
|
|
28
|
+
* Without `clean` the copy stays in this realm, so a marked value is carried by
|
|
29
|
+
* reference. That is what the two Vues' bridge wants: an identity both sides
|
|
30
|
+
* share, which `isEquivalent` then settles by `Object.is` instead of walking.
|
|
31
|
+
*/
|
|
32
|
+
export function toRawDeep(val: unknown, clean = false, seen: WeakMap<object, any> = new WeakMap()): any {
|
|
33
|
+
const value = walk(val, clean, seen)
|
|
34
|
+
|
|
35
|
+
// Only a nested key is ever dropped. A state object that is itself marked
|
|
36
|
+
// would otherwise leave here as the sentinel.
|
|
37
|
+
return value === DROP ? {} : value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function walk(val: unknown, clean: boolean, seen: WeakMap<object, any>): any {
|
|
7
41
|
const unwrappedValue = isRef(val) ? unref(val) : val
|
|
8
42
|
|
|
9
43
|
if (typeof unwrappedValue === 'symbol') {
|
|
@@ -14,16 +48,27 @@ export function toRawDeep(val: unknown, clean = false, seen = new WeakMap()): an
|
|
|
14
48
|
return unwrappedValue
|
|
15
49
|
}
|
|
16
50
|
|
|
51
|
+
if (isMarkedRaw(unwrappedValue)) {
|
|
52
|
+
return clean ? DROP : unwrappedValue
|
|
53
|
+
}
|
|
54
|
+
|
|
17
55
|
if (seen.has(unwrappedValue)) {
|
|
18
56
|
return seen.get(unwrappedValue)
|
|
19
57
|
}
|
|
20
58
|
|
|
59
|
+
if (!Array.isArray(unwrappedValue) && !isPlainObject(unwrappedValue)) {
|
|
60
|
+
return notPlain(unwrappedValue, clean, seen)
|
|
61
|
+
}
|
|
62
|
+
|
|
21
63
|
if (Array.isArray(unwrappedValue)) {
|
|
22
64
|
const result: unknown[] = []
|
|
23
65
|
seen.set(unwrappedValue, result)
|
|
24
|
-
let list = unwrappedValue.map(value =>
|
|
66
|
+
let list = unwrappedValue.map(value => walk(value, clean, seen))
|
|
25
67
|
if (clean) {
|
|
26
|
-
|
|
68
|
+
// Dropped the same way a function is, indices and all: an array that has
|
|
69
|
+
// one of these in it comes out shorter, which is what has always happened
|
|
70
|
+
// to an array of functions.
|
|
71
|
+
list = list.filter(value => typeof value !== 'function' && value !== DROP)
|
|
27
72
|
}
|
|
28
73
|
result.push(...list)
|
|
29
74
|
return result
|
|
@@ -36,12 +81,100 @@ export function toRawDeep(val: unknown, clean = false, seen = new WeakMap()): an
|
|
|
36
81
|
}
|
|
37
82
|
}
|
|
38
83
|
|
|
39
|
-
|
|
84
|
+
/**
|
|
85
|
+
* A value with a prototype of its own — a `Date`, a `Map`, a class instance.
|
|
86
|
+
*
|
|
87
|
+
* These used to fall past every branch into `toRawObject`, whose `Object.keys`
|
|
88
|
+
* is empty for most of them, so they arrived as `{}` with no error and no
|
|
89
|
+
* warning (#977). `postMessage` is structured clone, and structured clone
|
|
90
|
+
* carries `Date`, `Map`, `Set`, `RegExp`, `Error`, typed arrays and `BigInt`
|
|
91
|
+
* intact — so they were being destroyed here, before the transport that would
|
|
92
|
+
* have carried them was ever reached.
|
|
93
|
+
*
|
|
94
|
+
* Structured clone is therefore the line, rather than a list authored here:
|
|
95
|
+
* it is the boundary that actually exists, it needs no maintaining as the
|
|
96
|
+
* platform adds types, and adopting it is what makes this walk agree with
|
|
97
|
+
* `isEquivalent`, which refuses to compare anything that is not plain.
|
|
98
|
+
*
|
|
99
|
+
* Without `clean` the copy stays in this realm, so the value is carried by
|
|
100
|
+
* reference — the same answer `copyState` in `@poveste/shared` gives, and the
|
|
101
|
+
* identity `isEquivalent` then settles by `Object.is`.
|
|
102
|
+
*/
|
|
103
|
+
function notPlain(value: object, clean: boolean, seen: WeakMap<object, any>): any {
|
|
104
|
+
if (!clean) {
|
|
105
|
+
return value
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (value instanceof Map || value instanceof Set) {
|
|
109
|
+
return walkCollection(value, seen)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const copy = structuredClone(value)
|
|
114
|
+
seen.set(value, copy)
|
|
115
|
+
return copy
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// `URL`, a DOM node, a `Promise`: structured clone refuses them, so passing
|
|
119
|
+
// one through would turn a silent flattening into a thrown `DataCloneError`
|
|
120
|
+
// mid-sync. Dropped the way a function is, and the key does not arrive.
|
|
121
|
+
return DROP
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* `Map` and `Set`, walked rather than handed to `structuredClone`, for two
|
|
127
|
+
* measured reasons.
|
|
128
|
+
*
|
|
129
|
+
* They hold user values, so this walk's own rules have to reach inside them: a
|
|
130
|
+
* function or a marked value in a `Map` is the same problem it is in an array,
|
|
131
|
+
* and an array already comes out shorter for it. Cloning the whole container
|
|
132
|
+
* would carry a function straight into a `DataCloneError`, and would strip a
|
|
133
|
+
* marked value's `__v_skip` and hand the far side a plain graph to deep-watch
|
|
134
|
+
* — which is the hazard #974 exists to prevent.
|
|
135
|
+
*
|
|
136
|
+
* And these are the only types here that Vue proxies. `structuredClone` throws
|
|
137
|
+
* `DataCloneError` on a reactive `Map` while the raw one clones fine, so a
|
|
138
|
+
* `Map` in reactive state would be refused and dropped — a second silence
|
|
139
|
+
* rather than a fix for the first. `Date`, `RegExp`, `Error` and typed arrays
|
|
140
|
+
* are not proxied, so the path below never meets a proxy and needs no `toRaw`.
|
|
141
|
+
*/
|
|
142
|
+
function walkCollection(value: Map<any, any> | Set<any>, seen: WeakMap<object, any>): any {
|
|
143
|
+
const keep = (walked: any) => walked !== DROP && typeof walked !== 'function'
|
|
144
|
+
|
|
145
|
+
if (value instanceof Set) {
|
|
146
|
+
const copy = new Set()
|
|
147
|
+
seen.set(value, copy)
|
|
148
|
+
for (const entry of value) {
|
|
149
|
+
const walked = walk(entry, true, seen)
|
|
150
|
+
if (keep(walked)) copy.add(walked)
|
|
151
|
+
}
|
|
152
|
+
return copy
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const copy = new Map()
|
|
156
|
+
seen.set(value, copy)
|
|
157
|
+
for (const [key, entry] of value) {
|
|
158
|
+
const walkedKey = walk(key, true, seen)
|
|
159
|
+
const walked = walk(entry, true, seen)
|
|
160
|
+
if (keep(walkedKey) && keep(walked)) copy.set(walkedKey, walked)
|
|
161
|
+
}
|
|
162
|
+
return copy
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function toRawObject(obj: Record<any, any>, target: Record<any, any>, clean: boolean, seen: WeakMap<object, any>) {
|
|
40
166
|
Object.keys(obj).forEach((key) => {
|
|
41
167
|
if (clean && typeof obj[key] === 'function') {
|
|
42
168
|
return
|
|
43
169
|
}
|
|
44
|
-
|
|
170
|
+
|
|
171
|
+
const value = walk(obj[key], clean, seen)
|
|
172
|
+
|
|
173
|
+
if (value === DROP) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
target[key] = value
|
|
45
178
|
})
|
|
46
179
|
}
|
|
47
180
|
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/* @TODO custom themes */
|
|
2
|
-
|
|
3
|
-
.v-popper {
|
|
4
|
-
line-height: 0;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.v-popper--theme-dropdown {
|
|
8
|
-
/*
|
|
9
|
-
* Written with the `dark:` variant rather than a `.ptw-dark &` descendant
|
|
10
|
-
* rule. The chrome CSS is wrapped in `@scope`, and `.ptw-dark` sits on
|
|
11
|
-
* `<html>` — above the scope root — so a descendant rule keyed on it never
|
|
12
|
-
* matches and the dropdown silently falls back to floating-vue's stock light
|
|
13
|
-
* theme. `dark:` compiles to `&:where(.ptw-dark, .ptw-dark *)`, which puts the
|
|
14
|
-
* condition on the subject and survives the wrapper. See #101.
|
|
15
|
-
*/
|
|
16
|
-
.v-popper__inner {
|
|
17
|
-
@apply dark:bg-gray-700 dark:border-gray-850 dark:text-gray-100;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.v-popper__arrow-inner {
|
|
21
|
-
@apply dark:border-gray-700;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.v-popper__arrow-outer {
|
|
25
|
-
@apply dark:border-gray-850;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
&.v-popper__popper--show-from .v-popper__wrapper {
|
|
29
|
-
transform: scale(.75);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
&.v-popper__popper--show-to .v-popper__wrapper {
|
|
33
|
-
transform: none;
|
|
34
|
-
transition: transform .15s cubic-bezier(0, 1, .5, 1);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.v-popper__popper:focus-visible {
|
|
39
|
-
outline: none;
|
|
40
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { isMac } from "./env.js";
|
|
2
|
-
import { formatKey } from "./keyboard.js";
|
|
3
|
-
//#region src/app/util/tooltip.ts
|
|
4
|
-
function makeTooltip(descriptionHtml, keyboardShortcut) {
|
|
5
|
-
return {
|
|
6
|
-
content: `<div>${descriptionHtml}</div><div class="flex items-center gap-1 mt-2 text-sm">${genKeyboardShortcutHtml(keyboardShortcut({ isMac }))}</div>`,
|
|
7
|
-
html: true
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
function genKeyboardShortcutHtml(shortcut) {
|
|
11
|
-
return `<span class="border border-gray-600 px-1 rounded-sm text-gray-400">${shortcut.split("+").map((k) => formatKey(k.trim())).join(" ")}</span>`;
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
|
-
export { makeTooltip };
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/* @TODO custom themes */
|
|
2
|
-
|
|
3
|
-
.v-popper {
|
|
4
|
-
line-height: 0;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.v-popper--theme-dropdown {
|
|
8
|
-
/*
|
|
9
|
-
* Written with the `dark:` variant rather than a `.ptw-dark &` descendant
|
|
10
|
-
* rule. The chrome CSS is wrapped in `@scope`, and `.ptw-dark` sits on
|
|
11
|
-
* `<html>` — above the scope root — so a descendant rule keyed on it never
|
|
12
|
-
* matches and the dropdown silently falls back to floating-vue's stock light
|
|
13
|
-
* theme. `dark:` compiles to `&:where(.ptw-dark, .ptw-dark *)`, which puts the
|
|
14
|
-
* condition on the subject and survives the wrapper. See #101.
|
|
15
|
-
*/
|
|
16
|
-
.v-popper__inner {
|
|
17
|
-
@apply dark:bg-gray-700 dark:border-gray-850 dark:text-gray-100;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.v-popper__arrow-inner {
|
|
21
|
-
@apply dark:border-gray-700;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.v-popper__arrow-outer {
|
|
25
|
-
@apply dark:border-gray-850;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
&.v-popper__popper--show-from .v-popper__wrapper {
|
|
29
|
-
transform: scale(.75);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
&.v-popper__popper--show-to .v-popper__wrapper {
|
|
33
|
-
transform: none;
|
|
34
|
-
transition: transform .15s cubic-bezier(0, 1, .5, 1);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.v-popper__popper:focus-visible {
|
|
39
|
-
outline: none;
|
|
40
|
-
}
|
package/src/app/util/tooltip.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { isMac } from './env.js'
|
|
2
|
-
import { formatKey } from './keyboard.js'
|
|
3
|
-
|
|
4
|
-
export function makeTooltip(descriptionHtml: string, keyboardShortcut: (options: { isMac: boolean }) => string) {
|
|
5
|
-
return {
|
|
6
|
-
content: `<div>${descriptionHtml}</div><div class="flex items-center gap-1 mt-2 text-sm">${genKeyboardShortcutHtml(keyboardShortcut({ isMac }))}</div>`,
|
|
7
|
-
html: true,
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function genKeyboardShortcutHtml(shortcut: string) {
|
|
12
|
-
return `<span class="border border-gray-600 px-1 rounded-sm text-gray-400">${shortcut.split('+').map(k => formatKey(k.trim())).join(' ')}</span>`
|
|
13
|
-
}
|