@poveste/app 0.12.3 → 0.13.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 (71) hide show
  1. package/dist/app/style/font.css +11 -7
  2. package/dist/app/style/fonts/LICENSE +93 -0
  3. package/dist/app/style/fonts/noto-sans-display-cyrillic-400-normal.woff2 +0 -0
  4. package/dist/app/style/fonts/noto-sans-display-cyrillic-ext-400-normal.woff2 +0 -0
  5. package/dist/app/style/fonts/noto-sans-display-greek-400-normal.woff2 +0 -0
  6. package/dist/app/style/fonts/noto-sans-display-greek-ext-400-normal.woff2 +0 -0
  7. package/dist/app/style/fonts/noto-sans-display-latin-400-normal.woff2 +0 -0
  8. package/dist/app/style/fonts/noto-sans-display-latin-ext-400-normal.woff2 +0 -0
  9. package/dist/app/style/fonts/noto-sans-display-vietnamese-400-normal.woff2 +0 -0
  10. package/dist/bundled/App.vue_vue_type_script_setup_true_lang.js +30 -38
  11. package/dist/bundled/app.css +11 -17
  12. package/dist/bundled/components/base/BaseListItemLink.js +6 -3
  13. package/dist/bundled/components/base/BaseListItemLink.vue_vue_type_script_lang.js +10 -4
  14. package/dist/bundled/components/base/BaseOverflowTab.js +6 -3
  15. package/dist/bundled/components/base/BaseOverflowTab.vue_vue_type_script_lang.js +4 -0
  16. package/dist/bundled/components/base/BaseSplitPane.js +1 -1
  17. package/dist/bundled/components/base/BaseSplitPane.vue_vue_type_script_setup_true_lang.js +67 -29
  18. package/dist/bundled/components/base/BaseTab.js +6 -3
  19. package/dist/bundled/components/base/BaseTab.vue_vue_type_script_lang.js +4 -0
  20. package/dist/bundled/components/panel/PaneTabs.vue_vue_type_script_setup_true_lang.js +4 -4
  21. package/dist/bundled/components/panel/StorySourceCode.js +1 -1
  22. package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +3 -3
  23. package/dist/bundled/components/story/StoryVariantGridItem.vue_vue_type_script_setup_true_lang.js +1 -1
  24. package/dist/bundled/components/story/StoryVariantSingle.js +1 -1
  25. package/dist/bundled/components/story/StoryVariantSingle.vue_vue_type_script_setup_true_lang.js +28 -43
  26. package/dist/bundled/components/story/StoryVariantSinglePreviewNative.vue_vue_type_script_setup_true_lang.js +5 -2
  27. package/dist/bundled/components/story/StoryVariantSinglePreviewRemote.vue_vue_type_script_setup_true_lang.js +4 -0
  28. package/dist/bundled/components/story/StoryVariantSingleView.vue_vue_type_script_setup_true_lang.js +23 -13
  29. package/dist/bundled/components/story/StoryView.vue_vue_type_script_setup_true_lang.js +8 -11
  30. package/dist/bundled/components/toolbar/ToolbarBackground.js +1 -3
  31. package/dist/bundled/components/toolbar/ToolbarBackground.vue_vue_type_script_setup_true_lang.js +1 -5
  32. package/dist/bundled/sandbox.js +6 -1
  33. package/dist/bundled/util/events.js +4 -1
  34. package/dist/bundled/util/highlighter.js +11 -4
  35. package/dist/bundled/util/mapping.js +5 -1
  36. package/dist/bundled/util/occupant.js +16 -0
  37. package/dist/bundled/util/variant.js +12 -1
  38. package/dist/style.css +18 -6
  39. package/package.json +8 -6
  40. package/src/__tests__/font.spec.ts +48 -2
  41. package/src/app/App.vue +77 -63
  42. package/src/app/components/base/BaseListItemLink.vue +12 -1
  43. package/src/app/components/base/BaseOverflowTab.vue +12 -1
  44. package/src/app/components/base/BaseSplitPane.vue +95 -29
  45. package/src/app/components/base/BaseTab.vue +12 -1
  46. package/src/app/components/command/CommandPrompts.vue +9 -1
  47. package/src/app/components/panel/PaneTabs.vue +4 -4
  48. package/src/app/components/panel/StorySourceCode.vue +5 -5
  49. package/src/app/components/story/StoryVariantSingle.vue +65 -67
  50. package/src/app/components/story/StoryVariantSinglePreviewNative.vue +13 -2
  51. package/src/app/components/story/StoryVariantSinglePreviewRemote.vue +8 -0
  52. package/src/app/components/story/StoryVariantSingleView.vue +59 -13
  53. package/src/app/components/story/StoryView.vue +17 -17
  54. package/src/app/components/toolbar/ToolbarBackground.vue +0 -7
  55. package/src/app/components/tree/StoryGroup.vue +1 -1
  56. package/src/app/sandbox.ts +9 -1
  57. package/src/app/style/font.css +11 -7
  58. package/src/app/style/fonts/LICENSE +93 -0
  59. package/src/app/style/fonts/noto-sans-display-cyrillic-400-normal.woff2 +0 -0
  60. package/src/app/style/fonts/noto-sans-display-cyrillic-ext-400-normal.woff2 +0 -0
  61. package/src/app/style/fonts/noto-sans-display-greek-400-normal.woff2 +0 -0
  62. package/src/app/style/fonts/noto-sans-display-greek-ext-400-normal.woff2 +0 -0
  63. package/src/app/style/fonts/noto-sans-display-latin-400-normal.woff2 +0 -0
  64. package/src/app/style/fonts/noto-sans-display-latin-ext-400-normal.woff2 +0 -0
  65. package/src/app/style/fonts/noto-sans-display-vietnamese-400-normal.woff2 +0 -0
  66. package/src/app/util/events.ts +3 -0
  67. package/src/app/util/highlighter.ts +24 -9
  68. package/src/app/util/mapping.ts +5 -1
  69. package/src/app/util/occupant.ts +13 -0
  70. package/src/app/util/variant.ts +13 -1
  71. package/LICENSE +0 -22
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" setup>
2
- import type { Highlighter } from 'shiki'
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'
@@ -25,7 +25,7 @@ watchEffect(async () => {
25
25
  }
26
26
  })
27
27
 
28
- const highlighter = shallowRef<Highlighter>()
28
+ const highlighter = shallowRef<HighlighterCore>()
29
29
 
30
30
  const dynamicSourceCode = ref('')
31
31
  const error = ref<string>(null)
@@ -103,7 +103,7 @@ onMounted(async () => {
103
103
  const sourceHtml = computed(() => displayedSourceCode.value
104
104
  ? highlighter.value?.codeToHtml(displayedSourceCode.value, {
105
105
  lang: 'html',
106
- theme: isDark.value ? 'github-dark' : 'github-light',
106
+ theme: isDark.value ? 'github-dark' : 'github-light-high-contrast',
107
107
  })
108
108
  : '')
109
109
 
@@ -152,7 +152,7 @@ watch(sourceHtml, async () => {
152
152
  v-tooltip="!dynamicSourceCode ? 'Dynamic source code is not available' : displayedSource !== 'dynamic' ? 'Switch to dynamic source' : null"
153
153
  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
154
  :class="[
155
- displayedSource !== 'dynamic' ? 'max-w-6 opacity-70' : 'max-w-[82px] text-primary-600 dark:text-primary-400',
155
+ displayedSource !== 'dynamic' ? 'max-w-6 opacity-70' : 'max-w-[82px] text-primary-800 dark:text-primary-400',
156
156
  dynamicSourceCode ? 'cursor-pointer hover:bg-gray-500/30 active:bg-gray-600/50' : 'opacity-50',
157
157
  ]"
158
158
  @click="dynamicSourceCode && (displayedSource = 'dynamic')"
@@ -174,7 +174,7 @@ watch(sourceHtml, async () => {
174
174
  v-tooltip="!staticSourceCode ? 'Static source code is not available' : displayedSource !== 'static' ? 'Switch to static source' : null"
175
175
  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
176
  :class="[
177
- displayedSource !== 'static' ? 'max-w-6 opacity-70' : 'max-w-[63px] text-primary-600 dark:text-primary-400',
177
+ displayedSource !== 'static' ? 'max-w-6 opacity-70' : 'max-w-[63px] text-primary-800 dark:text-primary-400',
178
178
  staticSourceCode ? 'cursor-pointer hover:bg-gray-500/30 active:bg-gray-600/50' : 'opacity-50',
179
179
  ]"
180
180
  @click="staticSourceCode && (displayedSource = 'static')"
@@ -3,6 +3,7 @@ import { Icon } from '@iconify/vue'
3
3
  import { computed } from 'vue'
4
4
  import { useStoryStore } from '../../stores/story'
5
5
  import { isMobile } from '../../util/responsive'
6
+ import { autoSelectsVariant } from '../../util/variant'
6
7
  import BaseSplitPane from '../base/BaseSplitPane.vue'
7
8
  import StoryVariantListItem from './StoryVariantListItem.vue'
8
9
  import StoryVariantSingleView from './StoryVariantSingleView.vue'
@@ -15,87 +16,84 @@ const storyStore = useStoryStore()
15
16
 
16
17
  const hasSingleVariant = computed(() => (storyStore.currentStory?.variants.length === 1))
17
18
 
18
- const variant = computed(() => storyStore.currentVariant)
19
+ const variant = computed(() => storyStore.currentVariant ?? null)
20
+
21
+ // The preview area exists while a variant is showing or on its way. Dropping it
22
+ // the moment the variant goes null would unmount the preview across the two-step
23
+ // route change, which is the whole thing #328 is about; keeping it when no
24
+ // variant is coming leaves an empty box and, on mobile, a divider under the
25
+ // picker. Same predicate the view below releases its held pair on.
26
+ const showsPreview = computed(() => !!variant.value || autoSelectsVariant(storyStore.currentStory))
19
27
  </script>
20
28
 
21
29
  <template>
22
- <div
23
- v-if="hasSingleVariant && variant"
24
- class="poveste-story-variant-single p-2 h-full __poveste-pane-shadow-from-right"
30
+ <!--
31
+ One split pane for every layout. The variant list, the mobile picker row and
32
+ the bare single-variant view used to be three branches each carrying their
33
+ own `StoryVariantSingleView`, so moving between them rebuilt the preview and
34
+ cold-booted its sandbox. `isMobile` is a live media query, so that happened
35
+ on a window resize (#600); the single/many-variant half was #328.
36
+ -->
37
+ <BaseSplitPane
38
+ save-id="story-single-main-split"
39
+ :min="5"
40
+ :max="40"
41
+ :default-split="17"
42
+ :show-first="!isMobile && !hasSingleVariant"
25
43
  >
26
- <StoryVariantSingleView
27
- :variant="variant"
28
- :story="storyStore.currentStory"
29
- />
30
- </div>
31
- <template v-else>
32
- <div
33
- v-if="isMobile"
34
- class="divide-y divide-gray-100 dark:divide-gray-800 h-full flex flex-col"
35
- >
36
- <a
37
- class="px-6 h-12 hover:text-primary-500 dark:hover:text-primary-400 cursor-pointer flex gap-2 flex-wrap w-full items-center flex-none"
38
- @click="$emit('openVariantMenu')"
39
- >
40
- <template v-if="variant">
41
- <Icon
42
- :icon="variant.icon ?? 'carbon:cube'"
43
- class="w-5 h-5 flex-none"
44
- :class="{
45
- 'text-gray-500': !variant.iconColor,
46
- 'bind-icon-color': variant.iconColor,
47
- }"
48
- />
49
- {{ variant.title }}
50
- </template>
51
- <template v-else>
52
- Select a variant...
53
- </template>
54
-
55
- <Icon
56
- icon="carbon:chevron-sort"
57
- class="w-5 h-5 shrink-0 ml-auto"
44
+ <template #first>
45
+ <div class="h-full overflow-y-auto">
46
+ <StoryVariantListItem
47
+ v-for="(v, index) of storyStore.currentStory?.variants ?? []"
48
+ :key="index"
49
+ :variant="v"
58
50
  />
59
- </a>
51
+ </div>
52
+ </template>
53
+ <template #last>
60
54
  <div
61
- v-if="storyStore.currentVariant"
62
- class="p-2 h-full"
55
+ class="h-full flex flex-col"
56
+ :class="{ 'divide-y divide-gray-100 dark:divide-gray-800': isMobile }"
63
57
  >
64
- <StoryVariantSingleView
65
- :variant="storyStore.currentVariant"
66
- :story="storyStore.currentStory"
67
- />
68
- </div>
69
- </div>
70
- <BaseSplitPane
71
- v-else
72
- save-id="story-single-main-split"
73
- :min="5"
74
- :max="40"
75
- :default-split="17"
76
- >
77
- <template #first>
78
- <div class="h-full overflow-y-auto">
79
- <StoryVariantListItem
80
- v-for="(v, index) of storyStore.currentStory.variants"
81
- :key="index"
82
- :variant="v"
58
+ <!-- Mobile has no room for the list beside the preview, so the variant
59
+ it is showing opens a full-screen overlay instead. -->
60
+ <a
61
+ v-if="isMobile && (!hasSingleVariant || !variant)"
62
+ class="px-6 h-12 hover:text-primary-500 dark:hover:text-primary-400 cursor-pointer flex gap-2 flex-wrap w-full items-center flex-none"
63
+ @click="$emit('openVariantMenu')"
64
+ >
65
+ <template v-if="variant">
66
+ <Icon
67
+ :icon="variant.icon ?? 'carbon:cube'"
68
+ class="w-5 h-5 flex-none"
69
+ :class="{
70
+ 'text-gray-500': !variant.iconColor,
71
+ 'bind-icon-color': variant.iconColor,
72
+ }"
73
+ />
74
+ {{ variant.title }}
75
+ </template>
76
+ <template v-else>
77
+ Select a variant...
78
+ </template>
79
+
80
+ <Icon
81
+ icon="carbon:chevron-sort"
82
+ class="w-5 h-5 shrink-0 ml-auto"
83
83
  />
84
- </div>
85
- </template>
86
- <template #last>
84
+ </a>
87
85
  <div
88
- v-if="storyStore.currentVariant"
89
- class="p-2 h-full __poveste-pane-shadow-from-right"
86
+ v-if="showsPreview"
87
+ class="poveste-story-variant-single p-2 h-full __poveste-pane-shadow-from-right"
90
88
  >
91
89
  <StoryVariantSingleView
92
- :variant="storyStore.currentVariant"
90
+ :variant="variant"
93
91
  :story="storyStore.currentStory"
94
92
  />
95
93
  </div>
96
- </template>
97
- </BaseSplitPane>
98
- </template>
94
+ </div>
95
+ </template>
96
+ </BaseSplitPane>
99
97
  </template>
100
98
 
101
99
  <style scoped>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import type { Story, Variant } from '../../types'
3
- import { computed } from 'vue'
3
+ import { computed, watch } from 'vue'
4
4
  import { usePreviewSettingsStore } from '../../stores/preview-settings'
5
5
  import { useStoryStore } from '../../stores/story'
6
6
  import { previewDarkClasses, usePreviewDark } from '../../util/color-scheme'
@@ -16,7 +16,18 @@ const props = defineProps<{
16
16
 
17
17
  const storyStore = useStoryStore()
18
18
 
19
- storyStore.setPreviewReady(props.variant, false)
19
+ // This component now outlives a story change (#328), so the flag is cleared per
20
+ // variant rather than once at setup. Both ends, as `StoryVariantGridItem` does
21
+ // for the same reason: the one arriving has not rendered yet, and the one
22
+ // leaving is no longer rendered anywhere.
23
+ watch(() => props.variant, (variant, previous) => {
24
+ if (previous) {
25
+ storyStore.setPreviewReady(previous, false)
26
+ }
27
+ storyStore.setPreviewReady(variant, false)
28
+ }, {
29
+ immediate: true,
30
+ })
20
31
 
21
32
  function onReady() {
22
33
  storyStore.setPreviewReady(props.variant, true)
@@ -110,6 +110,7 @@ useEventListener(window, 'message', (event) => {
110
110
  updateVariantState(event.data.state)
111
111
  break
112
112
  case EVENT_SEND:
113
+ if (!fromCurrentOccupant(event.data)) break
113
114
  logEvent(event.data.event)
114
115
  break
115
116
  case SANDBOX_READY:
@@ -198,6 +199,9 @@ const isIframeLoaded = ref(false)
198
199
  let retargets = 0
199
200
  // The story whose document the realm holds — booted or retargeted into it.
200
201
  let servedStory = props.story
202
+ // Its variant, kept so the one being replaced can have its `previewReady`
203
+ // cleared: it is no longer rendered anywhere once the realm moves on.
204
+ let servedVariant = props.variant
201
205
 
202
206
  let stopTrackKeyboard: (() => void) | undefined
203
207
  let unmounted = false
@@ -222,6 +226,10 @@ watch(sandboxUrl, (url) => {
222
226
  // A new occupant has agreed to nothing, so neither has this end. Kept as a
223
227
  // new bridge rather than a reset so there is one way to be in that state.
224
228
  bridge = createStateBridge(postState)
229
+ if (servedVariant && servedVariant !== props.variant) {
230
+ storyStore.setPreviewReady(servedVariant, false)
231
+ }
232
+ servedVariant = props.variant
225
233
  storyStore.setPreviewReady(props.variant, false)
226
234
 
227
235
  const reuse = props.story.layout?.isolate !== true && servedStory.layout?.isolate !== true
@@ -1,28 +1,74 @@
1
1
  <script lang="ts" setup>
2
2
  import type { Story, Variant } from '../../types'
3
+ import { shallowRef, watchEffect } from 'vue'
4
+ import { autoSelectsVariant } from '../../util/variant'
3
5
  import StoryVariantSinglePreviewNative from './StoryVariantSinglePreviewNative.vue'
4
6
  import StoryVariantSinglePreviewRemote from './StoryVariantSinglePreviewRemote.vue'
5
7
 
6
- defineProps<{
7
- variant: Variant
8
- story: Story
8
+ const props = defineProps<{
9
+ story: Story | undefined
10
+ variant: Variant | null
9
11
  }>()
12
+
13
+ /*
14
+ * The pair the preview renders, which is not always the pair the route names.
15
+ *
16
+ * Opening a story is two route changes — the story, then the variant
17
+ * `StoryView` picks for it — and `currentVariant` is null in between. The
18
+ * preview used to be mounted under a `v-if` on that variant, so every sidebar
19
+ * click destroyed it and rebuilt it, and the realm reuse in the preview
20
+ * components below was never reached: the component was gone before its
21
+ * `watch(sandboxUrl)` could run (#328).
22
+ *
23
+ * Holding the last pair keeps one instance across the gap. The pair rather than
24
+ * the variant alone because the story changes first, and a new story with the
25
+ * previous story's variant resolves to a sandbox URL that names neither.
26
+ *
27
+ * The hold is released when the new story is not going to select a variant at
28
+ * all, because then there is no gap to bridge — only a reader looking at a
29
+ * variant list. Holding there would leave the story they left running behind a
30
+ * hidden iframe for as long as they stay, timers and all.
31
+ */
32
+ const shown = shallowRef<{ story: Story, variant: Variant } | null>(null)
33
+
34
+ watchEffect(() => {
35
+ // Read outside the branch: while `variant` is null this effect would
36
+ // otherwise not depend on `story`, and an HMR update that replaces the story
37
+ // object would leave the pair below pointing at the discarded one.
38
+ const story = props.story
39
+
40
+ if (story && props.variant) {
41
+ shown.value = { story, variant: props.variant }
42
+ }
43
+ else if (shown.value && shown.value.story !== story && !autoSelectsVariant(story)) {
44
+ shown.value = null
45
+ }
46
+ })
10
47
  </script>
11
48
 
12
49
  <template>
13
50
  <div
14
51
  class="poveste-story-variant-single-view h-full flex flex-col"
52
+ :class="{ invisible: !variant }"
15
53
  data-testid="story-variant-single-view"
16
54
  >
17
- <StoryVariantSinglePreviewNative
18
- v-if="story.layout?.iframe === false"
19
- :story="story"
20
- :variant="variant"
21
- />
22
- <StoryVariantSinglePreviewRemote
23
- v-else
24
- :story="story"
25
- :variant="variant"
26
- />
55
+ <!--
56
+ Hidden rather than unmounted across the gap. What it holds then is the
57
+ story the reader has just left, and showing that is the trap the block
58
+ above is about: the preview is only correct once the route names a
59
+ variant again.
60
+ -->
61
+ <template v-if="shown">
62
+ <StoryVariantSinglePreviewNative
63
+ v-if="shown.story.layout?.type === 'single' && shown.story.layout.iframe === false"
64
+ :story="shown.story"
65
+ :variant="shown.variant"
66
+ />
67
+ <StoryVariantSinglePreviewRemote
68
+ v-else
69
+ :story="shown.story"
70
+ :variant="shown.variant"
71
+ />
72
+ </template>
27
73
  </div>
28
74
  </template>
@@ -4,8 +4,9 @@ import { computed, ref, watch } from 'vue'
4
4
  import { useRoute, useRouter } from 'vue-router'
5
5
  import { useLayoutStore } from '../../stores/layout'
6
6
  import { useStoryStore } from '../../stores/story'
7
-
8
7
  import { isMobile } from '../../util/responsive'
8
+
9
+ import { autoSelectsVariant } from '../../util/variant'
9
10
  import BaseEmpty from '../base/BaseEmpty.vue'
10
11
  import BaseSplitPane from '../base/BaseSplitPane.vue'
11
12
  import StoryDocs from '../panel/StoryDocs.vue'
@@ -15,8 +16,11 @@ import StoryViewer from './StoryViewer.vue'
15
16
  const storyStore = useStoryStore()
16
17
  const layoutStore = useLayoutStore()
17
18
 
19
+ // Coerced: a settings object stored before a key existed leaves it `undefined`,
20
+ // which the pane's Boolean prop would read as its `true` default — the inverse
21
+ // of what the branch this replaced did with the same value (#596).
18
22
  const effectiveStoryOptionsVisible = computed(() =>
19
- storyStore.currentStory?.meta?.storyOptions ?? layoutStore.settings.storyOptionsVisible,
23
+ !!(storyStore.currentStory?.meta?.storyOptions ?? layoutStore.settings.storyOptionsVisible),
20
24
  )
21
25
 
22
26
  const placement = computed(() => layoutStore.settings.storyOptionsPlacement)
@@ -37,15 +41,9 @@ watch(() => storyStore.currentVariant, (value) => {
37
41
  })
38
42
 
39
43
  watch(() => [storyStore.currentStory, storyStore.currentVariant], () => {
40
- if (!storyStore.currentVariant) {
41
- if (storyStore.currentStory?.lastSelectedVariant) {
42
- setVariant(storyStore.currentStory.lastSelectedVariant.id)
43
- return
44
- }
45
-
46
- if (storyStore.currentStory?.variants.length === 1) {
47
- setVariant(storyStore.currentStory.variants[0].id)
48
- }
44
+ if (!storyStore.currentVariant && autoSelectsVariant(storyStore.currentStory)) {
45
+ const variant = storyStore.currentStory.lastSelectedVariant ?? storyStore.currentStory.variants[0]
46
+ setVariant(variant.id)
49
47
  }
50
48
  }, {
51
49
  immediate: true,
@@ -97,12 +95,13 @@ function scrollDocsToTop() {
97
95
  @scroll-top="scrollDocsToTop()"
98
96
  />
99
97
  </div>
100
- <template v-else-if="isMobile">
101
- <StoryViewer />
102
- </template>
103
- <template v-else-if="!effectiveStoryOptionsVisible">
104
- <StoryViewer />
105
- </template>
98
+ <!--
99
+ One split pane whether or not the options pane is showing, and whether or
100
+ not the viewport is mobile — both used to render `StoryViewer` from a
101
+ different branch, which moved it in the tree and cold-booted the sandbox
102
+ under it. `isMobile` is a live media query, so that one fired on a resize
103
+ (#596).
104
+ -->
106
105
  <BaseSplitPane
107
106
  v-else
108
107
  :save-id="`story-main-${placement}`"
@@ -111,6 +110,7 @@ function scrollDocsToTop() {
111
110
  :max="95"
112
111
  :default-split="splitDefaultSplit"
113
112
  :show-divider="false"
113
+ :show-last="!isMobile && effectiveStoryOptionsVisible"
114
114
  class="h-full"
115
115
  >
116
116
  <template #first>
@@ -109,10 +109,3 @@ const showDropdown = showColorScheme || !!povesteConfig.backgroundPresets.length
109
109
  </template>
110
110
  </VDropdown>
111
111
  </template>
112
-
113
- <style scoped>
114
- .bind-preview-bg {
115
- background-color: v-bind('settings.backgroundColor');
116
- color: v-bind('contrastColor');
117
- }
118
- </style>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" setup>
2
- import type { Story, TreeGroup } from '../../types'
2
+ import type { Story, TreeFolder, TreeGroup, TreeLeaf } from '../../types'
3
3
  import { Icon } from '@iconify/vue'
4
4
  import { computed } from 'vue'
5
5
  import { useFolderStore } from '../../stores/folder'
@@ -3,7 +3,7 @@ import { reportStoryError } from '@poveste/shared'
3
3
  import { usePreferredDark } from '@vueuse/core'
4
4
  import { createPinia } from 'pinia'
5
5
  import { files, onUpdate } from 'virtual:$poveste-stories'
6
- import { computed, createApp, h, onMounted, ref, watch } from 'vue'
6
+ import { computed, createApp, h, onMounted, ref, watch, watchEffect } from 'vue'
7
7
  import { parseQuery } from 'vue-router'
8
8
  import GenericMountStory from './components/story/GenericMountStory.vue'
9
9
  import GenericRenderStory from './components/story/GenericRenderStory.vue'
@@ -11,6 +11,7 @@ import { previewDarkClasses, resolvePreviewDark } from './util/color-scheme.js'
11
11
  import { PREVIEW_SETTINGS_REQUEST, PREVIEW_SETTINGS_SYNC, SANDBOX_HEIGHT, SANDBOX_READY, SANDBOX_RETARGET, STATE_SYNC } from './util/const.js'
12
12
  import { isDark } from './util/dark.js'
13
13
  import { mapFile } from './util/mapping'
14
+ import { occupant } from './util/occupant.js'
14
15
  import { applyPreviewSettings, loadStoredPreviewSettings, receivedSettings } from './util/preview-settings.js'
15
16
  import { createStateBridge } from './util/state-bridge.js'
16
17
  import { toRawDeep } from './util/state.js'
@@ -24,6 +25,13 @@ const query = parseQuery(window.location.search)
24
25
  const storyId = ref(typeof query.storyId === 'string' ? query.storyId : null)
25
26
  const variantId = ref(typeof query.variantId === 'string' ? query.variantId : null)
26
27
 
28
+ // Kept in step so `logEvent` can name the occupant it is speaking for; it posts
29
+ // from the story's own code and cannot see these refs.
30
+ watchEffect(() => {
31
+ occupant.storyId = storyId.value
32
+ occupant.variantId = variantId.value
33
+ })
34
+
27
35
  // `files` is `[]` until the dev server's first collect lands. The app's iframe
28
36
  // never sees that — it mounts after — but a sandbox opened on its own does, and
29
37
  // the list only arrives through `onUpdate`.
@@ -4,6 +4,10 @@
4
4
  * in each book. Copied rather than imported because the package pairs the two
5
5
  * formats in every `src`; `font.spec.ts` fails if a bump moves the subsets.
6
6
  *
7
+ * Vendored rather than resolved from the package, which publishes the family
8
+ * whole: 5.9 MB installed to ship 108 KB (#306). OFL-1.1, so `fonts/LICENSE`
9
+ * travels with the files.
10
+ *
7
11
  * Regenerate: node packages/poveste-app/scripts/font.mjs
8
12
  */
9
13
 
@@ -13,7 +17,7 @@
13
17
  font-style: normal;
14
18
  font-display: swap;
15
19
  font-weight: 400;
16
- src: url('@fontsource/noto-sans-display/files/noto-sans-display-cyrillic-ext-400-normal.woff2') format('woff2');
20
+ src: url('./fonts/noto-sans-display-cyrillic-ext-400-normal.woff2') format('woff2');
17
21
  unicode-range: U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F;
18
22
  }
19
23
 
@@ -23,7 +27,7 @@
23
27
  font-style: normal;
24
28
  font-display: swap;
25
29
  font-weight: 400;
26
- src: url('@fontsource/noto-sans-display/files/noto-sans-display-cyrillic-400-normal.woff2') format('woff2');
30
+ src: url('./fonts/noto-sans-display-cyrillic-400-normal.woff2') format('woff2');
27
31
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
28
32
  }
29
33
 
@@ -33,7 +37,7 @@
33
37
  font-style: normal;
34
38
  font-display: swap;
35
39
  font-weight: 400;
36
- src: url('@fontsource/noto-sans-display/files/noto-sans-display-greek-ext-400-normal.woff2') format('woff2');
40
+ src: url('./fonts/noto-sans-display-greek-ext-400-normal.woff2') format('woff2');
37
41
  unicode-range: U+1F00-1FFF;
38
42
  }
39
43
 
@@ -43,7 +47,7 @@
43
47
  font-style: normal;
44
48
  font-display: swap;
45
49
  font-weight: 400;
46
- src: url('@fontsource/noto-sans-display/files/noto-sans-display-greek-400-normal.woff2') format('woff2');
50
+ src: url('./fonts/noto-sans-display-greek-400-normal.woff2') format('woff2');
47
51
  unicode-range: U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF;
48
52
  }
49
53
 
@@ -53,7 +57,7 @@
53
57
  font-style: normal;
54
58
  font-display: swap;
55
59
  font-weight: 400;
56
- src: url('@fontsource/noto-sans-display/files/noto-sans-display-vietnamese-400-normal.woff2') format('woff2');
60
+ src: url('./fonts/noto-sans-display-vietnamese-400-normal.woff2') format('woff2');
57
61
  unicode-range: U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB;
58
62
  }
59
63
 
@@ -63,7 +67,7 @@
63
67
  font-style: normal;
64
68
  font-display: swap;
65
69
  font-weight: 400;
66
- src: url('@fontsource/noto-sans-display/files/noto-sans-display-latin-ext-400-normal.woff2') format('woff2');
70
+ src: url('./fonts/noto-sans-display-latin-ext-400-normal.woff2') format('woff2');
67
71
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
68
72
  }
69
73
 
@@ -73,6 +77,6 @@
73
77
  font-style: normal;
74
78
  font-display: swap;
75
79
  font-weight: 400;
76
- src: url('@fontsource/noto-sans-display/files/noto-sans-display-latin-400-normal.woff2') format('woff2');
80
+ src: url('./fonts/noto-sans-display-latin-400-normal.woff2') format('woff2');
77
81
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
78
82
  }
@@ -0,0 +1,93 @@
1
+ Copyright 2015 Google Inc. All Rights Reserved. NotoSansDisplay-Italic[wdth,wght].ttf: Copyright 2015 Google Inc. All Rights Reserved.
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.