@poveste/app 0.19.1 → 0.21.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 (58) hide show
  1. package/dist/bundled/app.css +208 -7
  2. package/dist/bundled/components/HomeView.vue_vue_type_script_setup_true_lang.js +3 -3
  3. package/dist/bundled/components/app/AppLogo.vue_vue_type_script_setup_true_lang.js +4 -4
  4. package/dist/bundled/components/app/TopBarChipItem.js +10 -6
  5. package/dist/bundled/components/layout/LayoutItem.js +1 -0
  6. package/dist/bundled/components/layout/LayoutItem.vue_vue_type_script_setup_true_lang.js +20 -23
  7. package/dist/bundled/components/layout/LayoutModal.vue_vue_type_script_setup_true_lang.js +44 -5
  8. package/dist/bundled/components/panel/ComponentDocs.js +7 -0
  9. package/dist/bundled/components/panel/ComponentDocs.vue_vue_type_script_setup_true_lang.js +164 -0
  10. package/dist/bundled/components/panel/StoryDocs.vue_vue_type_script_setup_true_lang.js +57 -22
  11. package/dist/bundled/components/panel/StorySourceCode.js +1 -1
  12. package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +22 -6
  13. package/dist/bundled/components/story/GenericMountStory.vue_vue_type_script_setup_true_lang.js +3 -7
  14. package/dist/bundled/components/story/GenericRenderStory.vue_vue_type_script_setup_true_lang.js +3 -10
  15. package/dist/bundled/components/story/StoryResponsivePreview.js +1 -1
  16. package/dist/bundled/components/story/StoryResponsivePreview.vue_vue_type_script_setup_true_lang.js +5 -3
  17. package/dist/bundled/components/story/StoryVariantGridItem.js +1 -1
  18. package/dist/bundled/components/story/StoryVariantGridItem.vue_vue_type_script_setup_true_lang.js +5 -3
  19. package/dist/bundled/components/story/StoryVariantSinglePreviewRemote.vue_vue_type_script_setup_true_lang.js +3 -2
  20. package/dist/bundled/components/toolbar/DevOnlyToolbarOpenInEditor.vue_vue_type_script_setup_true_lang.js +0 -1
  21. package/dist/bundled/index.js +5 -1
  22. package/dist/bundled/sandbox.js +2 -0
  23. package/dist/bundled/util/client-plugin.js +22 -0
  24. package/dist/bundled/util/component-docs-data.js +5 -0
  25. package/dist/bundled/util/component-docs.js +133 -0
  26. package/dist/bundled/util/dark.js +3 -1
  27. package/dist/bundled/util/docs.js +13 -3
  28. package/dist/bundled/util/highlighter.js +1 -1
  29. package/dist/bundled/util/icons.generated.js +3 -2
  30. package/dist/bundled/util/keyboard.js +5 -4
  31. package/dist/bundled/util/mapping.js +1 -5
  32. package/dist/style.css +321 -195
  33. package/package.json +17 -17
  34. package/src/app/components/HomeView.vue +2 -2
  35. package/src/app/components/app/AppLogo.vue +4 -6
  36. package/src/app/components/app/TopBarChipItem.vue +55 -6
  37. package/src/app/components/layout/LayoutItem.vue +56 -6
  38. package/src/app/components/layout/LayoutModal.vue +55 -2
  39. package/src/app/components/panel/ComponentDocs.vue +323 -0
  40. package/src/app/components/panel/StoryDocs.vue +38 -2
  41. package/src/app/components/panel/StorySourceCode.vue +29 -5
  42. package/src/app/components/story/GenericMountStory.vue +3 -5
  43. package/src/app/components/story/GenericRenderStory.vue +4 -12
  44. package/src/app/components/story/StoryResponsivePreview.vue +4 -1
  45. package/src/app/components/story/StoryVariantGridItem.vue +4 -1
  46. package/src/app/components/story/StoryVariantSinglePreviewRemote.vue +6 -4
  47. package/src/app/components/toolbar/DevOnlyToolbarOpenInEditor.vue +0 -1
  48. package/src/app/index.ts +12 -3
  49. package/src/app/sandbox.ts +5 -0
  50. package/src/app/util/client-plugin.ts +33 -0
  51. package/src/app/util/component-docs-data.ts +6 -0
  52. package/src/app/util/component-docs.ts +169 -0
  53. package/src/app/util/dark.ts +6 -0
  54. package/src/app/util/docs.ts +18 -2
  55. package/src/app/util/highlighter.ts +2 -1
  56. package/src/app/util/icons.generated.ts +2 -2
  57. package/src/app/util/keyboard.ts +8 -4
  58. package/src/app/util/mapping.ts +1 -5
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@poveste/app",
3
3
  "type": "module",
4
- "version": "0.19.1",
4
+ "version": "0.21.0",
5
5
  "description": "Pre-bundled UI for Poveste",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan",
@@ -36,16 +36,16 @@
36
36
  "node": ">=22.22.2"
37
37
  },
38
38
  "dependencies": {
39
- "@poveste/controls": "^0.19.1",
40
- "@poveste/shared": "^0.19.1",
41
- "@poveste/vendors": "^0.19.1",
39
+ "@poveste/controls": "^0.21.0",
40
+ "@poveste/shared": "^0.21.0",
41
+ "@poveste/vendors": "^0.21.0",
42
42
  "fuse.js": "^7.0.0",
43
- "shiki": "^4.4.3"
43
+ "shiki": "^4.5.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@fontsource/noto-sans-display": "^5.3.0",
47
- "@iconify-json/carbon": "^1.2.27",
48
- "@iconify-json/fluent": "1.2.58",
47
+ "@iconify-json/carbon": "^1.2.28",
48
+ "@iconify-json/fluent": "1.2.60",
49
49
  "@iconify-json/mdi": "^1.2.3",
50
50
  "@iconify-json/ri": "^1.2.10",
51
51
  "@iconify/utils": "^3.1.7",
@@ -53,24 +53,24 @@
53
53
  "@tailwindcss/postcss": "^4.3.3",
54
54
  "@tailwindcss/typography": "^0.5.20",
55
55
  "@types/fs-extra": "^11.0.4",
56
- "@types/node": "^26.6.3",
57
- "@vitejs/plugin-vue": "^6.0.9",
56
+ "@types/node": "^26.6.5",
57
+ "@vitejs/plugin-vue": "^6.0.10",
58
58
  "@vueuse/core": "^15.0.0",
59
59
  "chokidar": "^5.0.0",
60
- "concurrently": "^10.0.5",
60
+ "concurrently": "^10.0.6",
61
61
  "fs-extra": "^11.4.1",
62
62
  "globby": "^16.2.4",
63
63
  "pathe": "^2.0.3",
64
- "pinia": "^4.0.3",
65
- "postcss": "^8.5.23",
64
+ "pinia": "^4.1.0",
65
+ "postcss": "^8.5.31",
66
66
  "postcss-cli": "^12.0.0",
67
67
  "tailwindcss": "^4.3.3",
68
68
  "typescript": "6.0.3",
69
- "vite": "^8.3.1",
70
- "vitest": "^5.0.2",
71
- "vue": "^3.5.43",
72
- "vue-router": "^5.3.1",
73
- "vue-tsc": "^3.3.11"
69
+ "vite": "^8.3.4",
70
+ "vitest": "^5.0.5",
71
+ "vue": "^3.5.44",
72
+ "vue-router": "^5.4.0",
73
+ "vue-tsc": "^3.3.13"
74
74
  },
75
75
  "scripts": {
76
76
  "build": "pnpm run icons && rimraf dist && vite build && pnpm run build:css && node ./scripts/copy.mjs",
@@ -2,10 +2,10 @@
2
2
  import { computed } from 'vue'
3
3
  import PovesteLogo from '../assets/poveste.svg'
4
4
  import { useStoryStore } from '../stores/story'
5
- import { customLogos, povesteConfig } from '../util/config'
5
+ import { customLogos } from '../util/config'
6
6
  import HomeCounter from './app/HomeCounter.vue'
7
7
 
8
- const logoUrl = computed(() => povesteConfig.theme?.logo?.square ? customLogos?.square ?? PovesteLogo : PovesteLogo)
8
+ const logoUrl = computed(() => customLogos?.square ?? PovesteLogo)
9
9
  const storyStore = useStoryStore()
10
10
 
11
11
  const stats = computed(() => {
@@ -6,15 +6,13 @@ import PovesteLogoLight from '../../assets/poveste-text.svg'
6
6
  import { customLogos, povesteConfig } from '../../util/config'
7
7
  import { isDark } from '../../util/dark'
8
8
 
9
- // `theme.logo` is what the config asked for; `customLogos` is the resolved URL
10
- // map from the virtual module. Two objects, so a check on one narrows nothing
11
- // about the other — and falling back to the bundled logo beats rendering an
12
- // `<img>` with no `src` if they ever disagree.
9
+ // `customLogos` holds a served URL for each logo the config names. The config's
10
+ // own entries are paths on the machine that built the book, and are not sent (#1383).
13
11
  const logoUrl = computed(() => {
14
12
  if (isDark.value) {
15
- return povesteConfig.theme.logo?.dark ? customLogos?.dark ?? PovesteLogoDark : PovesteLogoDark
13
+ return customLogos?.dark ?? PovesteLogoDark
16
14
  }
17
- return povesteConfig.theme.logo?.light ? customLogos?.light ?? PovesteLogoLight : PovesteLogoLight
15
+ return customLogos?.light ?? PovesteLogoLight
18
16
  })
19
17
 
20
18
  const altText = computed(() => `${povesteConfig.theme.title} logo`)
@@ -1,10 +1,59 @@
1
1
  <template>
2
- <!-- Without an href this is not a link, so `aria-label` on it is prohibited and
3
- it has no accessible role. Chip items are used both ways (#310). -->
4
- <a
5
- :role="$attrs['href'] ? undefined : 'button'"
6
- class="poveste-top-bar-chip-item flex items-center gap-1 px-2.5 py-1.5 hover:bg-white/50 active:bg-white/70 dark:hover:bg-white/10 dark:active:bg-white/15 hover:text-primary-500 dark:hover:text-primary-400 cursor-pointer text-gray-900 dark:text-gray-100 transition-colors"
2
+ <!-- A link where it navigates, a button everywhere else. An `<a>` without an
3
+ href is not focusable, so a keyboard could not reach the chip (#1351). -->
4
+ <component
5
+ :is="$attrs['href'] ? 'a' : 'button'"
6
+ :type="$attrs['href'] ? undefined : 'button'"
7
+ class="poveste-top-bar-chip-item"
8
+ data-slot="top-bar-chip-item"
7
9
  >
8
10
  <slot />
9
- </a>
11
+ </component>
10
12
  </template>
13
+
14
+ <style lang="postcss">
15
+ .poveste-top-bar-chip-item {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: .25rem;
19
+ padding: .375rem .625rem;
20
+ border: 0;
21
+ font: inherit;
22
+ color: var(--color-gray-900);
23
+ cursor: pointer;
24
+
25
+ @media (prefers-reduced-motion: no-preference) {
26
+ transition: color .15s ease-out, background-color .15s ease-out;
27
+ }
28
+
29
+ &:hover {
30
+ background-color: rgb(255 255 255 / .5);
31
+ color: var(--color-primary-500);
32
+ }
33
+
34
+ &:active {
35
+ background-color: rgb(255 255 255 / .7);
36
+ }
37
+
38
+ /* Inset: the chip clips its items to its rounded border. */
39
+ &:focus-visible {
40
+ outline: 2px solid var(--color-primary-500);
41
+ outline-offset: -2px;
42
+ }
43
+
44
+ /* Spelled out on the subject: `.ptw-dark` sits above the `@scope` root, so a
45
+ descendant rule keyed on it never matches from in here (#101). */
46
+ &:where(.ptw-dark, .ptw-dark *) {
47
+ color: var(--color-gray-100);
48
+
49
+ &:hover {
50
+ background-color: rgb(255 255 255 / .1);
51
+ color: var(--color-primary-400);
52
+ }
53
+
54
+ &:active {
55
+ background-color: rgb(255 255 255 / .15);
56
+ }
57
+ }
58
+ }
59
+ </style>
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import { Icon } from '@iconify/vue'
3
- import BaseListItem from '../base/BaseListItem.vue'
4
3
 
5
4
  defineProps<{
6
5
  title: string
@@ -15,10 +14,14 @@ defineEmits<{
15
14
  </script>
16
15
 
17
16
  <template>
18
- <BaseListItem
19
- :is-active="isActive"
20
- class="px-6 py-4 gap-4"
21
- @navigate="$emit('click')"
17
+ <!-- A button: `BaseListItem` is an `<a>` with no href, which a keyboard cannot
18
+ reach (#1351). -->
19
+ <button
20
+ type="button"
21
+ class="poveste-layout-item"
22
+ :class="{ active: isActive }"
23
+ data-slot="layout-item"
24
+ @click="$emit('click')"
22
25
  >
23
26
  <Icon
24
27
  class="w-4 h-4 flex-none"
@@ -32,5 +35,52 @@ defineEmits<{
32
35
  :icon="stateIcon"
33
36
  aria-hidden="true"
34
37
  />
35
- </BaseListItem>
38
+ </button>
36
39
  </template>
40
+
41
+ <style lang="postcss">
42
+ .poveste-layout-item {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 1rem;
46
+ width: 100%;
47
+ padding: 1rem 1.5rem;
48
+ border: 0;
49
+ font: inherit;
50
+ text-align: start;
51
+ color: var(--color-gray-900);
52
+ cursor: pointer;
53
+
54
+ &:hover {
55
+ background-color: var(--color-primary-100);
56
+ }
57
+
58
+ &:focus-visible {
59
+ outline: 2px solid var(--color-primary-500);
60
+ outline-offset: -2px;
61
+ }
62
+
63
+ &.active {
64
+ background-color: var(--color-primary-500);
65
+ color: var(--color-white);
66
+
67
+ &:hover {
68
+ background-color: var(--color-primary-600);
69
+ }
70
+ }
71
+
72
+ /* Spelled out on the subject: `.ptw-dark` sits above the `@scope` root, so a
73
+ descendant rule keyed on it never matches from in here (#101). */
74
+ &:where(.ptw-dark, .ptw-dark *) {
75
+ color: var(--color-gray-100);
76
+
77
+ &:hover {
78
+ background-color: var(--color-primary-900);
79
+ }
80
+
81
+ &.active {
82
+ color: var(--color-black);
83
+ }
84
+ }
85
+ }
86
+ </style>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { Icon } from '@iconify/vue'
3
+ import { nextTick, ref, watch } from 'vue'
3
4
  import { onKeyboardShortcut } from '../../util/keyboard'
4
5
  import LayoutPane from './LayoutPane.vue'
5
6
 
@@ -23,6 +24,48 @@ onKeyboardShortcut(['escape'], () => {
23
24
  close()
24
25
  }
25
26
  })
27
+
28
+ // A dialog to the keyboard as well: focus moves in when it opens, Tab stays inside,
29
+ // and closing hands focus back to whatever opened it, the layout chip or the page
30
+ // behind a shortcut. Focus used to stay on the chip, behind the pane (#1372).
31
+ const panel = ref<HTMLElement>()
32
+ let opener: HTMLElement | null = null
33
+
34
+ const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
35
+
36
+ function focusables() {
37
+ return [...panel.value?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []]
38
+ .filter(element => !element.hasAttribute('disabled') && element.getClientRects().length > 0)
39
+ }
40
+
41
+ watch(() => props.shown, async (shown) => {
42
+ if (shown) {
43
+ opener = document.activeElement instanceof HTMLElement ? document.activeElement : null
44
+ await nextTick()
45
+ focusables()[0]?.focus()
46
+ }
47
+ else if (opener?.isConnected) {
48
+ opener.focus()
49
+ opener = null
50
+ }
51
+ })
52
+
53
+ function keepFocusInside(event: KeyboardEvent) {
54
+ const elements = focusables()
55
+ const first = elements[0]
56
+ const last = elements.at(-1)
57
+ if (!first || !last) {
58
+ return
59
+ }
60
+ if (event.shiftKey && document.activeElement === first) {
61
+ event.preventDefault()
62
+ last.focus()
63
+ }
64
+ else if (!event.shiftKey && document.activeElement === last) {
65
+ event.preventDefault()
66
+ first.focus()
67
+ }
68
+ }
26
69
  </script>
27
70
 
28
71
  <template>
@@ -35,9 +78,19 @@ onKeyboardShortcut(['escape'], () => {
35
78
  class="absolute inset-0"
36
79
  @click="close()"
37
80
  />
38
- <div class="bg-white dark:bg-gray-900 md:mt-16 md:mx-auto w-screen max-w-[512px] shadow-xl border border-gray-200 dark:border-gray-750 rounded-lg relative divide-y divide-gray-200 dark:divide-gray-850">
81
+ <div
82
+ ref="panel"
83
+ role="dialog"
84
+ aria-modal="true"
85
+ aria-labelledby="poveste-layout-modal-title"
86
+ class="bg-white dark:bg-gray-900 md:mt-16 md:mx-auto w-screen max-w-[512px] shadow-xl border border-gray-200 dark:border-gray-750 rounded-lg relative divide-y divide-gray-200 dark:divide-gray-850"
87
+ @keydown.tab="keepFocusInside"
88
+ >
39
89
  <div class="flex items-center px-6 py-3 text-gray-900 dark:text-gray-100">
40
- <h2 class="flex-1 text-sm font-medium m-0">
90
+ <h2
91
+ id="poveste-layout-modal-title"
92
+ class="flex-1 text-sm font-medium m-0"
93
+ >
41
94
  Customize Layout
42
95
  </h2>
43
96
  <button
@@ -0,0 +1,323 @@
1
+ <script lang="ts" setup>
2
+ import type { DocProp } from '@poveste/shared'
3
+ import type { ComponentDocsEntry } from '../../util/component-docs'
4
+ import { computed } from 'vue'
5
+ import { modelLabel, OPEN_PARTS, rootFirst, tagHref, tagText } from '../../util/component-docs'
6
+
7
+ const props = defineProps<{
8
+ components: ComponentDocsEntry[]
9
+ /** The story's file, which names the family whose root opens first. */
10
+ storyFile?: string | undefined
11
+ }>()
12
+
13
+ const ordered = computed(() => rootFirst(props.components, props.storyFile))
14
+
15
+ function shownDefault(prop: DocProp) {
16
+ return prop.default ?? prop.defaultTag
17
+ }
18
+ </script>
19
+
20
+ <template>
21
+ <div
22
+ class="poveste-component-docs"
23
+ data-testid="component-docs"
24
+ >
25
+ <details
26
+ v-for="(component, index) of ordered"
27
+ :key="component.file"
28
+ :open="ordered.length <= OPEN_PARTS || index === 0"
29
+ data-slot="component"
30
+ :data-component="component.name"
31
+ >
32
+ <summary>
33
+ <h3 data-slot="component-name">
34
+ {{ component.name }}<code
35
+ v-if="component.doc?.generic"
36
+ title="The component's type parameters, as its script declares them"
37
+ data-slot="generic"
38
+ >&lt;{{ component.doc.generic }}&gt;</code>
39
+ </h3>
40
+ </summary>
41
+
42
+ <p
43
+ v-if="component.error"
44
+ data-slot="error"
45
+ >
46
+ Could not read this component's types: {{ component.error }}
47
+ </p>
48
+
49
+ <template v-else-if="component.doc">
50
+ <table
51
+ v-if="component.doc.props.length"
52
+ data-slot="props"
53
+ >
54
+ <thead>
55
+ <tr>
56
+ <th>Prop</th>
57
+ <th>Type</th>
58
+ <th>Default</th>
59
+ <th>Description</th>
60
+ </tr>
61
+ </thead>
62
+ <tbody>
63
+ <tr
64
+ v-for="prop of component.doc.props"
65
+ :key="prop.name"
66
+ :data-prop="prop.name"
67
+ >
68
+ <td>
69
+ <code
70
+ :class="{ deprecated: tagText(prop.tags, 'deprecated') !== undefined }"
71
+ >{{ prop.name }}</code><span
72
+ v-if="prop.required"
73
+ title="Required"
74
+ data-slot="required"
75
+ >*</span>
76
+ <code
77
+ v-if="modelLabel(component.doc, prop.name)"
78
+ :title="`Bound two ways with ${modelLabel(component.doc, prop.name)}, through the update:${prop.name} event`"
79
+ data-slot="model"
80
+ >{{ modelLabel(component.doc, prop.name) }}</code>
81
+ </td>
82
+ <td><code>{{ prop.type }}</code></td>
83
+ <td>
84
+ <code v-if="shownDefault(prop) !== undefined">{{ shownDefault(prop) }}</code>
85
+ <span
86
+ v-if="__POVESTE_DEV__ && prop.defaultConflict"
87
+ :title="`The code's default is shown; the @default tag says ${prop.defaultTag}`"
88
+ data-slot="default-conflict"
89
+ >≠</span>
90
+ </td>
91
+ <td>
92
+ <span
93
+ v-if="tagText(prop.tags, 'deprecated') !== undefined"
94
+ data-slot="deprecated"
95
+ >Deprecated<template v-if="tagText(prop.tags, 'deprecated')">: {{ tagText(prop.tags, 'deprecated') }}</template></span>
96
+ {{ prop.description }}
97
+ <template
98
+ v-for="tag of prop.tags.filter(tag => tag.name === 'see' || tag.name === 'link')"
99
+ :key="tag.text"
100
+ >
101
+ <a
102
+ v-if="tagHref(tag.text ?? '')"
103
+ :href="tagHref(tag.text ?? '')"
104
+ target="_blank"
105
+ rel="noopener"
106
+ data-slot="see"
107
+ >{{ tag.text }}</a>
108
+ <span
109
+ v-else
110
+ data-slot="see"
111
+ >{{ tag.text }}</span>
112
+ </template>
113
+ <pre
114
+ v-if="tagText(prop.tags, 'example')"
115
+ data-slot="example"
116
+ ><code>{{ tagText(prop.tags, 'example') }}</code></pre>
117
+ </td>
118
+ </tr>
119
+ </tbody>
120
+ </table>
121
+
122
+ <table
123
+ v-if="component.doc.slots.length"
124
+ data-slot="slots"
125
+ >
126
+ <thead>
127
+ <tr>
128
+ <th>Slot</th>
129
+ <th>Type</th>
130
+ <th>Description</th>
131
+ </tr>
132
+ </thead>
133
+ <tbody>
134
+ <template
135
+ v-for="slot of component.doc.slots"
136
+ :key="slot.name"
137
+ >
138
+ <tr :data-slot-name="slot.name">
139
+ <td><code>{{ slot.name }}</code></td>
140
+ <!-- Its props are listed below, one row each; the type string would only repeat them. -->
141
+ <td><code v-if="slot.type && !slot.props?.length">{{ slot.type }}</code></td>
142
+ <td>{{ slot.description }}</td>
143
+ </tr>
144
+ <tr
145
+ v-for="slotProp of slot.props ?? []"
146
+ :key="`${slot.name}.${slotProp.name}`"
147
+ :data-slot-prop="slotProp.name"
148
+ >
149
+ <td><code>{{ slotProp.name }}</code></td>
150
+ <td><code>{{ slotProp.type }}</code></td>
151
+ <td>{{ slotProp.description }}</td>
152
+ </tr>
153
+ </template>
154
+ </tbody>
155
+ </table>
156
+
157
+ <table
158
+ v-if="component.doc.events.length"
159
+ data-slot="events"
160
+ >
161
+ <thead>
162
+ <tr>
163
+ <th>Event</th>
164
+ <th>Payload</th>
165
+ <th>Description</th>
166
+ </tr>
167
+ </thead>
168
+ <tbody>
169
+ <tr
170
+ v-for="event of component.doc.events"
171
+ :key="event.name"
172
+ :data-event="event.name"
173
+ >
174
+ <td>
175
+ <code>{{ event.name }}</code>
176
+ <code
177
+ v-if="modelLabel(component.doc, event.name)"
178
+ :title="`Bound two ways with ${modelLabel(component.doc, event.name)}, with the ${event.name.slice('update:'.length)} prop`"
179
+ data-slot="model"
180
+ >{{ modelLabel(component.doc, event.name) }}</code>
181
+ </td>
182
+ <td><code v-if="event.type">{{ event.type }}</code></td>
183
+ <td>{{ event.description }}</td>
184
+ </tr>
185
+ </tbody>
186
+ </table>
187
+ </template>
188
+ </details>
189
+ </div>
190
+ </template>
191
+
192
+ <style scoped>
193
+ .poveste-component-docs {
194
+ padding: 1rem;
195
+ font-size: .875rem;
196
+ color: var(--color-gray-800);
197
+
198
+ /* On the subject, not `.ptw-dark &`: `.ptw-dark` sits above the `@scope` root (#101). */
199
+ &:where(.ptw-dark, .ptw-dark *) {
200
+ color: var(--color-gray-200);
201
+ }
202
+ }
203
+
204
+ details + details {
205
+ margin-top: 1.5rem;
206
+ }
207
+
208
+ summary {
209
+ cursor: pointer;
210
+ margin-bottom: .5rem;
211
+ }
212
+
213
+ h3 {
214
+ display: inline;
215
+ font-weight: 600;
216
+ font-size: 1rem;
217
+ }
218
+
219
+ table {
220
+ width: 100%;
221
+ border-collapse: collapse;
222
+ margin-bottom: 1rem;
223
+ }
224
+
225
+ th {
226
+ text-align: start;
227
+ font-weight: 500;
228
+ color: var(--color-gray-500);
229
+ }
230
+
231
+ th,
232
+ td {
233
+ padding: .375rem .5rem;
234
+ border-bottom: 1px solid var(--color-gray-200);
235
+ vertical-align: top;
236
+
237
+ &:where(.ptw-dark, .ptw-dark *) {
238
+ border-color: var(--color-gray-750);
239
+ }
240
+ }
241
+
242
+ code {
243
+ font-size: .8125rem;
244
+ background-color: var(--color-gray-100);
245
+ border-radius: .25rem;
246
+ padding: 0 .25rem;
247
+
248
+ &:where(.ptw-dark, .ptw-dark *) {
249
+ background-color: var(--color-gray-750);
250
+ }
251
+ }
252
+
253
+ pre code {
254
+ display: block;
255
+ padding: .5rem;
256
+ margin-top: .25rem;
257
+ white-space: pre-wrap;
258
+ }
259
+
260
+ .deprecated {
261
+ text-decoration: line-through;
262
+ }
263
+
264
+ /* Its own line: a constraint wrapped beside the name broke mid-phrase in the narrow panel. */
265
+ /* A prop a slot passes its content, under the slot it belongs to. */
266
+ [data-slot-prop] td:first-child {
267
+ padding-inline-start: 1rem;
268
+ }
269
+
270
+ [data-slot='generic'] {
271
+ display: block;
272
+ width: fit-content;
273
+ margin-top: .25rem;
274
+ font-size: .75rem;
275
+ font-weight: 400;
276
+ cursor: help;
277
+ }
278
+
279
+ [data-slot='required'] {
280
+ color: var(--color-red-500);
281
+ margin-inline-start: .125rem;
282
+ }
283
+
284
+ [data-slot='model'] {
285
+ display: inline-block;
286
+ margin-inline-start: .375rem;
287
+ color: var(--color-primary-600);
288
+ cursor: help;
289
+
290
+ &:where(.ptw-dark, .ptw-dark *) {
291
+ color: var(--color-primary-400);
292
+ }
293
+ }
294
+
295
+ [data-slot='deprecated'],
296
+ [data-slot='default-conflict'] {
297
+ color: var(--color-amber-600);
298
+ font-weight: 500;
299
+
300
+ &:where(.ptw-dark, .ptw-dark *) {
301
+ color: var(--color-amber-400);
302
+ }
303
+ }
304
+
305
+ [data-slot='default-conflict'] {
306
+ margin-inline-start: .25rem;
307
+ cursor: help;
308
+ }
309
+
310
+ [data-slot='see'] {
311
+ display: block;
312
+ margin-top: .25rem;
313
+ }
314
+
315
+ a[data-slot='see'] {
316
+ color: var(--color-primary-500);
317
+ text-decoration: underline;
318
+ }
319
+
320
+ [data-slot='error'] {
321
+ color: var(--color-gray-500);
322
+ }
323
+ </style>