@poveste/app 0.15.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.
- package/dist/app/style/main.pcss +18 -44
- package/dist/bundled/App.vue_vue_type_script_setup_true_lang.js +9 -7
- package/dist/bundled/app.css +80 -17
- package/dist/bundled/components/app/AppActions.vue_vue_type_script_setup_true_lang.js +44 -27
- package/dist/bundled/components/app/ShortcutTooltip.js +5 -0
- package/dist/bundled/components/app/ShortcutTooltip.vue_vue_type_script_setup_true_lang.js +27 -0
- package/dist/bundled/components/base/BaseDropdown.js +6 -0
- package/dist/bundled/components/base/BaseDropdown.vue_vue_type_script_setup_true_lang.js +68 -0
- package/dist/bundled/components/base/BaseOverflowMenu.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/bundled/components/base/BaseSelect.js +1 -0
- package/dist/bundled/components/base/BaseSelect.vue_vue_type_script_setup_true_lang.js +80 -51
- package/dist/bundled/components/base/BaseSplitPane.js +1 -1
- package/dist/bundled/components/base/BaseSplitPane.vue_vue_type_script_setup_true_lang.js +65 -4
- package/dist/bundled/components/panel/ControlsComponentPropItem.vue_vue_type_script_setup_true_lang.js +15 -13
- package/dist/bundled/components/panel/ControlsComponentProps.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/bundled/components/panel/ControlsComponentState.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/bundled/components/panel/StatePresets.vue_vue_type_script_setup_true_lang.js +69 -54
- package/dist/bundled/components/panel/StoryEvent.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/bundled/components/panel/StorySidePanel.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/bundled/components/panel/StorySourceCode.js +1 -1
- package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +21 -16
- package/dist/bundled/components/story/StoryResponsivePreview.js +1 -1
- package/dist/bundled/components/story/StoryResponsivePreview.vue_vue_type_script_setup_true_lang.js +46 -43
- package/dist/bundled/components/story/StoryVariantGridItem.js +1 -1
- package/dist/bundled/components/story/StoryVariantGridItem.vue_vue_type_script_setup_true_lang.js +21 -19
- package/dist/bundled/components/story/StoryVariantSingle.js +1 -1
- package/dist/bundled/components/story/StoryVariantSingle.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/bundled/components/story/StoryView.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/bundled/components/toolbar/DevOnlyToolbarOpenInEditor.vue_vue_type_script_setup_true_lang.js +15 -12
- package/dist/bundled/components/toolbar/ToolbarBackground.vue_vue_type_script_setup_true_lang.js +20 -16
- package/dist/bundled/components/toolbar/ToolbarNewTab.vue_vue_type_script_setup_true_lang.js +16 -13
- package/dist/bundled/components/toolbar/ToolbarResponsiveSize.vue_vue_type_script_setup_true_lang.js +35 -24
- package/dist/bundled/components/toolbar/ToolbarTextDirection.vue_vue_type_script_setup_true_lang.js +15 -12
- package/dist/bundled/index.js +0 -13
- package/dist/bundled/sandbox.js +2 -1
- package/dist/bundled/util/icons.generated.js +4 -2
- package/dist/bundled/util/scroll.js +26 -5
- package/dist/bundled/util/state.js +105 -5
- package/dist/style.css +731 -357
- package/package.json +19 -15
- package/src/__tests__/base-select.spec.ts +25 -0
- package/src/__tests__/reset.spec.ts +53 -0
- package/src/__tests__/state-clone.spec.ts +55 -1
- package/src/__tests__/state-types.spec.ts +175 -0
- package/src/app/App.vue +43 -34
- package/src/app/components/app/AppActions.vue +42 -30
- package/src/app/components/app/ShortcutTooltip.vue +29 -0
- package/src/app/components/base/BaseDropdown.vue +142 -0
- package/src/app/components/base/BaseOverflowMenu.vue +10 -5
- package/src/app/components/base/BaseSelect.vue +133 -60
- package/src/app/components/base/BaseSplitPane.vue +73 -2
- package/src/app/components/panel/ControlsComponentPropItem.vue +18 -17
- package/src/app/components/panel/ControlsComponentProps.vue +7 -5
- package/src/app/components/panel/ControlsComponentState.vue +7 -5
- package/src/app/components/panel/StatePresets.vue +82 -76
- package/src/app/components/panel/StoryEvent.vue +10 -9
- package/src/app/components/panel/StorySidePanel.vue +1 -0
- package/src/app/components/panel/StorySourceCode.vue +47 -41
- package/src/app/components/story/StoryResponsivePreview.vue +55 -27
- package/src/app/components/story/StoryVariantGridItem.vue +19 -18
- package/src/app/components/story/StoryVariantSingle.vue +1 -0
- package/src/app/components/story/StoryView.vue +1 -0
- package/src/app/components/toolbar/DevOnlyToolbarOpenInEditor.vue +14 -12
- package/src/app/components/toolbar/ToolbarBackground.vue +22 -23
- package/src/app/components/toolbar/ToolbarNewTab.vue +14 -12
- package/src/app/components/toolbar/ToolbarResponsiveSize.vue +31 -25
- package/src/app/components/toolbar/ToolbarTextDirection.vue +14 -10
- package/src/app/index.ts +0 -17
- package/src/app/sandbox.ts +8 -1
- package/src/app/style/main.pcss +18 -44
- package/src/app/util/icons.generated.ts +2 -2
- package/src/app/util/scroll.ts +47 -9
- package/src/app/util/state.ts +139 -6
- package/dist/app/style/floating.pcss +0 -40
- package/dist/bundled/util/tooltip.js +0 -14
- package/src/app/style/floating.pcss +0 -40
- package/src/app/util/tooltip.ts +0 -13
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { HstTooltip, PopoverAnchor, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger, portalTarget } from '@poveste/controls'
|
|
3
|
+
import { onMounted, ref, shallowRef } from 'vue'
|
|
4
|
+
|
|
5
|
+
// The wrapper below is what a caller's class and `data-testid` are for, the way
|
|
6
|
+
// they were on `VDropdown` itself.
|
|
7
|
+
defineOptions({ inheritAttrs: false })
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
11
|
+
align?: 'start' | 'center' | 'end'
|
|
12
|
+
/** floating-vue's `skidding`: a shift along the trigger's own edge. */
|
|
13
|
+
alignOffset?: number
|
|
14
|
+
/**
|
|
15
|
+
* Refuses to open, rather than disabling the button. A disabled button takes
|
|
16
|
+
* no pointer events, and the one site that sets this still wants its tooltip.
|
|
17
|
+
*/
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
/**
|
|
20
|
+
* Rendered here rather than around the trigger at the call site, because
|
|
21
|
+
* where the tooltip sits relative to the anchor below decides whether this
|
|
22
|
+
* opens in the right place at all.
|
|
23
|
+
*/
|
|
24
|
+
tooltip?: string | null | undefined
|
|
25
|
+
}>(), {
|
|
26
|
+
side: 'bottom',
|
|
27
|
+
align: 'center',
|
|
28
|
+
alignOffset: 0,
|
|
29
|
+
disabled: false,
|
|
30
|
+
tooltip: undefined,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const open = ref(false)
|
|
34
|
+
|
|
35
|
+
function setOpen(value: boolean) {
|
|
36
|
+
open.value = value && !props.disabled
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// After mount, not during setup: the chrome's root is only in the document once
|
|
40
|
+
// the tree it wraps has been inserted.
|
|
41
|
+
const target = shallowRef<HTMLElement>()
|
|
42
|
+
onMounted(() => {
|
|
43
|
+
target.value = portalTarget()
|
|
44
|
+
})
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<PopoverRoot
|
|
49
|
+
:open="open"
|
|
50
|
+
@update:open="setOpen"
|
|
51
|
+
>
|
|
52
|
+
<!-- The anchor is the wrapper, not the trigger, and it has to sit outside
|
|
53
|
+
the tooltip. A tooltip is a popper too, so a trigger rendered inside one
|
|
54
|
+
registers itself as *its* anchor, and the panel then has none: it opens at
|
|
55
|
+
the top-left corner, off-screen, which a test asking only whether it is
|
|
56
|
+
visible still passes. -->
|
|
57
|
+
<PopoverAnchor as-child>
|
|
58
|
+
<div
|
|
59
|
+
v-bind="$attrs"
|
|
60
|
+
class="poveste-dropdown-trigger"
|
|
61
|
+
>
|
|
62
|
+
<!-- No tooltip while the panel is up: it would sit on top of the first
|
|
63
|
+
row. -->
|
|
64
|
+
<HstTooltip :content="open ? null : tooltip">
|
|
65
|
+
<PopoverTrigger as-child>
|
|
66
|
+
<slot :open="open" />
|
|
67
|
+
</PopoverTrigger>
|
|
68
|
+
</HstTooltip>
|
|
69
|
+
</div>
|
|
70
|
+
</PopoverAnchor>
|
|
71
|
+
|
|
72
|
+
<PopoverPortal
|
|
73
|
+
v-if="target"
|
|
74
|
+
:to="target"
|
|
75
|
+
>
|
|
76
|
+
<PopoverContent
|
|
77
|
+
class="poveste-dropdown"
|
|
78
|
+
:side="side"
|
|
79
|
+
:align="align"
|
|
80
|
+
:side-offset="8"
|
|
81
|
+
:align-offset="alignOffset"
|
|
82
|
+
:collision-padding="4"
|
|
83
|
+
>
|
|
84
|
+
<slot
|
|
85
|
+
name="popper"
|
|
86
|
+
:close="() => setOpen(false)"
|
|
87
|
+
/>
|
|
88
|
+
</PopoverContent>
|
|
89
|
+
</PopoverPortal>
|
|
90
|
+
</PopoverRoot>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<style lang="pcss">
|
|
94
|
+
/* floating-vue put the same zero on its own wrapper, and the triggers that
|
|
95
|
+
wanted text to sit normally say `leading-normal` because of it. */
|
|
96
|
+
.poveste-dropdown-trigger {
|
|
97
|
+
line-height: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.poveste-dropdown {
|
|
101
|
+
background: #fff;
|
|
102
|
+
color: #000;
|
|
103
|
+
border: 1px solid #ddd;
|
|
104
|
+
border-radius: 6px;
|
|
105
|
+
box-shadow: 0 6px 30px rgb(0 0 0 / .1);
|
|
106
|
+
/* A row that paints to the panel's edge has to stop at the rounded corner,
|
|
107
|
+
which the `overflow-y: auto` on floating-vue's inner element did. */
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
z-index: 100;
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* What Tailwind's `dark:` compiles to, spelled out because `@apply` in a
|
|
113
|
+
* single-file component needs a `@reference` and no component here has one.
|
|
114
|
+
* Not `.ptw-dark &`: the chrome CSS is wrapped in `@scope` and `.ptw-dark`
|
|
115
|
+
* sits on `<html>`, above the scope root, so a descendant rule keyed on it
|
|
116
|
+
* never matches and the panel falls back to the light colours (#101). This
|
|
117
|
+
* puts the condition on the subject instead, and adds no specificity — so it
|
|
118
|
+
* has to come after the light values to win.
|
|
119
|
+
*/
|
|
120
|
+
&:where(.ptw-dark, .ptw-dark *) {
|
|
121
|
+
background: var(--color-gray-700);
|
|
122
|
+
border-color: var(--color-gray-850);
|
|
123
|
+
color: var(--color-gray-100);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&:focus-visible {
|
|
127
|
+
outline: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&[data-state='open'] {
|
|
131
|
+
transform-origin: var(--reka-popover-content-transform-origin);
|
|
132
|
+
animation: poveste-dropdown-open .15s cubic-bezier(0, 1, .5, 1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@keyframes poveste-dropdown-open {
|
|
137
|
+
from {
|
|
138
|
+
opacity: 0;
|
|
139
|
+
transform: scale(.75);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
@@ -3,6 +3,7 @@ import type { SetupContext, VNode } from 'vue'
|
|
|
3
3
|
import { Icon } from '@iconify/vue'
|
|
4
4
|
import { useResizeObserver } from '@vueuse/core'
|
|
5
5
|
import { computed, h, onBeforeUnmount, reactive, ref } from 'vue'
|
|
6
|
+
import BaseDropdown from './BaseDropdown.vue'
|
|
6
7
|
|
|
7
8
|
// Container
|
|
8
9
|
|
|
@@ -99,18 +100,22 @@ function ChildrenSlice(props: { start?: number, end?: number }, { slots }: Setup
|
|
|
99
100
|
<slot />
|
|
100
101
|
</ChildrenRender>
|
|
101
102
|
|
|
102
|
-
<
|
|
103
|
+
<BaseDropdown
|
|
103
104
|
v-if="visibleChildrenCount < children.size"
|
|
104
105
|
>
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
<!-- A `<button>`, where this was a `role="button"` div with no `tabindex`:
|
|
107
|
+
the popover writes `aria-expanded` onto whatever it is given, and axe
|
|
108
|
+
rejects that on an element with no role it can name (#924). A keyboard
|
|
109
|
+
reaches the hidden tabs now, which it could not before. -->
|
|
110
|
+
<button
|
|
111
|
+
aria-label="More"
|
|
107
112
|
class="cursor-pointer hover:bg-primary-50 dark:hover:bg-primary-900 w-8 h-full flex items-center justify-center absolute top-0 right-0"
|
|
108
113
|
>
|
|
109
114
|
<Icon
|
|
110
115
|
icon="carbon:caret-down"
|
|
111
116
|
class="w-4 h-4 opacity-50 group-hover:opacity-100"
|
|
112
117
|
/>
|
|
113
|
-
</
|
|
118
|
+
</button>
|
|
114
119
|
|
|
115
120
|
<template #popper>
|
|
116
121
|
<div class="flex flex-col items-stretch">
|
|
@@ -121,6 +126,6 @@ function ChildrenSlice(props: { start?: number, end?: number }, { slots }: Setup
|
|
|
121
126
|
</ChildrenSlice>
|
|
122
127
|
</div>
|
|
123
128
|
</template>
|
|
124
|
-
</
|
|
129
|
+
</BaseDropdown>
|
|
125
130
|
</div>
|
|
126
131
|
</template>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { ComputedRef } from 'vue'
|
|
3
3
|
import { Icon } from '@iconify/vue'
|
|
4
|
+
import { PopoverAnchor, PopoverContent, PopoverPortal, PopoverRoot, portalTarget } from '@poveste/controls'
|
|
4
5
|
import { refAutoReset } from '@vueuse/core'
|
|
5
|
-
import {
|
|
6
|
-
import { computed, ref, useId } from 'vue'
|
|
6
|
+
import { computed, nextTick, onMounted, ref, shallowRef, useId, watch } from 'vue'
|
|
7
7
|
|
|
8
8
|
/*
|
|
9
9
|
* The WAI-ARIA select-only combobox: the trigger keeps focus and names the active
|
|
@@ -156,76 +156,149 @@ function onKeydown(event: KeyboardEvent) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// After mount, not during setup: the chrome's root is only in the document once
|
|
160
|
+
// the tree it wraps has been inserted.
|
|
161
|
+
const target = shallowRef<HTMLElement>()
|
|
162
|
+
onMounted(() => {
|
|
163
|
+
target.value = portalTarget()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* Reka spares its own trigger from the dismiss it fires on any pointer or focus
|
|
168
|
+
* landing outside the popup — but only a `PopoverTrigger` registers as one, and
|
|
169
|
+
* this trigger cannot be that: `PopoverTrigger` writes its own `aria-expanded`,
|
|
170
|
+
* `aria-controls` and `aria-haspopup="dialog"` over the listbox wiring the
|
|
171
|
+
* combobox pattern is built on. So the dismiss is declined here instead.
|
|
172
|
+
*
|
|
173
|
+
* Without it the popup is impossible to close by clicking the picker: the
|
|
174
|
+
* dismiss lands on `pointerdown`, the handler below then reads `open` as false
|
|
175
|
+
* and opens it again, and the whole thing looks like a click that did nothing.
|
|
176
|
+
* Neither aria spec catches it — both drive this from the keyboard, on purpose.
|
|
177
|
+
*/
|
|
178
|
+
function keepOpenOnTheTrigger(event: Event) {
|
|
179
|
+
if (combobox.value?.contains(event.target as Node)) {
|
|
180
|
+
event.preventDefault()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* A capped list scrolls, and `aria-activedescendant` moves an option the browser
|
|
186
|
+
* has no reason to reveal — the focus never leaves the trigger, so nothing
|
|
187
|
+
* scrolls on its own.
|
|
188
|
+
*/
|
|
189
|
+
watch([open, activeIndex], async ([isOpen, index]) => {
|
|
190
|
+
if (!isOpen || index < 0) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
await nextTick()
|
|
194
|
+
target.value?.ownerDocument.getElementById(optionId(index))?.scrollIntoView({ block: 'nearest' })
|
|
195
|
+
})
|
|
196
|
+
|
|
159
197
|
defineExpose({
|
|
160
198
|
focus: () => combobox.value?.focus(),
|
|
161
199
|
})
|
|
162
200
|
</script>
|
|
163
201
|
|
|
164
202
|
<template>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
class="poveste-base-select"
|
|
170
|
-
:triggers="[]"
|
|
171
|
-
no-auto-focus
|
|
172
|
-
auto-size
|
|
173
|
-
auto-boundary-max-size
|
|
174
|
-
>
|
|
175
|
-
<div
|
|
176
|
-
ref="combobox"
|
|
177
|
-
role="combobox"
|
|
178
|
-
tabindex="0"
|
|
179
|
-
:aria-label="label"
|
|
180
|
-
aria-haspopup="listbox"
|
|
181
|
-
:aria-expanded="open"
|
|
182
|
-
:aria-controls="open ? listboxId : undefined"
|
|
183
|
-
:aria-activedescendant="open && activeIndex >= 0 ? optionId(activeIndex) : undefined"
|
|
184
|
-
class="cursor-pointer w-full outline-none focus-visible:border-primary-500 px-2 h-[27px] -my-1 border border-solid border-black/25 dark:border-white/25 hover:border-primary-500 dark:hover:border-primary-500 rounded-sm flex gap-2 items-center leading-normal"
|
|
185
|
-
@click="open ? close() : openList()"
|
|
186
|
-
@keydown="onKeydown"
|
|
203
|
+
<PopoverRoot v-model:open="open">
|
|
204
|
+
<PopoverAnchor
|
|
205
|
+
as-child
|
|
206
|
+
class="poveste-base-select"
|
|
187
207
|
>
|
|
188
|
-
<div class="flex-1 truncate">
|
|
189
|
-
<slot :label="selectedLabel">
|
|
190
|
-
{{ selectedLabel }}
|
|
191
|
-
</slot>
|
|
192
|
-
</div>
|
|
193
|
-
<Icon
|
|
194
|
-
icon="carbon:chevron-sort"
|
|
195
|
-
class="w-4 h-4 flex-none ml-auto"
|
|
196
|
-
/>
|
|
197
|
-
</div>
|
|
198
|
-
<template #popper>
|
|
199
208
|
<div
|
|
200
|
-
|
|
201
|
-
role="
|
|
209
|
+
ref="combobox"
|
|
210
|
+
role="combobox"
|
|
211
|
+
tabindex="0"
|
|
202
212
|
:aria-label="label"
|
|
203
|
-
|
|
213
|
+
aria-haspopup="listbox"
|
|
214
|
+
:aria-expanded="open"
|
|
215
|
+
:aria-controls="open ? listboxId : undefined"
|
|
216
|
+
:aria-activedescendant="open && activeIndex >= 0 ? optionId(activeIndex) : undefined"
|
|
217
|
+
class="cursor-pointer w-full outline-none focus-visible:border-primary-500 px-2 h-[27px] -my-1 border border-solid border-black/25 dark:border-white/25 hover:border-primary-500 dark:hover:border-primary-500 rounded-sm flex gap-2 items-center leading-normal"
|
|
218
|
+
@click="open ? close() : openList()"
|
|
219
|
+
@keydown="onKeydown"
|
|
220
|
+
>
|
|
221
|
+
<div class="flex-1 truncate">
|
|
222
|
+
<slot :label="selectedLabel">
|
|
223
|
+
{{ selectedLabel }}
|
|
224
|
+
</slot>
|
|
225
|
+
</div>
|
|
226
|
+
<Icon
|
|
227
|
+
icon="carbon:chevron-sort"
|
|
228
|
+
class="w-4 h-4 flex-none ml-auto"
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
</PopoverAnchor>
|
|
232
|
+
|
|
233
|
+
<PopoverPortal
|
|
234
|
+
v-if="target"
|
|
235
|
+
:to="target"
|
|
236
|
+
>
|
|
237
|
+
<!-- The popup takes no focus at either end: the combobox pattern keeps it on
|
|
238
|
+
the trigger, which owns the keys. Deliberate rather than load-bearing
|
|
239
|
+
today — Reka moves focus into the content on open, and only skips it
|
|
240
|
+
here because the options are divs with nothing focusable in them. The
|
|
241
|
+
aria specs cannot fail on this, so removing it would look safe. -->
|
|
242
|
+
<PopoverContent
|
|
243
|
+
class="poveste-base-select-popper"
|
|
244
|
+
align="start"
|
|
245
|
+
:side-offset="6"
|
|
246
|
+
:collision-padding="8"
|
|
247
|
+
@open-auto-focus.prevent
|
|
248
|
+
@close-auto-focus.prevent
|
|
249
|
+
@interact-outside="keepOpenOnTheTrigger"
|
|
204
250
|
>
|
|
205
251
|
<div
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
:
|
|
209
|
-
|
|
210
|
-
:aria-selected="value === modelValue"
|
|
211
|
-
class="px-2 py-1 cursor-pointer hover:bg-primary-100 dark:hover:bg-primary-700"
|
|
212
|
-
:class="{
|
|
213
|
-
'bg-primary-200 dark:bg-primary-800': value === modelValue,
|
|
214
|
-
'outline outline-2 -outline-offset-2 outline-primary-500': index === activeIndex,
|
|
215
|
-
}"
|
|
216
|
-
@mousedown.prevent
|
|
217
|
-
@mousemove="activeIndex = index"
|
|
218
|
-
@click="choose(index)"
|
|
252
|
+
:id="listboxId"
|
|
253
|
+
role="listbox"
|
|
254
|
+
:aria-label="label"
|
|
255
|
+
class="poveste-base-select-options flex flex-col bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
|
219
256
|
>
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
:
|
|
223
|
-
:
|
|
257
|
+
<div
|
|
258
|
+
v-for="([value, optionLabel], index) of entries"
|
|
259
|
+
:id="optionId(index)"
|
|
260
|
+
:key="value"
|
|
261
|
+
role="option"
|
|
262
|
+
:aria-selected="value === modelValue"
|
|
263
|
+
class="px-2 py-1 cursor-pointer hover:bg-primary-100 dark:hover:bg-primary-700"
|
|
264
|
+
:class="{
|
|
265
|
+
'bg-primary-200 dark:bg-primary-800': value === modelValue,
|
|
266
|
+
'outline outline-2 -outline-offset-2 outline-primary-500': index === activeIndex,
|
|
267
|
+
}"
|
|
268
|
+
@mousedown.prevent
|
|
269
|
+
@mousemove="activeIndex = index"
|
|
270
|
+
@click="choose(index)"
|
|
224
271
|
>
|
|
225
|
-
|
|
226
|
-
|
|
272
|
+
<slot
|
|
273
|
+
name="option"
|
|
274
|
+
:label="optionLabel"
|
|
275
|
+
:value="value"
|
|
276
|
+
>
|
|
277
|
+
{{ optionLabel }}
|
|
278
|
+
</slot>
|
|
279
|
+
</div>
|
|
227
280
|
</div>
|
|
228
|
-
</
|
|
229
|
-
</
|
|
230
|
-
</
|
|
281
|
+
</PopoverContent>
|
|
282
|
+
</PopoverPortal>
|
|
283
|
+
</PopoverRoot>
|
|
231
284
|
</template>
|
|
285
|
+
|
|
286
|
+
<style lang="pcss">
|
|
287
|
+
.poveste-base-select-popper {
|
|
288
|
+
/* The trigger's own width, which is what floating-vue's `auto-size` gave. */
|
|
289
|
+
min-width: var(--reka-popper-anchor-width);
|
|
290
|
+
z-index: 100;
|
|
291
|
+
|
|
292
|
+
&:focus-visible {
|
|
293
|
+
outline: none;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.poveste-base-select-options {
|
|
298
|
+
/* The room Reka measured between the trigger and the viewport edge, which is
|
|
299
|
+
what `auto-boundary-max-size` gave: a story with more presets than fit below
|
|
300
|
+
its picker otherwise drew them off the screen, reachable by nothing. */
|
|
301
|
+
max-height: var(--reka-popover-content-available-height);
|
|
302
|
+
overflow-y: auto;
|
|
303
|
+
}
|
|
304
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { computed, onUnmounted, ref, watch } from 'vue'
|
|
2
|
+
import { computed, onUnmounted, ref, useId, watch } from 'vue'
|
|
3
3
|
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
orientation: {
|
|
@@ -49,6 +49,17 @@ const props = defineProps({
|
|
|
49
49
|
default: true,
|
|
50
50
|
},
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* What the divider is called to a screen reader.
|
|
54
|
+
*
|
|
55
|
+
* Four are on screen at once and they resize different things, so a shared
|
|
56
|
+
* default would announce four identical separators.
|
|
57
|
+
*/
|
|
58
|
+
label: {
|
|
59
|
+
type: String,
|
|
60
|
+
default: 'Resize panels',
|
|
61
|
+
},
|
|
62
|
+
|
|
52
63
|
/**
|
|
53
64
|
* Whether the first pane exists at all.
|
|
54
65
|
*
|
|
@@ -225,6 +236,56 @@ watch(hasBothPanes, (both) => {
|
|
|
225
236
|
}
|
|
226
237
|
})
|
|
227
238
|
|
|
239
|
+
/*
|
|
240
|
+
* The keyboard half, which the divider had none of: it was a `div` with a
|
|
241
|
+
* `mousedown` handler, so the layout could only be changed with a pointer and a
|
|
242
|
+
* screen reader was told nothing was there (#995).
|
|
243
|
+
*
|
|
244
|
+
* The arrow step is one unit of whatever `split` is measured in — percent
|
|
245
|
+
* normally, pixels under `fixed` — and `PageUp`/`PageDown` move by ten, because
|
|
246
|
+
* one pixel per press is not a usable way to move a pixel-sized pane.
|
|
247
|
+
*/
|
|
248
|
+
const STEP = 1
|
|
249
|
+
const PAGE_STEP = 10
|
|
250
|
+
|
|
251
|
+
function moveBy(amount: number) {
|
|
252
|
+
currentSplit.value = Math.min(props.max, Math.max(props.min, boundSplit.value + amount))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function onKeydown(event: KeyboardEvent) {
|
|
256
|
+
const back = props.orientation === 'landscape' ? 'ArrowLeft' : 'ArrowUp'
|
|
257
|
+
const forward = props.orientation === 'landscape' ? 'ArrowRight' : 'ArrowDown'
|
|
258
|
+
|
|
259
|
+
switch (event.key) {
|
|
260
|
+
case back:
|
|
261
|
+
moveBy(-STEP)
|
|
262
|
+
break
|
|
263
|
+
case forward:
|
|
264
|
+
moveBy(STEP)
|
|
265
|
+
break
|
|
266
|
+
case 'PageUp':
|
|
267
|
+
moveBy(-PAGE_STEP)
|
|
268
|
+
break
|
|
269
|
+
case 'PageDown':
|
|
270
|
+
moveBy(PAGE_STEP)
|
|
271
|
+
break
|
|
272
|
+
case 'Home':
|
|
273
|
+
currentSplit.value = props.min
|
|
274
|
+
break
|
|
275
|
+
case 'End':
|
|
276
|
+
currentSplit.value = props.max
|
|
277
|
+
break
|
|
278
|
+
default:
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Only for a key this handles: the arrows scroll the pane otherwise, and a
|
|
283
|
+
// blanket `prevent` would stop that everywhere the divider has focus.
|
|
284
|
+
event.preventDefault()
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const firstPaneId = useId()
|
|
288
|
+
|
|
228
289
|
function removeDragListeners() {
|
|
229
290
|
window.removeEventListener('mousemove', dragMove)
|
|
230
291
|
window.removeEventListener('mouseup', dragEnd)
|
|
@@ -248,6 +309,7 @@ onUnmounted(() => {
|
|
|
248
309
|
>
|
|
249
310
|
<div
|
|
250
311
|
v-if="showFirst"
|
|
312
|
+
:id="firstPaneId"
|
|
251
313
|
class="relative top-0 left-0 z-20"
|
|
252
314
|
:class="{
|
|
253
315
|
'pointer-events-none': dragging,
|
|
@@ -260,7 +322,15 @@ onUnmounted(() => {
|
|
|
260
322
|
|
|
261
323
|
<div
|
|
262
324
|
v-if="hasBothPanes"
|
|
263
|
-
|
|
325
|
+
role="separator"
|
|
326
|
+
tabindex="0"
|
|
327
|
+
:aria-label="label"
|
|
328
|
+
:aria-orientation="orientation === 'landscape' ? 'vertical' : 'horizontal'"
|
|
329
|
+
:aria-controls="firstPaneId"
|
|
330
|
+
:aria-valuenow="Math.round(boundSplit)"
|
|
331
|
+
:aria-valuemin="min"
|
|
332
|
+
:aria-valuemax="max"
|
|
333
|
+
class="dragger absolute z-100 hover:bg-primary-500/50 focus-visible:bg-primary-500/50 focus-visible:outline-2 focus-visible:outline-primary-500 transition-colors duration-150 delay-150"
|
|
264
334
|
:class="{
|
|
265
335
|
'top-0 bottom-0 cursor-ew-resize': orientation === 'landscape',
|
|
266
336
|
'left-0 right-0 cursor-ns-resize': orientation === 'portrait',
|
|
@@ -268,6 +338,7 @@ onUnmounted(() => {
|
|
|
268
338
|
'bg-primary-500/25': dragging,
|
|
269
339
|
}"
|
|
270
340
|
@mousedown.prevent="dragStart"
|
|
341
|
+
@keydown="onKeydown"
|
|
271
342
|
/>
|
|
272
343
|
</div>
|
|
273
344
|
<div
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { AutoPropComponentDefinition, PropDefinition } from '@poveste/shared'
|
|
3
3
|
import type { Variant } from '../../types'
|
|
4
4
|
import { Icon } from '@iconify/vue'
|
|
5
|
-
import { HstCheckbox, HstJson, HstNumber, HstText } from '@poveste/controls'
|
|
5
|
+
import { HstCheckbox, HstJson, HstNumber, HstText, HstTooltip } from '@poveste/controls'
|
|
6
6
|
import { computed } from 'vue'
|
|
7
7
|
|
|
8
8
|
const props = defineProps<{
|
|
@@ -64,21 +64,22 @@ const canReset = computed(() => props.variant.state?._hPropState?.[props.compone
|
|
|
64
64
|
class="poveste-controls-component-prop-item grow min-w-0"
|
|
65
65
|
:title="`${definition.name}${canReset ? ' *' : ''}`"
|
|
66
66
|
/>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
67
|
+
<HstTooltip content="Remove override">
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
:aria-label="`Remove override of ${definition.name}`"
|
|
71
|
+
:disabled="!canReset"
|
|
72
|
+
class="flex-none flex mr-2 mb-[10px] p-0 bg-transparent border-0 cursor-pointer text-gray-900 dark:text-gray-100 hover:text-primary-500 dark:hover:text-primary-400"
|
|
73
|
+
:class="[
|
|
74
|
+
canReset ? 'opacity-50 hover:opacity-100 focus-visible:opacity-100' : 'opacity-25 pointer-events-none',
|
|
75
|
+
]"
|
|
76
|
+
@click="reset()"
|
|
77
|
+
>
|
|
78
|
+
<Icon
|
|
79
|
+
icon="carbon:erase"
|
|
80
|
+
class="w-4 h-4"
|
|
81
|
+
/>
|
|
82
|
+
</button>
|
|
83
|
+
</HstTooltip>
|
|
83
84
|
</div>
|
|
84
85
|
</template>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { AutoPropComponentDefinition } from '@poveste/shared'
|
|
3
3
|
import type { Variant } from '../../types'
|
|
4
4
|
import { Icon } from '@iconify/vue'
|
|
5
|
+
import { HstTooltip } from '@poveste/controls'
|
|
5
6
|
import ControlsComponentPropItem from './ControlsComponentPropItem.vue'
|
|
6
7
|
|
|
7
8
|
defineProps<{
|
|
@@ -13,11 +14,12 @@ defineProps<{
|
|
|
13
14
|
<template>
|
|
14
15
|
<div class="poveste-controls-component-props">
|
|
15
16
|
<div class="font-mono p-2 flex items-center gap-1">
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
<HstTooltip content="Auto-detected props">
|
|
18
|
+
<Icon
|
|
19
|
+
icon="carbon:flash"
|
|
20
|
+
class="w-4 h-4 text-primary-500 flex-none"
|
|
21
|
+
/>
|
|
22
|
+
</HstTooltip>
|
|
21
23
|
<div>
|
|
22
24
|
<span class="opacity-30"><</span>{{ definition.name }}<span class="opacity-30">></span>
|
|
23
25
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Variant } from '@poveste/shared'
|
|
3
3
|
import { Icon } from '@iconify/vue'
|
|
4
|
+
import { HstTooltip } from '@poveste/controls'
|
|
4
5
|
import { computed } from 'vue'
|
|
5
6
|
import ControlsComponentStateItem from './ControlsComponentStateItem.vue'
|
|
6
7
|
|
|
@@ -15,11 +16,12 @@ const stateKeys = computed(() => Object.keys(props.variant.state || {})
|
|
|
15
16
|
<template>
|
|
16
17
|
<div class="poveste-controls-component-init-state">
|
|
17
18
|
<div class="p-2 flex items-center gap-1">
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
<HstTooltip content="Auto-detected state">
|
|
20
|
+
<Icon
|
|
21
|
+
icon="carbon:data-vis-1"
|
|
22
|
+
class="w-4 h-4 text-primary-500 flex-none"
|
|
23
|
+
/>
|
|
24
|
+
</HstTooltip>
|
|
23
25
|
<div>
|
|
24
26
|
State
|
|
25
27
|
</div>
|