@poveste/app 0.13.0 → 0.14.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/dist/bundled/App.vue_vue_type_script_setup_true_lang.js +26 -34
- package/dist/bundled/app.css +18 -24
- package/dist/bundled/components/HomeView.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/app/AppLogo.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/bundled/components/app/Breadcrumb.js +1 -1
- package/dist/bundled/components/app/Breadcrumb.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/bundled/components/app/TopBar.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/bundled/components/base/BaseCheckbox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/base/BaseListItemLink.js +6 -3
- package/dist/bundled/components/base/BaseListItemLink.vue_vue_type_script_lang.js +10 -4
- package/dist/bundled/components/base/BaseOverflowMenu.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/bundled/components/base/BaseOverflowTab.js +6 -3
- package/dist/bundled/components/base/BaseOverflowTab.vue_vue_type_script_lang.js +4 -0
- package/dist/bundled/components/base/BaseSplitPane.js +1 -1
- package/dist/bundled/components/base/BaseSplitPane.vue_vue_type_script_setup_true_lang.js +6 -4
- package/dist/bundled/components/base/BaseTab.js +6 -3
- package/dist/bundled/components/base/BaseTab.vue_vue_type_script_lang.js +4 -0
- package/dist/bundled/components/command/CommandPrompts.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/command/CommandPromptsModal.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/bundled/components/command/PromptSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/command/PromptText.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/panel/PaneTabs.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/bundled/components/panel/StatePresets.vue_vue_type_script_setup_true_lang.js +30 -20
- package/dist/bundled/components/panel/StoryControls.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/panel/StoryDocs.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/bundled/components/panel/StoryEvent.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/bundled/components/panel/StoryEvents.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/panel/StorySidePanel.vue_vue_type_script_setup_true_lang.js +7 -5
- package/dist/bundled/components/panel/StorySourceCode.js +1 -1
- package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +6 -5
- package/dist/bundled/components/search/SearchPane.vue_vue_type_script_setup_true_lang.js +15 -7
- package/dist/bundled/components/story/GenericMountStory.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/story/GenericRenderStory.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/story/StoryResponsivePreview.js +1 -1
- package/dist/bundled/components/story/StoryResponsivePreview.vue_vue_type_script_setup_true_lang.js +17 -6
- package/dist/bundled/components/story/StoryVariantGrid.js +1 -1
- package/dist/bundled/components/story/StoryVariantGrid.vue_vue_type_script_setup_true_lang.js +15 -10
- package/dist/bundled/components/story/StoryVariantSingle.js +1 -1
- package/dist/bundled/components/story/StoryVariantSingle.vue_vue_type_script_setup_true_lang.js +21 -33
- package/dist/bundled/components/story/StoryVariantSingleView.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/bundled/components/story/StoryView.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/bundled/components/story/StoryViewer.js +1 -1
- package/dist/bundled/components/story/StoryViewer.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/bundled/components/toolbar/ToolbarBackground.js +1 -3
- package/dist/bundled/components/toolbar/ToolbarBackground.vue_vue_type_script_setup_true_lang.js +2 -6
- package/dist/bundled/components/toolbar/ToolbarResponsiveSize.vue_vue_type_script_setup_true_lang.js +17 -16
- package/dist/bundled/plugin.js +4 -3
- package/dist/bundled/sandbox.js +2 -2
- package/dist/bundled/stores/folder.js +1 -1
- package/dist/bundled/util/commands.js +20 -9
- package/dist/bundled/util/docs.js +2 -1
- package/dist/bundled/util/grid-slots.js +1 -1
- package/dist/bundled/util/highlighter.js +1 -1
- package/dist/bundled/util/icons.generated.js +0 -1
- package/dist/bundled/util/keyboard.js +1 -0
- package/dist/bundled/util/mapping.js +6 -2
- package/dist/bundled/util/preview-settings.js +1 -1
- package/dist/style.css +14 -2
- package/package.json +8 -6
- package/src/app/App.vue +36 -29
- package/src/app/components/HomeView.vue +1 -1
- package/src/app/components/app/AppLogo.vue +6 -2
- package/src/app/components/app/Breadcrumb.vue +2 -1
- package/src/app/components/app/TopBar.vue +7 -2
- package/src/app/components/base/BaseCheckbox.vue +1 -1
- package/src/app/components/base/BaseListItemLink.vue +12 -1
- package/src/app/components/base/BaseOverflowMenu.vue +9 -3
- package/src/app/components/base/BaseOverflowTab.vue +12 -1
- package/src/app/components/base/BaseSplitPane.vue +9 -4
- package/src/app/components/base/BaseTab.vue +12 -1
- package/src/app/components/command/CommandPrompts.vue +10 -2
- package/src/app/components/command/CommandPromptsModal.vue +1 -0
- package/src/app/components/command/PromptSelect.vue +2 -1
- package/src/app/components/command/PromptText.vue +7 -3
- package/src/app/components/panel/PaneTabs.vue +4 -4
- package/src/app/components/panel/StatePresets.vue +26 -5
- package/src/app/components/panel/StoryControls.vue +1 -1
- package/src/app/components/panel/StoryDocs.vue +3 -1
- package/src/app/components/panel/StoryEvent.vue +7 -2
- package/src/app/components/panel/StoryEvents.vue +4 -1
- package/src/app/components/panel/StorySidePanel.vue +2 -0
- package/src/app/components/panel/StorySourceCode.vue +11 -8
- package/src/app/components/search/SearchPane.vue +14 -5
- package/src/app/components/story/GenericMountStory.vue +2 -1
- package/src/app/components/story/GenericRenderStory.vue +2 -1
- package/src/app/components/story/StoryResponsivePreview.vue +30 -5
- package/src/app/components/story/StoryVariantGrid.vue +33 -16
- package/src/app/components/story/StoryVariantSingle.vue +44 -52
- package/src/app/components/story/StoryVariantSingleView.vue +1 -1
- package/src/app/components/story/StoryView.vue +7 -3
- package/src/app/components/story/StoryViewer.vue +3 -3
- package/src/app/components/toolbar/ToolbarBackground.vue +1 -8
- package/src/app/components/toolbar/ToolbarResponsiveSize.vue +23 -3
- package/src/app/components/tree/StoryGroup.vue +1 -1
- package/src/app/plugin.ts +7 -3
- package/src/app/sandbox.ts +1 -1
- package/src/app/stores/folder.ts +1 -1
- package/src/app/types.ts +2 -2
- package/src/app/util/commands.ts +26 -16
- package/src/app/util/docs.ts +2 -1
- package/src/app/util/grid-slots.ts +1 -1
- package/src/app/util/highlighter.ts +9 -1
- package/src/app/util/icons.generated.ts +1 -1
- package/src/app/util/keyboard.ts +3 -0
- package/src/app/util/mapping.ts +6 -2
- package/src/app/util/preview-settings.ts +1 -1
- package/src/app/util/scroll.ts +5 -1
- package/src/app/util/state.ts +1 -1
- package/src/app/util/variant.ts +4 -1
- package/src/app/components/base/BaseCopyIcon.vue +0 -34
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
2
4
|
import { defineComponent } from 'vue'
|
|
3
5
|
|
|
4
6
|
export default defineComponent({
|
|
5
7
|
inheritAttrs: false,
|
|
6
8
|
|
|
7
9
|
props: {
|
|
10
|
+
// Declared rather than passed through `$attrs`: with
|
|
11
|
+
// `inheritAttrs: false` the checker cannot see it there, and
|
|
12
|
+
// router-link requires it.
|
|
13
|
+
to: {
|
|
14
|
+
type: [String, Object] as PropType<RouteLocationRaw>,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
|
|
8
18
|
isActive: {
|
|
9
19
|
type: Boolean,
|
|
10
20
|
default: undefined,
|
|
@@ -31,6 +41,7 @@ export default defineComponent({
|
|
|
31
41
|
<template>
|
|
32
42
|
<RouterLink
|
|
33
43
|
v-slot="{ isActive: linkIsActive, href, navigate }"
|
|
44
|
+
:to="to"
|
|
34
45
|
class="poveste-base-list-item-link"
|
|
35
46
|
v-bind="$attrs"
|
|
36
47
|
custom
|
|
@@ -42,7 +53,7 @@ export default defineComponent({
|
|
|
42
53
|
:class="[
|
|
43
54
|
$attrs.class,
|
|
44
55
|
(isActive != null ? isActive : linkIsActive)
|
|
45
|
-
? 'active bg-primary-500 hover:bg-primary-600 text-white dark:text-black'
|
|
56
|
+
? 'active bg-primary-700 hover:bg-primary-800 dark:bg-primary-500 dark:hover:bg-primary-600 text-white dark:text-black'
|
|
46
57
|
: 'hover:bg-primary-100 dark:hover:bg-primary-900',
|
|
47
58
|
]"
|
|
48
59
|
@click="handleNavigate($event, navigate)"
|
|
@@ -49,15 +49,21 @@ const ChildWrapper = {
|
|
|
49
49
|
const state = reactive({ width: 0, index: props.index })
|
|
50
50
|
|
|
51
51
|
useResizeObserver(el, (entries) => {
|
|
52
|
+
// The observer only fires for an element it is observing, so this is
|
|
53
|
+
// never null here — but `children` is keyed by the element, and an
|
|
54
|
+
// undefined key would silently make a second entry for the same child.
|
|
55
|
+
const element = el.value
|
|
56
|
+
if (!element) return
|
|
52
57
|
const width = entries[0].contentRect.width
|
|
53
|
-
if (!children.value.has(
|
|
54
|
-
children.value.set(
|
|
58
|
+
if (!children.value.has(element)) {
|
|
59
|
+
children.value.set(element, state)
|
|
55
60
|
}
|
|
56
61
|
state.width = width
|
|
57
62
|
})
|
|
58
63
|
|
|
59
64
|
onBeforeUnmount(() => {
|
|
60
|
-
|
|
65
|
+
const element = el.value
|
|
66
|
+
if (element) children.value.delete(element)
|
|
61
67
|
})
|
|
62
68
|
|
|
63
69
|
const visible = computed(() => visibleChildrenCount.value > state.index)
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
2
4
|
import { defineComponent } from 'vue'
|
|
3
5
|
|
|
4
6
|
export default defineComponent({
|
|
5
7
|
inheritAttrs: false,
|
|
6
8
|
|
|
7
9
|
props: {
|
|
10
|
+
// Declared rather than passed through `$attrs`: with
|
|
11
|
+
// `inheritAttrs: false` the checker cannot see it there, and
|
|
12
|
+
// router-link requires it.
|
|
13
|
+
to: {
|
|
14
|
+
type: [String, Object] as PropType<RouteLocationRaw>,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
|
|
8
18
|
exact: {
|
|
9
19
|
type: Boolean,
|
|
10
20
|
default: false,
|
|
@@ -21,6 +31,7 @@ export default defineComponent({
|
|
|
21
31
|
<template>
|
|
22
32
|
<router-link
|
|
23
33
|
v-slot="{ isActive, isExactActive, href, navigate }"
|
|
34
|
+
:to="to"
|
|
24
35
|
class="poveste-base-overflow-tab"
|
|
25
36
|
v-bind="$attrs"
|
|
26
37
|
custom
|
|
@@ -30,7 +41,7 @@ export default defineComponent({
|
|
|
30
41
|
:href="href"
|
|
31
42
|
class="px-4 h-10 min-w-[150px] inline-flex items-center hover:bg-primary-50 dark:hover:bg-primary-900 relative text-gray-900 dark:text-gray-100"
|
|
32
43
|
:class="{
|
|
33
|
-
'text-primary-
|
|
44
|
+
'text-primary-700 dark:text-primary-400': matched != null ? matched : (exact && isExactActive) || (!exact && isActive),
|
|
34
45
|
}"
|
|
35
46
|
@click="navigate"
|
|
36
47
|
>
|
|
@@ -168,13 +168,13 @@ const rightStyle = computed(() => {
|
|
|
168
168
|
if (!hasBothPanes.value) {
|
|
169
169
|
return { [axis.value]: '100%' }
|
|
170
170
|
}
|
|
171
|
-
return { [axis.value]: props.fixed ?
|
|
171
|
+
return { [axis.value]: props.fixed ? undefined : `${100 - boundSplit.value}%` }
|
|
172
172
|
})
|
|
173
173
|
|
|
174
174
|
const dragging = ref(false)
|
|
175
175
|
let startPosition = 0
|
|
176
176
|
let startSplit = 0
|
|
177
|
-
const el = ref(
|
|
177
|
+
const el = ref<HTMLElement>()
|
|
178
178
|
|
|
179
179
|
function dragStart(e) {
|
|
180
180
|
dragging.value = true
|
|
@@ -186,15 +186,20 @@ function dragStart(e) {
|
|
|
186
186
|
|
|
187
187
|
function dragMove(e) {
|
|
188
188
|
if (dragging.value) {
|
|
189
|
+
// The move listener is on `window`, so it can outlive the element for the
|
|
190
|
+
// rest of a drag if the pane unmounts under it.
|
|
191
|
+
const pane = el.value
|
|
192
|
+
if (!pane) return
|
|
193
|
+
|
|
189
194
|
let position
|
|
190
195
|
let totalSize
|
|
191
196
|
if (props.orientation === 'landscape') {
|
|
192
197
|
position = e.pageX
|
|
193
|
-
totalSize =
|
|
198
|
+
totalSize = pane.offsetWidth
|
|
194
199
|
}
|
|
195
200
|
else {
|
|
196
201
|
position = e.pageY
|
|
197
|
-
totalSize =
|
|
202
|
+
totalSize = pane.offsetHeight
|
|
198
203
|
}
|
|
199
204
|
const dPosition = position - startPosition
|
|
200
205
|
if (props.fixed) {
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
2
4
|
import { defineComponent } from 'vue'
|
|
3
5
|
|
|
4
6
|
export default defineComponent({
|
|
5
7
|
inheritAttrs: false,
|
|
6
8
|
|
|
7
9
|
props: {
|
|
10
|
+
// Declared rather than passed through `$attrs`: with
|
|
11
|
+
// `inheritAttrs: false` the checker cannot see it there, and
|
|
12
|
+
// router-link requires it.
|
|
13
|
+
to: {
|
|
14
|
+
type: [String, Object] as PropType<RouteLocationRaw>,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
|
|
8
18
|
exact: {
|
|
9
19
|
type: Boolean,
|
|
10
20
|
default: false,
|
|
@@ -21,6 +31,7 @@ export default defineComponent({
|
|
|
21
31
|
<template>
|
|
22
32
|
<router-link
|
|
23
33
|
v-slot="{ isActive, isExactActive, href, navigate }"
|
|
34
|
+
:to="to"
|
|
24
35
|
class="poveste-base-tab"
|
|
25
36
|
v-bind="$attrs"
|
|
26
37
|
custom
|
|
@@ -30,7 +41,7 @@ export default defineComponent({
|
|
|
30
41
|
:href="href"
|
|
31
42
|
class="px-4 h-full inline-flex items-center hover:bg-primary-50 dark:hover:bg-primary-900 relative text-gray-900 dark:text-gray-100"
|
|
32
43
|
:class="{
|
|
33
|
-
'text-primary-
|
|
44
|
+
'text-primary-700 dark:text-primary-400': matched != null ? matched : (exact && isExactActive) || (!exact && isActive),
|
|
34
45
|
}"
|
|
35
46
|
@click="navigate"
|
|
36
47
|
>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { ClientCommand } from '@poveste/shared'
|
|
3
|
+
import type { Component } from 'vue'
|
|
3
4
|
import { nextTick, onMounted, reactive, ref } from 'vue'
|
|
4
5
|
import { executeCommand, getCommandContext } from '../../util/commands.js'
|
|
5
6
|
import BaseButton from '../base/BaseButton.vue'
|
|
@@ -23,7 +24,7 @@ const promptTypes = {
|
|
|
23
24
|
const answers = reactive<Record<string, any>>({})
|
|
24
25
|
|
|
25
26
|
// Initial default values
|
|
26
|
-
for (const prompt of props.command.prompts) {
|
|
27
|
+
for (const prompt of props.command.prompts ?? []) {
|
|
27
28
|
let defaultValue
|
|
28
29
|
if (typeof prompt.defaultValue === 'function') {
|
|
29
30
|
defaultValue = prompt.defaultValue(answers)
|
|
@@ -67,8 +68,15 @@ onMounted(() => {
|
|
|
67
68
|
{{ command.label }}
|
|
68
69
|
</div>
|
|
69
70
|
|
|
71
|
+
<!--
|
|
72
|
+
`promptTypes[prompt.type]` resolves to the component whose `prompt` prop
|
|
73
|
+
is that member of the union, but nothing states the correlation and the
|
|
74
|
+
checker cannot infer it. Splitting this into `v-if` branches per type is
|
|
75
|
+
what makes it checkable; that is a refactor of untested UI, so it waits
|
|
76
|
+
for coverage rather than riding along with the type gate (#441).
|
|
77
|
+
-->
|
|
70
78
|
<component
|
|
71
|
-
:is="promptTypes[prompt.type]"
|
|
79
|
+
:is="promptTypes[prompt.type] as Component"
|
|
72
80
|
v-for="(prompt, index) of command.prompts"
|
|
73
81
|
:key="prompt.field"
|
|
74
82
|
ref="promptComps"
|
|
@@ -33,6 +33,7 @@ const commandStore = useCommandStore()
|
|
|
33
33
|
/>
|
|
34
34
|
<div class="bg-white dark:bg-gray-900 md:mt-16 md:mx-auto w-screen max-w-[512px] max-h-[80vh] overflow-y-auto scroll-smooth shadow-xl border border-gray-200 dark:border-gray-750 rounded-lg relative divide-y divide-gray-200 dark:divide-gray-850">
|
|
35
35
|
<CommandPrompts
|
|
36
|
+
v-if="commandStore.selectedCommand"
|
|
36
37
|
:command="commandStore.selectedCommand"
|
|
37
38
|
@close="close()"
|
|
38
39
|
/>
|
|
@@ -16,8 +16,9 @@ const emit = defineEmits<{
|
|
|
16
16
|
(e: 'next'): void
|
|
17
17
|
}>()
|
|
18
18
|
|
|
19
|
+
// Optional coming in, required going out, so the empty case settles here.
|
|
19
20
|
const model = computed({
|
|
20
|
-
get: () => props.modelValue,
|
|
21
|
+
get: () => props.modelValue ?? '',
|
|
21
22
|
set: (value) => {
|
|
22
23
|
emit('update:modelValue', value)
|
|
23
24
|
emit('next')
|
|
@@ -9,11 +9,15 @@ const props = defineProps<{
|
|
|
9
9
|
}>()
|
|
10
10
|
|
|
11
11
|
const emit = defineEmits<{
|
|
12
|
-
(e: 'update:modelValue', value: string): void
|
|
12
|
+
(e: 'update:modelValue', value: string | undefined): void
|
|
13
13
|
}>()
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// The input needs a string to bind to, so the getter fills in `''` — but an
|
|
16
|
+
// absent answer has to leave as absent. `''` is falsy and *not* nullish, so
|
|
17
|
+
// coercing on the way out would stop a command's `answers.x ?? fallback` from
|
|
18
|
+
// firing, which is the trap #440 was about one file over.
|
|
19
|
+
const model = computed<string | undefined>({
|
|
20
|
+
get: () => props.modelValue ?? '',
|
|
17
21
|
set: value => emit('update:modelValue', value),
|
|
18
22
|
})
|
|
19
23
|
|
|
@@ -33,7 +33,7 @@ const hasEvents = computed(() => eventsStore.events.length)
|
|
|
33
33
|
:to="{ ...$route, query: { ...$route.query, tab: 'docs' } }"
|
|
34
34
|
:matched="$route.query.tab === 'docs'"
|
|
35
35
|
:class="{
|
|
36
|
-
'opacity-
|
|
36
|
+
'opacity-70': !renderedDoc,
|
|
37
37
|
}"
|
|
38
38
|
>
|
|
39
39
|
Docs
|
|
@@ -42,7 +42,7 @@ const hasEvents = computed(() => eventsStore.events.length)
|
|
|
42
42
|
:to="{ ...$route, query: { ...$route.query, tab: 'events' } }"
|
|
43
43
|
:matched="$route.query.tab === 'events'"
|
|
44
44
|
:class="{
|
|
45
|
-
'opacity-
|
|
45
|
+
'opacity-70': !hasEvents,
|
|
46
46
|
}"
|
|
47
47
|
>
|
|
48
48
|
Events
|
|
@@ -62,7 +62,7 @@ const hasEvents = computed(() => eventsStore.events.length)
|
|
|
62
62
|
:to="{ ...$route, query: { ...$route.query, tab: 'docs' } }"
|
|
63
63
|
:matched="$route.query.tab === 'docs'"
|
|
64
64
|
:class="{
|
|
65
|
-
'opacity-
|
|
65
|
+
'opacity-70': !renderedDoc,
|
|
66
66
|
}"
|
|
67
67
|
>
|
|
68
68
|
Docs
|
|
@@ -71,7 +71,7 @@ const hasEvents = computed(() => eventsStore.events.length)
|
|
|
71
71
|
:to="{ ...$route, query: { ...$route.query, tab: 'events' } }"
|
|
72
72
|
:matched="$route.query.tab === 'events'"
|
|
73
73
|
:class="{
|
|
74
|
-
'opacity-
|
|
74
|
+
'opacity-70': !hasEvents,
|
|
75
75
|
}"
|
|
76
76
|
>
|
|
77
77
|
Events
|
|
@@ -57,8 +57,13 @@ function applyPreset(id) {
|
|
|
57
57
|
if (id === DEFAULT_ID) {
|
|
58
58
|
resetState()
|
|
59
59
|
}
|
|
60
|
-
else
|
|
61
|
-
|
|
60
|
+
else {
|
|
61
|
+
// `has` then `get` is two lookups and only the second one is the value;
|
|
62
|
+
// reading it once says the same thing and carries the type with it.
|
|
63
|
+
const preset = presetStates.value.get(id)
|
|
64
|
+
if (preset) {
|
|
65
|
+
applyState(props.variant.state, clone(toRawDeep(preset.state)))
|
|
66
|
+
}
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -80,9 +85,22 @@ async function createPreset() {
|
|
|
80
85
|
selectedOption.value = id
|
|
81
86
|
isEditing.value = true
|
|
82
87
|
await nextTick()
|
|
83
|
-
input
|
|
88
|
+
// The input is rendered by the edit state set above, so it exists by now —
|
|
89
|
+
// unless editing was cancelled during the tick, which unmounts it.
|
|
90
|
+
input.value?.select()
|
|
84
91
|
}
|
|
85
92
|
|
|
93
|
+
// The template bound `v-model` straight to `presetStates.get(selectedOption).label`,
|
|
94
|
+
// which is a Map lookup that can miss. Naming it once keeps the template honest
|
|
95
|
+
// and gives the miss somewhere to go.
|
|
96
|
+
const editingLabel = computed({
|
|
97
|
+
get: () => presetStates.value.get(selectedOption.value)?.label ?? '',
|
|
98
|
+
set: (value) => {
|
|
99
|
+
const preset = presetStates.value.get(selectedOption.value)
|
|
100
|
+
if (preset) preset.label = value
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
|
|
86
104
|
const savedNotif = ref(false)
|
|
87
105
|
const savedTimeout = useTimeoutFn(() => {
|
|
88
106
|
savedNotif.value = false
|
|
@@ -92,6 +110,7 @@ async function savePreset() {
|
|
|
92
110
|
if (!canEdit.value) return
|
|
93
111
|
|
|
94
112
|
const preset = presetStates.value.get(selectedOption.value)
|
|
113
|
+
if (!preset) return
|
|
95
114
|
preset.state = toPresetState(props.variant.state, omitKeys)
|
|
96
115
|
savedNotif.value = true
|
|
97
116
|
savedTimeout.start()
|
|
@@ -117,7 +136,9 @@ async function startEditing() {
|
|
|
117
136
|
|
|
118
137
|
isEditing.value = true
|
|
119
138
|
await nextTick()
|
|
120
|
-
input
|
|
139
|
+
// The input is rendered by the edit state set above, so it exists by now —
|
|
140
|
+
// unless editing was cancelled during the tick, which unmounts it.
|
|
141
|
+
input.value?.select()
|
|
121
142
|
}
|
|
122
143
|
|
|
123
144
|
function stopEditing() {
|
|
@@ -145,7 +166,7 @@ onClickOutside(select, stopEditing)
|
|
|
145
166
|
<input
|
|
146
167
|
v-if="isEditing"
|
|
147
168
|
ref="input"
|
|
148
|
-
v-model="
|
|
169
|
+
v-model="editingLabel"
|
|
149
170
|
type="text"
|
|
150
171
|
class="text-inherit bg-transparent w-full h-full outline-none"
|
|
151
172
|
@click.stop.prevent
|
|
@@ -28,7 +28,7 @@ watch(() => props.variant, () => {
|
|
|
28
28
|
ready.value = false
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
-
const hasCustomControls = computed(() => props.variant.slots().controls || props.story.slots().controls)
|
|
31
|
+
const hasCustomControls = computed(() => props.variant.slots?.().controls || props.story.slots?.().controls)
|
|
32
32
|
|
|
33
33
|
const hasInitState = computed(() => Object
|
|
34
34
|
.entries(props.variant.state || {})
|
|
@@ -14,7 +14,9 @@ export function useStoryDoc(story: Ref<Story>) {
|
|
|
14
14
|
|
|
15
15
|
watchEffect(async () => {
|
|
16
16
|
// Markdown file
|
|
17
|
-
const
|
|
17
|
+
const filePath = story.value?.file?.filePath
|
|
18
|
+
if (!filePath) return
|
|
19
|
+
const mdKey = filePath.replace(/\.(\w*)$/, '.md')
|
|
18
20
|
if (markdownFiles[mdKey]) {
|
|
19
21
|
const md = await markdownFiles[mdKey]()
|
|
20
22
|
renderedDoc.value = md.html
|
|
@@ -10,8 +10,13 @@ const formattedArgument = computed(() => {
|
|
|
10
10
|
switch (typeof props.event.argument) {
|
|
11
11
|
case 'string':
|
|
12
12
|
return `"${props.event.argument}"`
|
|
13
|
-
case 'object':
|
|
14
|
-
|
|
13
|
+
case 'object': {
|
|
14
|
+
// `argument` is `unknown`, and `typeof x === 'object'` narrows it only as
|
|
15
|
+
// far as `object | null` — enough for `Object.keys`, not for indexing.
|
|
16
|
+
const argument = props.event.argument as Record<string, unknown> | null
|
|
17
|
+
if (!argument) return 'null'
|
|
18
|
+
return `{ ${Object.keys(argument).map(key => `${key}: ${argument[key]}`).join(', ')} }`
|
|
19
|
+
}
|
|
15
20
|
default:
|
|
16
21
|
return props.event.argument
|
|
17
22
|
}
|
|
@@ -19,7 +19,10 @@ async function resetUnseen() {
|
|
|
19
19
|
eventsStore.unseen = 0
|
|
20
20
|
}
|
|
21
21
|
await nextTick()
|
|
22
|
-
|
|
22
|
+
// Read after the await, and it is a template ref: the panel can be closed
|
|
23
|
+
// during the tick, and then there is nothing to scroll.
|
|
24
|
+
const events = eventsElement.value
|
|
25
|
+
events?.scrollTo({ top: events.scrollHeight })
|
|
23
26
|
}
|
|
24
27
|
</script>
|
|
25
28
|
|
|
@@ -60,6 +60,7 @@ const panelContentComponent = computed(() => {
|
|
|
60
60
|
<template #first>
|
|
61
61
|
<div class="flex flex-col h-full">
|
|
62
62
|
<PaneTabs
|
|
63
|
+
v-if="storyStore.currentStory"
|
|
63
64
|
:story="storyStore.currentStory"
|
|
64
65
|
:variant="storyStore.currentVariant"
|
|
65
66
|
/>
|
|
@@ -75,6 +76,7 @@ const panelContentComponent = computed(() => {
|
|
|
75
76
|
|
|
76
77
|
<template #last>
|
|
77
78
|
<StorySourceCode
|
|
79
|
+
v-if="storyStore.currentStory"
|
|
78
80
|
:story="storyStore.currentStory"
|
|
79
81
|
:variant="storyStore.currentVariant"
|
|
80
82
|
class="h-full"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import type {
|
|
2
|
+
import type { HighlighterCore } from 'shiki/core'
|
|
3
3
|
import type { Story, Variant } from '../../types'
|
|
4
4
|
import { Icon } from '@iconify/vue'
|
|
5
5
|
import { HstCopyIcon } from '@poveste/controls'
|
|
@@ -15,20 +15,23 @@ const props = defineProps<{
|
|
|
15
15
|
variant: Variant
|
|
16
16
|
}>()
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// `ref(null)` infers `Ref<null>`, so the assignment below and the call further
|
|
19
|
+
// down were both errors about a type nobody meant to declare.
|
|
20
|
+
const generateSourceCodeFn = shallowRef<((variant: Variant) => Promise<string>) | null>(null)
|
|
19
21
|
|
|
20
22
|
watchEffect(async () => {
|
|
21
|
-
const
|
|
23
|
+
const supportPluginId = props.story.file?.supportPluginId
|
|
24
|
+
const clientPlugin = supportPluginId ? clientSupportPlugins[supportPluginId] : undefined
|
|
22
25
|
if (clientPlugin) {
|
|
23
26
|
const pluginModule = await clientPlugin()
|
|
24
27
|
generateSourceCodeFn.value = markRaw(pluginModule.generateSourceCode)
|
|
25
28
|
}
|
|
26
29
|
})
|
|
27
30
|
|
|
28
|
-
const highlighter = shallowRef<
|
|
31
|
+
const highlighter = shallowRef<HighlighterCore>()
|
|
29
32
|
|
|
30
33
|
const dynamicSourceCode = ref('')
|
|
31
|
-
const error = ref<string>(null)
|
|
34
|
+
const error = ref<string | null>(null)
|
|
32
35
|
|
|
33
36
|
// Above the watch that assigns it: the callback is async but runs immediately,
|
|
34
37
|
// and a story whose `slots()` throws reaches the auto-switch below before its
|
|
@@ -103,7 +106,7 @@ onMounted(async () => {
|
|
|
103
106
|
const sourceHtml = computed(() => displayedSourceCode.value
|
|
104
107
|
? highlighter.value?.codeToHtml(displayedSourceCode.value, {
|
|
105
108
|
lang: 'html',
|
|
106
|
-
theme: isDark.value ? 'github-dark' : 'github-light',
|
|
109
|
+
theme: isDark.value ? 'github-dark' : 'github-light-high-contrast',
|
|
107
110
|
})
|
|
108
111
|
: '')
|
|
109
112
|
|
|
@@ -152,7 +155,7 @@ watch(sourceHtml, async () => {
|
|
|
152
155
|
v-tooltip="!dynamicSourceCode ? 'Dynamic source code is not available' : displayedSource !== 'dynamic' ? 'Switch to dynamic source' : null"
|
|
153
156
|
class="flex items-center gap-1 h-full px-1 bg-gray-500/10 rounded-l transition-all ease-[cubic-bezier(0,1,.6,1)] duration-300 overflow-hidden"
|
|
154
157
|
:class="[
|
|
155
|
-
displayedSource !== 'dynamic' ? 'max-w-6 opacity-70' : 'max-w-[82px] text-primary-
|
|
158
|
+
displayedSource !== 'dynamic' ? 'max-w-6 opacity-70' : 'max-w-[82px] text-primary-800 dark:text-primary-400',
|
|
156
159
|
dynamicSourceCode ? 'cursor-pointer hover:bg-gray-500/30 active:bg-gray-600/50' : 'opacity-50',
|
|
157
160
|
]"
|
|
158
161
|
@click="dynamicSourceCode && (displayedSource = 'dynamic')"
|
|
@@ -174,7 +177,7 @@ watch(sourceHtml, async () => {
|
|
|
174
177
|
v-tooltip="!staticSourceCode ? 'Static source code is not available' : displayedSource !== 'static' ? 'Switch to static source' : null"
|
|
175
178
|
class="flex items-center gap-1 h-full px-1 bg-gray-500/10 rounded-r transition-all ease-[cubic-bezier(0,1,.6,1)] duration-300 overflow-hidden"
|
|
176
179
|
:class="[
|
|
177
|
-
displayedSource !== 'static' ? 'max-w-6 opacity-70' : 'max-w-[63px] text-primary-
|
|
180
|
+
displayedSource !== 'static' ? 'max-w-6 opacity-70' : 'max-w-[63px] text-primary-800 dark:text-primary-400',
|
|
178
181
|
staticSourceCode ? 'cursor-pointer hover:bg-gray-500/30 active:bg-gray-600/50' : 'opacity-50',
|
|
179
182
|
]"
|
|
180
183
|
@click="staticSourceCode && (displayedSource = 'static')"
|
|
@@ -45,7 +45,9 @@ watch(() => props.shown, (value) => {
|
|
|
45
45
|
if (value) {
|
|
46
46
|
requestAnimationFrame(() => {
|
|
47
47
|
focused.value = true
|
|
48
|
-
|
|
48
|
+
// A whole frame later, so the pane may have been closed again — a
|
|
49
|
+
// template ref is undefined once its element unmounts.
|
|
50
|
+
input.value?.select()
|
|
49
51
|
})
|
|
50
52
|
}
|
|
51
53
|
})
|
|
@@ -127,7 +129,11 @@ watch(rateLimitedSearch, async (value) => {
|
|
|
127
129
|
if (!idMapData) continue
|
|
128
130
|
switch (idMapData.kind) {
|
|
129
131
|
case 'story': {
|
|
130
|
-
|
|
132
|
+
// The search index is built from the stories and outlives an
|
|
133
|
+
// individual one, so a hit can name a story that is no longer loaded.
|
|
134
|
+
const story = storyStore.getStoryById(idMapData.id)
|
|
135
|
+
if (!story) continue
|
|
136
|
+
list.push(storyResultFactory(story, rank))
|
|
131
137
|
rank++
|
|
132
138
|
break
|
|
133
139
|
}
|
|
@@ -135,6 +141,7 @@ watch(rateLimitedSearch, async (value) => {
|
|
|
135
141
|
const [storyId] = idMapData.id.split(':')
|
|
136
142
|
const story = storyStore.getStoryById(storyId)
|
|
137
143
|
const variant = storyStore.getVariantById(idMapData.id)
|
|
144
|
+
if (!story || !variant) continue
|
|
138
145
|
list.push(variantResultFactory(story, variant, rank))
|
|
139
146
|
rank++
|
|
140
147
|
break
|
|
@@ -158,7 +165,9 @@ async function searchOnDocField(query: string) {
|
|
|
158
165
|
if (!idMapData) continue
|
|
159
166
|
switch (idMapData.kind) {
|
|
160
167
|
case 'story': {
|
|
161
|
-
|
|
168
|
+
const story = storyStore.getStoryById(idMapData.id)
|
|
169
|
+
if (!story) continue
|
|
170
|
+
list.push(storyResultFactory(story, rank, 'docs'))
|
|
162
171
|
rank++
|
|
163
172
|
break
|
|
164
173
|
}
|
|
@@ -188,7 +197,7 @@ function storyResultFactory(story: Story, rank: number, type: SearchResultType =
|
|
|
188
197
|
: {},
|
|
189
198
|
},
|
|
190
199
|
},
|
|
191
|
-
path: story.file
|
|
200
|
+
path: story.file?.path.slice(0, -1) ?? [],
|
|
192
201
|
icon: story.icon,
|
|
193
202
|
iconColor: story.iconColor,
|
|
194
203
|
}
|
|
@@ -212,7 +221,7 @@ function variantResultFactory(story: Story, variant: Variant, rank: number, type
|
|
|
212
221
|
: {},
|
|
213
222
|
},
|
|
214
223
|
},
|
|
215
|
-
path: [...story.file
|
|
224
|
+
path: [...story.file?.path ?? [], story.title],
|
|
216
225
|
icon: variant.icon,
|
|
217
226
|
iconColor: variant.iconColor,
|
|
218
227
|
}
|
|
@@ -16,7 +16,8 @@ const props = defineProps<{
|
|
|
16
16
|
const mountComponent = ref(null)
|
|
17
17
|
|
|
18
18
|
watchEffect(async () => {
|
|
19
|
-
const
|
|
19
|
+
const supportPluginId = props.story.file?.supportPluginId
|
|
20
|
+
const clientPlugin = supportPluginId ? clientSupportPlugins[supportPluginId] : undefined
|
|
20
21
|
if (clientPlugin) {
|
|
21
22
|
const pluginModule = await clientPlugin()
|
|
22
23
|
mountComponent.value = markRaw(pluginModule.MountStory)
|
|
@@ -16,7 +16,8 @@ const props = defineProps<{
|
|
|
16
16
|
const mountComponent = ref(null)
|
|
17
17
|
|
|
18
18
|
watchEffect(async () => {
|
|
19
|
-
const
|
|
19
|
+
const supportPluginId = props.story.file?.supportPluginId
|
|
20
|
+
const clientPlugin = supportPluginId ? clientSupportPlugins[supportPluginId] : undefined
|
|
20
21
|
if (clientPlugin) {
|
|
21
22
|
try {
|
|
22
23
|
const pluginModule = await clientPlugin()
|
|
@@ -43,12 +43,34 @@ function addWindowListener(event: string, listener: (event: any) => unknown) {
|
|
|
43
43
|
|
|
44
44
|
const previewWrapper = ref<HTMLDivElement>()
|
|
45
45
|
|
|
46
|
-
function useDragger(el: Ref<HTMLDivElement>, value: Ref<number>, min: number, max: number, axis: 'x' | 'y') {
|
|
46
|
+
function useDragger(el: Ref<HTMLDivElement | undefined>, value: Ref<number | null>, min: number, max: number, axis: 'x' | 'y') {
|
|
47
|
+
// The wrapper is a template ref, so it is undefined before mount and after
|
|
48
|
+
// unmount. Every read below happens during a drag, when it is on screen — but
|
|
49
|
+
// the move and up listeners are on `window`, so "during a drag" is not the
|
|
50
|
+
// same as "while this component is alive".
|
|
51
|
+
function wrapperSize() {
|
|
52
|
+
const wrapper = previewWrapper.value
|
|
53
|
+
if (!wrapper) return null
|
|
54
|
+
return axis === 'x' ? wrapper.clientWidth : wrapper.clientHeight
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// `null` is the value that means "size to the available space", so a drag
|
|
58
|
+
// beginning from auto has to begin from the size actually rendered. The touch
|
|
59
|
+
// path had no fallback and `null + delta` coerces to `delta`, which collapsed
|
|
60
|
+
// a full-height preview to the minimum on the first move (#440).
|
|
61
|
+
function startFrom() {
|
|
62
|
+
if (value.value !== null) return value.value
|
|
63
|
+
const size = wrapperSize()
|
|
64
|
+
// Nothing to measure against, so start from the floor rather than from NaN.
|
|
65
|
+
if (size === null) return min
|
|
66
|
+
return size - (axis === 'x' ? 67 : 70)
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
function onMouseDown(event: MouseEvent) {
|
|
48
70
|
event.preventDefault()
|
|
49
71
|
event.stopPropagation()
|
|
50
72
|
const start = axis === 'x' ? event.clientX : event.clientY
|
|
51
|
-
const startValue =
|
|
73
|
+
const startValue = startFrom()
|
|
52
74
|
resizing.value = true
|
|
53
75
|
|
|
54
76
|
const removeListeners = [
|
|
@@ -57,11 +79,14 @@ function useDragger(el: Ref<HTMLDivElement>, value: Ref<number>, min: number, ma
|
|
|
57
79
|
]
|
|
58
80
|
|
|
59
81
|
function onMouseMove(event: MouseEvent) {
|
|
60
|
-
const snapTarget = (
|
|
82
|
+
const snapTarget = wrapperSize()
|
|
61
83
|
const delta = (axis === 'x' ? event.clientX : event.clientY) - start
|
|
62
84
|
value.value = Math.max(min, Math.min(max, startValue + delta))
|
|
63
85
|
|
|
64
|
-
|
|
86
|
+
// 67 on both axes, unlike `startFrom`. Left as it was: the snap threshold
|
|
87
|
+
// is a feel, not a measurement, and changing it here would be a UX edit
|
|
88
|
+
// wearing a type fix's clothes.
|
|
89
|
+
if (snapTarget !== null && Math.abs(value.value - (snapTarget - 67)) < 16) {
|
|
65
90
|
value.value = null
|
|
66
91
|
}
|
|
67
92
|
}
|
|
@@ -77,7 +102,7 @@ function useDragger(el: Ref<HTMLDivElement>, value: Ref<number>, min: number, ma
|
|
|
77
102
|
event.preventDefault()
|
|
78
103
|
event.stopPropagation()
|
|
79
104
|
const start = axis === 'x' ? event.touches[0].clientX : event.touches[0].clientY
|
|
80
|
-
const startValue =
|
|
105
|
+
const startValue = startFrom()
|
|
81
106
|
resizing.value = true
|
|
82
107
|
|
|
83
108
|
const removeListeners = [
|