@poveste/app 0.14.0 → 0.16.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.
Files changed (126) 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 +91 -28
  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/app/TopBarChipItem.js +1 -1
  8. package/dist/bundled/components/base/BaseDropdown.js +6 -0
  9. package/dist/bundled/components/base/BaseDropdown.vue_vue_type_script_setup_true_lang.js +68 -0
  10. package/dist/bundled/components/base/BaseEmpty.js +2 -2
  11. package/dist/bundled/components/base/BaseOverflowMenu.vue_vue_type_script_setup_true_lang.js +19 -19
  12. package/dist/bundled/components/base/BaseSelect.js +1 -0
  13. package/dist/bundled/components/base/BaseSelect.vue_vue_type_script_setup_true_lang.js +194 -33
  14. package/dist/bundled/components/base/BaseSplitPane.js +1 -1
  15. package/dist/bundled/components/base/BaseSplitPane.vue_vue_type_script_setup_true_lang.js +65 -4
  16. package/dist/bundled/components/command/PromptSelect.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/bundled/components/panel/ControlsComponentPropItem.vue_vue_type_script_setup_true_lang.js +24 -16
  18. package/dist/bundled/components/panel/ControlsComponentProps.vue_vue_type_script_setup_true_lang.js +9 -6
  19. package/dist/bundled/components/panel/ControlsComponentState.vue_vue_type_script_setup_true_lang.js +9 -6
  20. package/dist/bundled/components/panel/PaneTabs.vue_vue_type_script_setup_true_lang.js +6 -6
  21. package/dist/bundled/components/panel/StatePresets.vue_vue_type_script_setup_true_lang.js +96 -52
  22. package/dist/bundled/components/panel/StoryEvent.vue_vue_type_script_setup_true_lang.js +5 -5
  23. package/dist/bundled/components/panel/StorySidePanel.vue_vue_type_script_setup_true_lang.js +2 -1
  24. package/dist/bundled/components/panel/StorySourceCode.js +1 -1
  25. package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +24 -18
  26. package/dist/bundled/components/search/SearchItemContent.vue_vue_type_script_setup_true_lang.js +2 -1
  27. package/dist/bundled/components/search/SearchPane.vue_vue_type_script_setup_true_lang.js +1 -2
  28. package/dist/bundled/components/story/StoryResponsivePreview.js +1 -1
  29. package/dist/bundled/components/story/StoryResponsivePreview.vue_vue_type_script_setup_true_lang.js +52 -45
  30. package/dist/bundled/components/story/StoryVariantGridItem.js +1 -1
  31. package/dist/bundled/components/story/StoryVariantGridItem.vue_vue_type_script_setup_true_lang.js +22 -20
  32. package/dist/bundled/components/story/StoryVariantSingle.js +1 -1
  33. package/dist/bundled/components/story/StoryVariantSingle.vue_vue_type_script_setup_true_lang.js +2 -1
  34. package/dist/bundled/components/story/StoryVariantSinglePreviewRemote.vue_vue_type_script_setup_true_lang.js +9 -6
  35. package/dist/bundled/components/story/StoryView.vue_vue_type_script_setup_true_lang.js +5 -1
  36. package/dist/bundled/components/toolbar/DevOnlyToolbarOpenInEditor.vue_vue_type_script_setup_true_lang.js +15 -12
  37. package/dist/bundled/components/toolbar/ToolbarBackground.vue_vue_type_script_setup_true_lang.js +20 -16
  38. package/dist/bundled/components/toolbar/ToolbarNewTab.vue_vue_type_script_setup_true_lang.js +16 -13
  39. package/dist/bundled/components/toolbar/ToolbarResponsiveSize.vue_vue_type_script_setup_true_lang.js +35 -24
  40. package/dist/bundled/components/toolbar/ToolbarTextDirection.vue_vue_type_script_setup_true_lang.js +15 -12
  41. package/dist/bundled/components/toolbar/ToolbarTitle.js +1 -1
  42. package/dist/bundled/components/toolbar/ToolbarTitle.vue_vue_type_script_setup_true_lang.js +1 -1
  43. package/dist/bundled/components/tree/StoryGroup.vue_vue_type_script_setup_true_lang.js +2 -2
  44. package/dist/bundled/components/tree/StoryList.vue_vue_type_script_setup_true_lang.js +3 -3
  45. package/dist/bundled/components/tree/StoryListFolder.js +1 -1
  46. package/dist/bundled/components/tree/StoryListFolder.vue_vue_type_script_setup_true_lang.js +3 -3
  47. package/dist/bundled/components/tree/StoryListItem.js +1 -1
  48. package/dist/bundled/components/tree/StoryListItem.vue_vue_type_script_setup_true_lang.js +3 -3
  49. package/dist/bundled/index.js +0 -13
  50. package/dist/bundled/sandbox.js +5 -3
  51. package/dist/bundled/stores/story.js +2 -2
  52. package/dist/bundled/util/commands.js +1 -1
  53. package/dist/bundled/util/dark.js +1 -1
  54. package/dist/bundled/util/events.js +1 -1
  55. package/dist/bundled/util/grid-slots.js +1 -1
  56. package/dist/bundled/util/icons.generated.js +4 -2
  57. package/dist/bundled/util/keyboard.js +8 -14
  58. package/dist/bundled/util/mapping.js +2 -2
  59. package/dist/bundled/util/render-tracker.js +33 -0
  60. package/dist/bundled/util/scroll.js +26 -5
  61. package/dist/bundled/util/state.js +105 -5
  62. package/dist/bundled/util/variant.js +12 -2
  63. package/dist/style.css +753 -357
  64. package/package.json +28 -28
  65. package/src/__tests__/base-select.spec.ts +25 -0
  66. package/src/__tests__/render-tracker.spec.ts +38 -0
  67. package/src/__tests__/reset.spec.ts +53 -0
  68. package/src/__tests__/state-clone.spec.ts +55 -1
  69. package/src/__tests__/state-types.spec.ts +175 -0
  70. package/src/app/App.vue +44 -35
  71. package/src/app/components/app/AppActions.vue +42 -30
  72. package/src/app/components/app/ShortcutTooltip.vue +29 -0
  73. package/src/app/components/app/TopBarChipItem.vue +1 -1
  74. package/src/app/components/base/BaseDropdown.vue +142 -0
  75. package/src/app/components/base/BaseEmpty.vue +1 -1
  76. package/src/app/components/base/BaseListItemLink.vue +4 -2
  77. package/src/app/components/base/BaseOverflowMenu.vue +27 -21
  78. package/src/app/components/base/BaseSelect.vue +270 -41
  79. package/src/app/components/base/BaseSplitPane.vue +75 -4
  80. package/src/app/components/command/PromptSelect.vue +1 -1
  81. package/src/app/components/panel/ControlsComponentPropItem.vue +29 -17
  82. package/src/app/components/panel/ControlsComponentProps.vue +7 -5
  83. package/src/app/components/panel/ControlsComponentState.vue +7 -5
  84. package/src/app/components/panel/PaneTabs.vue +6 -6
  85. package/src/app/components/panel/StatePresets.vue +116 -74
  86. package/src/app/components/panel/StoryEvent.vue +10 -9
  87. package/src/app/components/panel/StorySidePanel.vue +2 -1
  88. package/src/app/components/panel/StorySourceCode.vue +51 -44
  89. package/src/app/components/search/SearchItemContent.vue +2 -1
  90. package/src/app/components/search/SearchPane.vue +2 -3
  91. package/src/app/components/story/StoryResponsivePreview.vue +61 -29
  92. package/src/app/components/story/StoryVariantGridItem.vue +20 -19
  93. package/src/app/components/story/StoryVariantSingle.vue +1 -0
  94. package/src/app/components/story/StoryVariantSinglePreviewRemote.vue +14 -8
  95. package/src/app/components/story/StoryView.vue +4 -1
  96. package/src/app/components/toolbar/DevOnlyToolbarOpenInEditor.vue +14 -12
  97. package/src/app/components/toolbar/ToolbarBackground.vue +22 -23
  98. package/src/app/components/toolbar/ToolbarNewTab.vue +14 -12
  99. package/src/app/components/toolbar/ToolbarResponsiveSize.vue +31 -25
  100. package/src/app/components/toolbar/ToolbarTextDirection.vue +14 -10
  101. package/src/app/components/toolbar/ToolbarTitle.vue +2 -2
  102. package/src/app/components/tree/StoryGroup.vue +2 -2
  103. package/src/app/components/tree/StoryList.vue +2 -2
  104. package/src/app/components/tree/StoryListFolder.vue +2 -2
  105. package/src/app/components/tree/StoryListItem.vue +1 -1
  106. package/src/app/index.ts +0 -17
  107. package/src/app/sandbox.ts +16 -3
  108. package/src/app/stores/story.ts +2 -2
  109. package/src/app/style/main.pcss +18 -44
  110. package/src/app/types.ts +4 -4
  111. package/src/app/util/commands.ts +1 -1
  112. package/src/app/util/dark.ts +1 -1
  113. package/src/app/util/env.ts +0 -1
  114. package/src/app/util/events.ts +6 -5
  115. package/src/app/util/grid-slots.ts +1 -1
  116. package/src/app/util/icons.generated.ts +2 -2
  117. package/src/app/util/keyboard.ts +7 -11
  118. package/src/app/util/mapping.ts +2 -2
  119. package/src/app/util/render-tracker.ts +31 -0
  120. package/src/app/util/scroll.ts +47 -9
  121. package/src/app/util/state.ts +141 -8
  122. package/src/app/util/variant.ts +12 -2
  123. package/dist/app/style/floating.pcss +0 -40
  124. package/dist/bundled/util/tooltip.js +0 -14
  125. package/src/app/style/floating.pcss +0 -40
  126. package/src/app/util/tooltip.ts +0 -13
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Whether the render now under way inside a realm reported a throw.
3
+ *
4
+ * The host cannot see inside the realm, so "did this render succeed" has to be
5
+ * read off the two named messages it does see. A throw reaches
6
+ * `app.config.errorHandler` during mount and `SANDBOX_READY` is emitted after
7
+ * `await host.mount()` returns, so the order is always error-then-ready — which
8
+ * makes a ready with nothing recorded since the render began a clean render.
9
+ *
10
+ * Clearing on the story and variant ids changing instead cleared the pair being
11
+ * navigated *to*, so arriving at a broken story wiped its marker, and the fixed
12
+ * story the comment was about was never reached at all: a file edited in place
13
+ * keeps its ids, so that watcher did not fire on the one path it named (#597).
14
+ */
15
+ export function createRenderTracker() {
16
+ let threw = false
17
+
18
+ return {
19
+ /** A new render is starting: what the previous one reported no longer holds. */
20
+ begin(): void {
21
+ threw = false
22
+ },
23
+ threw(): void {
24
+ threw = true
25
+ },
26
+ /** True when the render that has just reported ready said nothing before it. */
27
+ wasClean(): boolean {
28
+ return !threw
29
+ },
30
+ }
31
+ }
@@ -1,7 +1,33 @@
1
1
  import type { Ref } from 'vue'
2
- import scrollIntoView from 'scroll-into-view-if-needed'
3
2
  import { onMounted, watch } from 'vue'
4
3
 
4
+ /**
5
+ * The closest ancestor that can actually scroll, or `null` for the viewport.
6
+ *
7
+ * `overlay` is in the test because it is what `overflow: overlay` computes to
8
+ * where it is still supported, and an element scrolling under it would
9
+ * otherwise be measured against the wrong box.
10
+ */
11
+ function scrollParent(el: HTMLElement): HTMLElement | null {
12
+ for (let node = el.parentElement; node; node = node.parentElement) {
13
+ const style = getComputedStyle(node)
14
+ if (/auto|scroll|overlay/.test(style.overflowY + style.overflowX)) {
15
+ return node
16
+ }
17
+ }
18
+ return null
19
+ }
20
+
21
+ /** Whether the element is already wholly inside the box that would scroll. */
22
+ function isVisibleIn(el: HTMLElement, scroller: HTMLElement | null): boolean {
23
+ const rect = el.getBoundingClientRect()
24
+ const bounds = scroller?.getBoundingClientRect()
25
+ const top = bounds?.top ?? 0
26
+ const bottom = bounds?.bottom ?? window.innerHeight
27
+
28
+ return rect.top >= top && rect.bottom <= bottom
29
+ }
30
+
5
31
  // `el` is a template ref, so it is undefined until the component mounts and
6
32
  // again once it unmounts. `autoScroll` has always guarded for that; the
7
33
  // signature was the only thing claiming otherwise, and it made four callers
@@ -13,16 +39,28 @@ export function useScrollOnActive(active: Ref<boolean>, el: Ref<HTMLElement | un
13
39
  }
14
40
  }, { flush: 'post' })
15
41
 
42
+ /*
43
+ * What `scroll-into-view-if-needed` was here for: centre the element, but
44
+ * only when it is not already on screen. The platform has no single option
45
+ * for that pair — `block: 'center'` always scrolls, and `block: 'nearest'`
46
+ * never centres — so the "if needed" half is the check above and the
47
+ * "centre" half is the native call.
48
+ *
49
+ * Two `getBoundingClientRect` reads, on an activation event rather than per
50
+ * frame, so this is not the per-event shape CONVENTIONS.md rule 6 is about.
51
+ *
52
+ * The library took `boundary: document.body`, which native has no equivalent
53
+ * for: it will scroll every scrollable ancestor up to the document. The
54
+ * chrome's layout is full-height and the document does not scroll, so there
55
+ * is nothing above the pane for it to move.
56
+ */
16
57
  function autoScroll() {
17
- if (el.value) {
18
- scrollIntoView(el.value, {
19
- scrollMode: 'if-needed',
20
- block: 'center',
21
- inline: 'nearest',
22
- behavior: 'smooth',
23
- boundary: document.body,
24
- })
58
+ const target = el.value
59
+ if (!target || isVisibleIn(target, scrollParent(target))) {
60
+ return
25
61
  }
62
+
63
+ target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' })
26
64
  }
27
65
 
28
66
  onMounted(() => {
@@ -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
- const isObject = val => val !== null && typeof val === 'object'
4
+ const isObject = (val: unknown): val is object => val !== null && typeof val === 'object'
5
5
 
6
- export function toRawDeep(val, clean = false, seen = new WeakMap()) {
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, clean = false, seen = new WeakMap()) {
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
@@ -31,17 +76,105 @@ export function toRawDeep(val, clean = false, seen = new WeakMap()) {
31
76
  else {
32
77
  const result = {}
33
78
  seen.set(unwrappedValue, result)
34
- toRawObject(unwrappedValue, result, clean, seen)
79
+ toRawObject(unwrappedValue as Record<any, any>, result, clean, seen)
35
80
  return result
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
 
@@ -10,17 +10,27 @@ import { useRoute } from 'vue-router'
10
10
  * has to tell "a variant is on its way" from "this story is waiting for the
11
11
  * reader to pick one" needs the same answer it will give — hence one predicate
12
12
  * rather than two copies of the condition (#328).
13
+ *
14
+ * Any story with variants, deliberately. It used to require a remembered choice
15
+ * or exactly one variant, which made `StoryView`'s `variants[0]` fallback
16
+ * unreachable: a story with several variants and no history satisfied neither
17
+ * clause, so the first visit of a session showed an empty panel and every visit
18
+ * after it worked, because the first one recorded the history (#724).
19
+ *
20
+ * No layout clause. Grid stories already auto-selected on revisit through the
21
+ * remembered choice, so excluding them here would auto-open them on the second
22
+ * visit and never the first — this defect, made permanent for one layout.
13
23
  */
14
24
  // A type predicate rather than `boolean`: the body already establishes that the
15
25
  // story is there, and returning `boolean` threw that away at every call site —
16
26
  // `StoryView` reads the story twice immediately after this returns true.
17
27
  export function autoSelectsVariant(story: Story | null | undefined): story is Story {
18
- return !!story && (!!story.lastSelectedVariant || story.variants.length === 1)
28
+ return !!story && story.variants.length > 0
19
29
  }
20
30
 
21
31
  export function useCurrentVariantRoute(variant: Ref<Variant>) {
22
32
  const route = useRoute()
23
- const isActive = computed(() => route.query.variantId === variant.value.id)
33
+ const isActive = computed(() => route.query['variantId'] === variant.value.id)
24
34
  const targetRoute = computed(() => ({
25
35
  ...route,
26
36
  query: {
@@ -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
- }