@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.
Files changed (77) hide show
  1. package/dist/app/style/main.pcss +18 -44
  2. package/dist/bundled/App.vue_vue_type_script_setup_true_lang.js +9 -7
  3. package/dist/bundled/app.css +80 -17
  4. package/dist/bundled/components/app/AppActions.vue_vue_type_script_setup_true_lang.js +44 -27
  5. package/dist/bundled/components/app/ShortcutTooltip.js +5 -0
  6. package/dist/bundled/components/app/ShortcutTooltip.vue_vue_type_script_setup_true_lang.js +27 -0
  7. package/dist/bundled/components/base/BaseDropdown.js +6 -0
  8. package/dist/bundled/components/base/BaseDropdown.vue_vue_type_script_setup_true_lang.js +68 -0
  9. package/dist/bundled/components/base/BaseOverflowMenu.vue_vue_type_script_setup_true_lang.js +5 -5
  10. package/dist/bundled/components/base/BaseSelect.js +1 -0
  11. package/dist/bundled/components/base/BaseSelect.vue_vue_type_script_setup_true_lang.js +80 -51
  12. package/dist/bundled/components/base/BaseSplitPane.js +1 -1
  13. package/dist/bundled/components/base/BaseSplitPane.vue_vue_type_script_setup_true_lang.js +65 -4
  14. package/dist/bundled/components/panel/ControlsComponentPropItem.vue_vue_type_script_setup_true_lang.js +15 -13
  15. package/dist/bundled/components/panel/ControlsComponentProps.vue_vue_type_script_setup_true_lang.js +9 -6
  16. package/dist/bundled/components/panel/ControlsComponentState.vue_vue_type_script_setup_true_lang.js +9 -6
  17. package/dist/bundled/components/panel/StatePresets.vue_vue_type_script_setup_true_lang.js +69 -54
  18. package/dist/bundled/components/panel/StoryEvent.vue_vue_type_script_setup_true_lang.js +5 -5
  19. package/dist/bundled/components/panel/StorySidePanel.vue_vue_type_script_setup_true_lang.js +1 -0
  20. package/dist/bundled/components/panel/StorySourceCode.js +1 -1
  21. package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +21 -16
  22. package/dist/bundled/components/story/StoryResponsivePreview.js +1 -1
  23. package/dist/bundled/components/story/StoryResponsivePreview.vue_vue_type_script_setup_true_lang.js +46 -43
  24. package/dist/bundled/components/story/StoryVariantGridItem.js +1 -1
  25. package/dist/bundled/components/story/StoryVariantGridItem.vue_vue_type_script_setup_true_lang.js +21 -19
  26. package/dist/bundled/components/story/StoryVariantSingle.js +1 -1
  27. package/dist/bundled/components/story/StoryVariantSingle.vue_vue_type_script_setup_true_lang.js +2 -1
  28. package/dist/bundled/components/story/StoryView.vue_vue_type_script_setup_true_lang.js +1 -0
  29. package/dist/bundled/components/toolbar/DevOnlyToolbarOpenInEditor.vue_vue_type_script_setup_true_lang.js +15 -12
  30. package/dist/bundled/components/toolbar/ToolbarBackground.vue_vue_type_script_setup_true_lang.js +20 -16
  31. package/dist/bundled/components/toolbar/ToolbarNewTab.vue_vue_type_script_setup_true_lang.js +16 -13
  32. package/dist/bundled/components/toolbar/ToolbarResponsiveSize.vue_vue_type_script_setup_true_lang.js +35 -24
  33. package/dist/bundled/components/toolbar/ToolbarTextDirection.vue_vue_type_script_setup_true_lang.js +15 -12
  34. package/dist/bundled/index.js +0 -13
  35. package/dist/bundled/sandbox.js +2 -1
  36. package/dist/bundled/util/icons.generated.js +4 -2
  37. package/dist/bundled/util/scroll.js +26 -5
  38. package/dist/bundled/util/state.js +105 -5
  39. package/dist/style.css +731 -357
  40. package/package.json +19 -15
  41. package/src/__tests__/base-select.spec.ts +25 -0
  42. package/src/__tests__/reset.spec.ts +53 -0
  43. package/src/__tests__/state-clone.spec.ts +55 -1
  44. package/src/__tests__/state-types.spec.ts +175 -0
  45. package/src/app/App.vue +43 -34
  46. package/src/app/components/app/AppActions.vue +42 -30
  47. package/src/app/components/app/ShortcutTooltip.vue +29 -0
  48. package/src/app/components/base/BaseDropdown.vue +142 -0
  49. package/src/app/components/base/BaseOverflowMenu.vue +10 -5
  50. package/src/app/components/base/BaseSelect.vue +133 -60
  51. package/src/app/components/base/BaseSplitPane.vue +73 -2
  52. package/src/app/components/panel/ControlsComponentPropItem.vue +18 -17
  53. package/src/app/components/panel/ControlsComponentProps.vue +7 -5
  54. package/src/app/components/panel/ControlsComponentState.vue +7 -5
  55. package/src/app/components/panel/StatePresets.vue +82 -76
  56. package/src/app/components/panel/StoryEvent.vue +10 -9
  57. package/src/app/components/panel/StorySidePanel.vue +1 -0
  58. package/src/app/components/panel/StorySourceCode.vue +47 -41
  59. package/src/app/components/story/StoryResponsivePreview.vue +55 -27
  60. package/src/app/components/story/StoryVariantGridItem.vue +19 -18
  61. package/src/app/components/story/StoryVariantSingle.vue +1 -0
  62. package/src/app/components/story/StoryView.vue +1 -0
  63. package/src/app/components/toolbar/DevOnlyToolbarOpenInEditor.vue +14 -12
  64. package/src/app/components/toolbar/ToolbarBackground.vue +22 -23
  65. package/src/app/components/toolbar/ToolbarNewTab.vue +14 -12
  66. package/src/app/components/toolbar/ToolbarResponsiveSize.vue +31 -25
  67. package/src/app/components/toolbar/ToolbarTextDirection.vue +14 -10
  68. package/src/app/index.ts +0 -17
  69. package/src/app/sandbox.ts +8 -1
  70. package/src/app/style/main.pcss +18 -44
  71. package/src/app/util/icons.generated.ts +2 -2
  72. package/src/app/util/scroll.ts +47 -9
  73. package/src/app/util/state.ts +139 -6
  74. package/dist/app/style/floating.pcss +0 -40
  75. package/dist/bundled/util/tooltip.js +0 -14
  76. package/src/app/style/floating.pcss +0 -40
  77. package/src/app/util/tooltip.ts +0 -13
@@ -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
- export function toRawDeep(val: unknown, clean = false, seen = new WeakMap()): any {
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 => toRawDeep(value, clean, seen))
66
+ let list = unwrappedValue.map(value => walk(value, clean, seen))
25
67
  if (clean) {
26
- list = list.filter(value => typeof value !== 'function')
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
- function toRawObject(obj: Record<any, any>, target: Record<any, any>, clean = false, seen = new WeakMap()) {
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
- target[key] = toRawDeep(obj[key], clean, seen)
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
- }
@@ -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
- }