@hina-ui/vue 1.7.13 → 1.7.14
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/index.js +1295 -283
- package/dist/index.js.map +1 -1
- package/dist/shared/lib/thumbhash.ts +12 -0
- package/dist/shared/lib/tour-engine.ts +721 -0
- package/dist/shared/lib/tour.ts +202 -24
- package/dist/shared/locale/en-US.ts +1 -0
- package/dist/shared/locale/ja-JP.ts +1 -0
- package/dist/shared/locale/types.ts +1 -0
- package/dist/shared/locale/zh-CN.ts +1 -0
- package/dist/shared/primitives/dialog/index.ts +2 -2
- package/dist/shared/primitives/dismissable-layer/index.ts +48 -1
- package/dist/shared/primitives/focus-scope/index.ts +5 -3
- package/dist/shared/styles/semantic.css +1 -0
- package/dist/shared/styles/tour.css +107 -2
- package/dist/shared/variants/image.ts +28 -0
- package/dist/shared/variants/tour.ts +22 -4
- package/dist/types/shared/src/lib/thumbhash.d.ts +2 -0
- package/dist/types/shared/src/lib/thumbhash.d.ts.map +1 -0
- package/dist/types/shared/src/lib/tour-engine.d.ts +47 -0
- package/dist/types/shared/src/lib/tour-engine.d.ts.map +1 -0
- package/dist/types/shared/src/lib/tour.d.ts +65 -9
- package/dist/types/shared/src/lib/tour.d.ts.map +1 -1
- package/dist/types/shared/src/locale/en-US.d.ts.map +1 -1
- package/dist/types/shared/src/locale/ja-JP.d.ts.map +1 -1
- package/dist/types/shared/src/locale/types.d.ts +1 -0
- package/dist/types/shared/src/locale/types.d.ts.map +1 -1
- package/dist/types/shared/src/locale/zh-CN.d.ts.map +1 -1
- package/dist/types/shared/src/primitives/dialog/index.d.ts +1 -1
- package/dist/types/shared/src/primitives/dialog/index.d.ts.map +1 -1
- package/dist/types/shared/src/primitives/dismissable-layer/index.d.ts +1 -0
- package/dist/types/shared/src/primitives/dismissable-layer/index.d.ts.map +1 -1
- package/dist/types/shared/src/primitives/focus-scope/index.d.ts.map +1 -1
- package/dist/types/shared/src/variants/image.d.ts +35 -0
- package/dist/types/shared/src/variants/image.d.ts.map +1 -1
- package/dist/types/shared/src/variants/tour.d.ts +9 -4
- package/dist/types/shared/src/variants/tour.d.ts.map +1 -1
- package/dist/types/vue/src/components/button/Button.vue.d.ts.map +1 -1
- package/dist/types/vue/src/components/dialog/Dialog.vue.d.ts +3 -0
- package/dist/types/vue/src/components/dialog/Dialog.vue.d.ts.map +1 -1
- package/dist/types/vue/src/components/drawer/Drawer.vue.d.ts +3 -0
- package/dist/types/vue/src/components/drawer/Drawer.vue.d.ts.map +1 -1
- package/dist/types/vue/src/components/image/Image.vue.d.ts +1 -0
- package/dist/types/vue/src/components/image/Image.vue.d.ts.map +1 -1
- package/dist/types/vue/src/components/image/composables/useImage.d.ts +2 -0
- package/dist/types/vue/src/components/image/composables/useImage.d.ts.map +1 -1
- package/dist/types/vue/src/components/overlay-branch/OverlayBranch.vue.d.ts +17 -0
- package/dist/types/vue/src/components/overlay-branch/OverlayBranch.vue.d.ts.map +1 -0
- package/dist/types/vue/src/components/sheet/Sheet.vue.d.ts +3 -0
- package/dist/types/vue/src/components/sheet/Sheet.vue.d.ts.map +1 -1
- package/dist/types/vue/src/components/tour/Tour.vue.d.ts +10 -1
- package/dist/types/vue/src/components/tour/Tour.vue.d.ts.map +1 -1
- package/dist/types/vue/src/components/tour/types.d.ts +9 -3
- package/dist/types/vue/src/components/tour/types.d.ts.map +1 -1
- package/dist/types/vue/src/index.d.ts +2 -1
- package/dist/types/vue/src/index.d.ts.map +1 -1
- package/dist/types/vue/src/primitives/dismissable-layer/DismissableLayerBranch.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/button/Button.vue +1 -1
- package/src/components/dialog/Dialog.vue +14 -1
- package/src/components/drawer/Drawer.vue +14 -1
- package/src/components/image/Image.vue +58 -24
- package/src/components/image/composables/useImage.ts +46 -21
- package/src/components/overlay-branch/OverlayBranch.vue +14 -0
- package/src/components/sheet/Sheet.vue +14 -1
- package/src/components/tour/Tour.vue +302 -141
- package/src/components/tour/types.ts +33 -2
- package/src/index.ts +11 -0
- package/src/primitives/dismissable-layer/DismissableLayerBranch.ts +9 -18
package/dist/shared/lib/tour.ts
CHANGED
|
@@ -1,11 +1,70 @@
|
|
|
1
1
|
import { autoUpdate } from '@floating-ui/dom'
|
|
2
|
-
import {
|
|
3
|
-
import { isHighestLayer } from '../primitives/dismissable-layer'
|
|
2
|
+
import { hideOutsideLayers, isHighestLayer } from '../primitives/dismissable-layer'
|
|
4
3
|
import { getActiveElement, getTabbableCandidates } from '../primitives/focus-scope'
|
|
4
|
+
import { cssTimeToMs } from './css-time'
|
|
5
5
|
|
|
6
6
|
export type TourSide = 'top' | 'right' | 'bottom' | 'left'
|
|
7
7
|
export type TourAlign = 'start' | 'center' | 'end'
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
export interface TourRectLike {
|
|
10
|
+
x?: number
|
|
11
|
+
y?: number
|
|
12
|
+
left?: number
|
|
13
|
+
top?: number
|
|
14
|
+
width: number
|
|
15
|
+
height: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TourVirtualElement {
|
|
19
|
+
getBoundingClientRect: () => TourRectLike
|
|
20
|
+
contextElement?: Element
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TourTargetItem = string | Element | TourRectLike | TourVirtualElement
|
|
24
|
+
export type TourTarget =
|
|
25
|
+
| TourTargetItem
|
|
26
|
+
| TourTargetItem[]
|
|
27
|
+
| (() => TourTargetItem | TourTargetItem[] | null | undefined)
|
|
28
|
+
| null
|
|
29
|
+
|
|
30
|
+
export type TourPointerKind = 'mouse' | 'touch'
|
|
31
|
+
export type TourCloseReason = 'finish' | 'skip' | 'escape' | 'close'
|
|
32
|
+
|
|
33
|
+
export interface TourPoint {
|
|
34
|
+
x: number
|
|
35
|
+
y: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TourPointer {
|
|
39
|
+
move: (target: TourTarget | TourPoint, duration?: number) => Promise<void>
|
|
40
|
+
tap: () => Promise<void>
|
|
41
|
+
press: (duration?: number) => Promise<void>
|
|
42
|
+
drag: (
|
|
43
|
+
target: TourTarget | TourPoint,
|
|
44
|
+
duration?: number,
|
|
45
|
+
onMove?: (point: TourPoint) => void,
|
|
46
|
+
) => Promise<void>
|
|
47
|
+
key: (key: string, duration?: number) => Promise<void>
|
|
48
|
+
hide: () => Promise<void>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TourSpotlightOptions {
|
|
52
|
+
padding?: number
|
|
53
|
+
radius?: number
|
|
54
|
+
wait?: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface TourPlayContext {
|
|
58
|
+
step: number
|
|
59
|
+
signal: AbortSignal
|
|
60
|
+
defer: (cleanup: () => void) => void
|
|
61
|
+
pointer: TourPointer
|
|
62
|
+
spotlight: (target?: TourTarget, options?: TourSpotlightOptions) => Promise<void>
|
|
63
|
+
waitFor: (target: TourTarget, timeout?: number) => Promise<boolean>
|
|
64
|
+
sleep: (ms: number) => Promise<void>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type TourPlay = (context: TourPlayContext) => void | Promise<void>
|
|
9
68
|
|
|
10
69
|
export interface TourStepBase {
|
|
11
70
|
target?: TourTarget
|
|
@@ -16,8 +75,11 @@ export interface TourStepBase {
|
|
|
16
75
|
padding?: number
|
|
17
76
|
radius?: number
|
|
18
77
|
blockTarget?: boolean
|
|
78
|
+
blockPage?: boolean
|
|
19
79
|
scroll?: boolean
|
|
20
80
|
beacon?: boolean
|
|
81
|
+
wait?: number
|
|
82
|
+
play?: TourPlay
|
|
21
83
|
}
|
|
22
84
|
|
|
23
85
|
export type TourBeforeChange = (
|
|
@@ -48,12 +110,100 @@ export function tourTargetBlocked(step: TourStepBase, blockTarget: boolean) {
|
|
|
48
110
|
return step.blockTarget ?? blockTarget
|
|
49
111
|
}
|
|
50
112
|
|
|
51
|
-
export function
|
|
113
|
+
export function tourPageBlocked(step: TourStepBase, blockPage: boolean) {
|
|
114
|
+
return step.blockPage ?? blockPage
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TourResolvedTarget {
|
|
118
|
+
elements: HTMLElement[]
|
|
119
|
+
virtual: boolean
|
|
120
|
+
rect: () => TourRect
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function toRect(rect: TourRectLike): TourRect {
|
|
124
|
+
return {
|
|
125
|
+
x: rect.left ?? rect.x ?? 0,
|
|
126
|
+
y: rect.top ?? rect.y ?? 0,
|
|
127
|
+
width: rect.width,
|
|
128
|
+
height: rect.height,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function unionRect(rects: TourRect[]): TourRect {
|
|
133
|
+
const left = Math.min(...rects.map(rect => rect.x))
|
|
134
|
+
const top = Math.min(...rects.map(rect => rect.y))
|
|
135
|
+
const right = Math.max(...rects.map(rect => rect.x + rect.width))
|
|
136
|
+
const bottom = Math.max(...rects.map(rect => rect.y + rect.height))
|
|
137
|
+
return { x: left, y: top, width: right - left, height: bottom - top }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type TourItemReader = { element: HTMLElement | null; read: () => TourRect }
|
|
141
|
+
|
|
142
|
+
function readItem(item: TourTargetItem): TourItemReader | null {
|
|
143
|
+
if (typeof item === 'string') {
|
|
144
|
+
if (typeof document === 'undefined') return null
|
|
145
|
+
const element = document.querySelector<HTMLElement>(item)
|
|
146
|
+
return element ? { element, read: () => toRect(element.getBoundingClientRect()) } : null
|
|
147
|
+
}
|
|
148
|
+
if (typeof Element !== 'undefined' && item instanceof Element)
|
|
149
|
+
return { element: item as HTMLElement, read: () => toRect(item.getBoundingClientRect()) }
|
|
150
|
+
if (typeof (item as TourVirtualElement).getBoundingClientRect === 'function') {
|
|
151
|
+
const virtual = item as TourVirtualElement
|
|
152
|
+
return { element: null, read: () => toRect(virtual.getBoundingClientRect()) }
|
|
153
|
+
}
|
|
154
|
+
const rect = toRect(item as TourRectLike)
|
|
155
|
+
return { element: null, read: () => rect }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function resolveTourTarget(target: TourTarget | undefined): TourResolvedTarget | null {
|
|
52
159
|
if (!target) return null
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
160
|
+
const value = typeof target === 'function' ? target() : target
|
|
161
|
+
if (!value) return null
|
|
162
|
+
const items = Array.isArray(value) ? value : [value]
|
|
163
|
+
if (!items.length) return null
|
|
164
|
+
const readers: TourItemReader[] = []
|
|
165
|
+
for (const item of items) {
|
|
166
|
+
const reader = item ? readItem(item) : null
|
|
167
|
+
if (!reader) return null
|
|
168
|
+
readers.push(reader)
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
elements: readers.flatMap(reader => (reader.element ? [reader.element] : [])),
|
|
172
|
+
virtual: readers.some(reader => !reader.element),
|
|
173
|
+
rect: () => unionRect(readers.map(reader => reader.read())),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function waitForTourTarget(
|
|
178
|
+
target: TourTarget | undefined,
|
|
179
|
+
timeout: number,
|
|
180
|
+
signal?: AbortSignal,
|
|
181
|
+
): Promise<TourResolvedTarget | null> {
|
|
182
|
+
const found = resolveTourTarget(target)
|
|
183
|
+
if (found || timeout <= 0 || signal?.aborted) return Promise.resolve(found)
|
|
184
|
+
return new Promise(resolve => {
|
|
185
|
+
const check = () => {
|
|
186
|
+
const resolved = resolveTourTarget(target)
|
|
187
|
+
if (resolved) finish(resolved)
|
|
188
|
+
}
|
|
189
|
+
const observer = new MutationObserver(check)
|
|
190
|
+
const interval = window.setInterval(check, 100)
|
|
191
|
+
const timer = window.setTimeout(() => finish(null), timeout)
|
|
192
|
+
const abort = () => finish(null)
|
|
193
|
+
function finish(value: TourResolvedTarget | null) {
|
|
194
|
+
observer.disconnect()
|
|
195
|
+
window.clearInterval(interval)
|
|
196
|
+
window.clearTimeout(timer)
|
|
197
|
+
signal?.removeEventListener('abort', abort)
|
|
198
|
+
resolve(value)
|
|
199
|
+
}
|
|
200
|
+
observer.observe(document.documentElement, {
|
|
201
|
+
childList: true,
|
|
202
|
+
subtree: true,
|
|
203
|
+
attributes: true,
|
|
204
|
+
})
|
|
205
|
+
signal?.addEventListener('abort', abort)
|
|
206
|
+
})
|
|
57
207
|
}
|
|
58
208
|
|
|
59
209
|
export function clampTourStep(step: number | undefined, count: number) {
|
|
@@ -167,11 +317,17 @@ export async function runTourChange(
|
|
|
167
317
|
}
|
|
168
318
|
}
|
|
169
319
|
|
|
170
|
-
export function
|
|
320
|
+
export function tourCardGap(element?: Element | null) {
|
|
321
|
+
return cssTimeToMs(
|
|
322
|
+
getComputedStyle(element ?? document.documentElement).getPropertyValue('--hn-duration-exit'),
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function tourReference(read: () => TourRect | null, contextElement?: Element | null) {
|
|
171
327
|
return {
|
|
172
|
-
contextElement:
|
|
328
|
+
contextElement: contextElement ?? undefined,
|
|
173
329
|
getBoundingClientRect() {
|
|
174
|
-
const rect =
|
|
330
|
+
const rect = read() ?? { x: 0, y: 0, width: 0, height: 0 }
|
|
175
331
|
return {
|
|
176
332
|
...rect,
|
|
177
333
|
left: rect.x,
|
|
@@ -183,23 +339,37 @@ export function tourReference(element: HTMLElement, padding = TOUR_PADDING) {
|
|
|
183
339
|
}
|
|
184
340
|
}
|
|
185
341
|
|
|
186
|
-
export function watchTourTarget(
|
|
342
|
+
export function watchTourTarget(resolved: TourResolvedTarget, onChange: () => void) {
|
|
187
343
|
onChange()
|
|
188
|
-
|
|
344
|
+
const stops = resolved.elements.map(element =>
|
|
345
|
+
autoUpdate(element, document.documentElement, onChange),
|
|
346
|
+
)
|
|
347
|
+
if (resolved.virtual)
|
|
348
|
+
stops.push(
|
|
349
|
+
autoUpdate(
|
|
350
|
+
{ getBoundingClientRect: () => DOMRect.fromRect(resolved.rect()) },
|
|
351
|
+
document.documentElement,
|
|
352
|
+
onChange,
|
|
353
|
+
{ animationFrame: true },
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
return () => {
|
|
357
|
+
for (const stop of stops) stop()
|
|
358
|
+
}
|
|
189
359
|
}
|
|
190
360
|
|
|
191
|
-
export function isFullyVisible(
|
|
192
|
-
const rect = element.getBoundingClientRect()
|
|
361
|
+
export function isFullyVisible(rect: TourRect) {
|
|
193
362
|
return (
|
|
194
|
-
rect.
|
|
195
|
-
rect.
|
|
196
|
-
rect.
|
|
197
|
-
rect.
|
|
363
|
+
rect.y >= 0 &&
|
|
364
|
+
rect.x >= 0 &&
|
|
365
|
+
rect.y + rect.height <= window.innerHeight &&
|
|
366
|
+
rect.x + rect.width <= window.innerWidth
|
|
198
367
|
)
|
|
199
368
|
}
|
|
200
369
|
|
|
201
|
-
export function revealTourTarget(
|
|
202
|
-
|
|
370
|
+
export function revealTourTarget(resolved: TourResolvedTarget) {
|
|
371
|
+
const [element] = resolved.elements
|
|
372
|
+
if (!element || isFullyVisible(resolved.rect())) return
|
|
203
373
|
const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
|
|
204
374
|
element.scrollIntoView({
|
|
205
375
|
block: 'center',
|
|
@@ -210,15 +380,18 @@ export function revealTourTarget(element: HTMLElement) {
|
|
|
210
380
|
|
|
211
381
|
export function hideOutsideTour(elements: (HTMLElement | null | undefined)[]) {
|
|
212
382
|
const present = elements.filter((element): element is HTMLElement => !!element?.isConnected)
|
|
213
|
-
return present.length ?
|
|
383
|
+
return present.length ? hideOutsideLayers(present) : () => {}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function tourOwnsInput(layer: HTMLElement | null | undefined, scripted: boolean) {
|
|
387
|
+
return scripted || isHighestLayer(layer)
|
|
214
388
|
}
|
|
215
389
|
|
|
216
390
|
export function trapTourFocus(
|
|
217
391
|
getContainers: () => (HTMLElement | null | undefined)[],
|
|
218
|
-
|
|
392
|
+
enabled: () => boolean,
|
|
219
393
|
) {
|
|
220
394
|
const containers = () => getContainers().filter((node): node is HTMLElement => !!node)
|
|
221
|
-
const enabled = () => isHighestLayer(getLayer())
|
|
222
395
|
const inside = (node: Node | null) => !!node && containers().some(c => c.contains(node))
|
|
223
396
|
|
|
224
397
|
function candidates() {
|
|
@@ -259,3 +432,8 @@ export function trapTourFocus(
|
|
|
259
432
|
document.removeEventListener('keydown', onKeyDown, true)
|
|
260
433
|
}
|
|
261
434
|
}
|
|
435
|
+
|
|
436
|
+
export function listenTourKeys(onKeyDown: (event: KeyboardEvent) => void) {
|
|
437
|
+
window.addEventListener('keydown', onKeyDown, true)
|
|
438
|
+
return () => window.removeEventListener('keydown', onKeyDown, true)
|
|
439
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hideOutsideLayers } from '../dismissable-layer'
|
|
2
2
|
|
|
3
3
|
export function dialogWarnings({
|
|
4
4
|
titleName = 'DialogTitle',
|
|
@@ -40,7 +40,7 @@ export function hideOutside(element: Element) {
|
|
|
40
40
|
try {
|
|
41
41
|
insideClosedPopover = !!element.closest('[popover]:not(:popover-open)')
|
|
42
42
|
} catch {}
|
|
43
|
-
return insideClosedPopover ? undefined :
|
|
43
|
+
return insideClosedPopover ? undefined : hideOutsideLayers([element])
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
interface OutsideEvent {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { hideOthers } from 'aria-hidden'
|
|
2
|
+
|
|
1
3
|
export type PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>
|
|
2
4
|
export type FocusOutsideEvent = CustomEvent<{ originalEvent: FocusEvent }>
|
|
3
5
|
|
|
@@ -35,11 +37,30 @@ export function addLayer(element: HTMLElement) {
|
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
const branchInline = new Map<HTMLElement, string>()
|
|
41
|
+
|
|
42
|
+
function syncBranchPointerEvents() {
|
|
43
|
+
const list = [...branches]
|
|
44
|
+
for (const branch of list) branch.style.pointerEvents = branchInline.get(branch) ?? ''
|
|
45
|
+
if (blocking.size === 0 || list.length === 0) return
|
|
46
|
+
const body = list[0]!.ownerDocument.body
|
|
47
|
+
const blocked = body.style.pointerEvents
|
|
48
|
+
body.style.pointerEvents = originalBodyPointerEvents ?? ''
|
|
49
|
+
const natural = list.map(branch => getComputedStyle(branch).pointerEvents)
|
|
50
|
+
body.style.pointerEvents = blocked
|
|
51
|
+
list.forEach((branch, index) => (branch.style.pointerEvents = natural[index]!))
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
export function addBranch(element: HTMLElement) {
|
|
55
|
+
if (!branches.has(element)) branchInline.set(element, element.style.pointerEvents)
|
|
39
56
|
branches.add(element)
|
|
57
|
+
syncBranchPointerEvents()
|
|
40
58
|
changed()
|
|
41
59
|
return () => {
|
|
42
|
-
if (branches.delete(element))
|
|
60
|
+
if (!branches.delete(element)) return
|
|
61
|
+
element.style.pointerEvents = branchInline.get(element) ?? ''
|
|
62
|
+
branchInline.delete(element)
|
|
63
|
+
changed()
|
|
43
64
|
}
|
|
44
65
|
}
|
|
45
66
|
|
|
@@ -50,11 +71,13 @@ export function blockOutsidePointerEvents(element: HTMLElement) {
|
|
|
50
71
|
body.style.pointerEvents = 'none'
|
|
51
72
|
}
|
|
52
73
|
blocking.add(element)
|
|
74
|
+
syncBranchPointerEvents()
|
|
53
75
|
changed()
|
|
54
76
|
return () => {
|
|
55
77
|
if (!blocking.delete(element)) return
|
|
56
78
|
if (blocking.size === 0 && originalBodyPointerEvents != null)
|
|
57
79
|
body.style.pointerEvents = originalBodyPointerEvents
|
|
80
|
+
syncBranchPointerEvents()
|
|
58
81
|
changed()
|
|
59
82
|
}
|
|
60
83
|
}
|
|
@@ -62,6 +85,7 @@ export function blockOutsidePointerEvents(element: HTMLElement) {
|
|
|
62
85
|
export function releaseLayer(element: HTMLElement) {
|
|
63
86
|
const removed = layers.delete(element)
|
|
64
87
|
const unblocked = blocking.delete(element)
|
|
88
|
+
if (unblocked) syncBranchPointerEvents()
|
|
65
89
|
if (removed || unblocked) changed()
|
|
66
90
|
}
|
|
67
91
|
|
|
@@ -82,6 +106,7 @@ export function bodyPointerEventsDisabled() {
|
|
|
82
106
|
}
|
|
83
107
|
|
|
84
108
|
export function pointerEventsEnabled(element: HTMLElement | null | undefined) {
|
|
109
|
+
if (inBranch(element ?? null)) return true
|
|
85
110
|
const highest = [...blocking].at(-1)
|
|
86
111
|
return layerIndex(element) >= (highest ? [...layers].indexOf(highest) : -1)
|
|
87
112
|
}
|
|
@@ -95,6 +120,28 @@ export function inBranch(target: EventTarget | null) {
|
|
|
95
120
|
return [...branches].some(branch => branch.contains(target as Node | null))
|
|
96
121
|
}
|
|
97
122
|
|
|
123
|
+
export function hideOutsideLayers(targets: Element[]) {
|
|
124
|
+
const connected = () => [...branches].filter(branch => branch.isConnected)
|
|
125
|
+
let kept = connected()
|
|
126
|
+
const hide = () => {
|
|
127
|
+
const visible = [...targets.filter(target => target.isConnected), ...kept]
|
|
128
|
+
return visible.length ? hideOthers(visible) : () => {}
|
|
129
|
+
}
|
|
130
|
+
let undo = hide()
|
|
131
|
+
const stop = subscribeLayers(() => {
|
|
132
|
+
const next = connected()
|
|
133
|
+
if (next.length === kept.length && next.every((branch, index) => branch === kept[index])) return
|
|
134
|
+
kept = next
|
|
135
|
+
const previous = undo
|
|
136
|
+
undo = hide()
|
|
137
|
+
previous()
|
|
138
|
+
})
|
|
139
|
+
return () => {
|
|
140
|
+
stop()
|
|
141
|
+
undo()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
98
145
|
export function isLayerExist(layer: HTMLElement, target: EventTarget | null) {
|
|
99
146
|
if (!(target instanceof Element)) return false
|
|
100
147
|
const targetLayer = target.closest('[data-dismissable-layer]')
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { inBranch } from '../dismissable-layer'
|
|
2
|
+
|
|
1
3
|
export const AUTOFOCUS_ON_MOUNT = 'focusScope.autoFocusOnMount'
|
|
2
4
|
export const AUTOFOCUS_ON_UNMOUNT = 'focusScope.autoFocusOnUnmount'
|
|
3
5
|
const EVENT_OPTIONS = { bubbles: false, cancelable: true }
|
|
@@ -118,21 +120,21 @@ export function trapFocus(container: HTMLElement, scope: FocusScopeAPI) {
|
|
|
118
120
|
if (scope.paused) return
|
|
119
121
|
const target = event.target as HTMLElement | null
|
|
120
122
|
if (container.contains(target)) lastFocused = target
|
|
121
|
-
else focus(lastFocused, { select: true })
|
|
123
|
+
else if (!inBranch(target)) focus(lastFocused, { select: true })
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
const handleFocusOut = (event: FocusEvent) => {
|
|
125
127
|
if (scope.paused) return
|
|
126
128
|
const related = event.relatedTarget as HTMLElement | null
|
|
127
129
|
if (related === null) return
|
|
128
|
-
if (!container.contains(related)) focus(lastFocused, { select: true })
|
|
130
|
+
if (!container.contains(related) && !inBranch(related)) focus(lastFocused, { select: true })
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
const handleMutations = (mutations: MutationRecord[]) => {
|
|
132
134
|
if (lastFocused === null) return
|
|
133
135
|
if (!mutations.some(mutation => mutation.removedNodes.length > 0)) return
|
|
134
136
|
const active = getActiveElement()
|
|
135
|
-
if (active && container.contains(active)) return
|
|
137
|
+
if (active && (container.contains(active) || inBranch(active))) return
|
|
136
138
|
if (!container.contains(lastFocused)) focus(container)
|
|
137
139
|
}
|
|
138
140
|
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
--hn-bg-subtle: #141414;
|
|
89
89
|
--hn-bg-inset: var(--color-neutral-800);
|
|
90
90
|
--hn-bg-scrim: rgb(0 0 0 / 0.6);
|
|
91
|
+
--hn-bg-veil: color-mix(in srgb, var(--hn-surface) 70%, transparent);
|
|
91
92
|
|
|
92
93
|
--hn-fg-default: var(--color-neutral-150);
|
|
93
94
|
--hn-fg-max: var(--color-neutral-0);
|
|
@@ -13,8 +13,42 @@
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
@keyframes hn-tour-ripple {
|
|
17
|
+
from {
|
|
18
|
+
opacity: 0.6;
|
|
19
|
+
transform: translate(-50%, -50%) scale(0.2);
|
|
20
|
+
}
|
|
21
|
+
to {
|
|
22
|
+
opacity: 0;
|
|
23
|
+
transform: translate(-50%, -50%) scale(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@keyframes hn-tour-press {
|
|
28
|
+
from {
|
|
29
|
+
opacity: 0.15;
|
|
30
|
+
transform: translate(-50%, -50%) scale(0.4);
|
|
31
|
+
}
|
|
32
|
+
to {
|
|
33
|
+
opacity: 0.45;
|
|
34
|
+
transform: translate(-50%, -50%) scale(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@keyframes hn-tour-key-press {
|
|
39
|
+
50% {
|
|
40
|
+
scale: 0.92;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
16
44
|
@utility hn-tour-mask {
|
|
17
|
-
|
|
45
|
+
&[data-hn-tour-mask] {
|
|
46
|
+
z-index: calc(var(--hn-z-overlay) + 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&[data-mask='dim'] {
|
|
50
|
+
backdrop-filter: none;
|
|
51
|
+
}
|
|
18
52
|
|
|
19
53
|
&[data-state='open'][data-hn-tour-hole='shown'] {
|
|
20
54
|
animation:
|
|
@@ -24,6 +58,77 @@
|
|
|
24
58
|
&[data-state='open'][data-hn-tour-hole='hidden'] {
|
|
25
59
|
animation:
|
|
26
60
|
hn-fade-in var(--hn-duration-base) var(--hn-ease-enter),
|
|
27
|
-
hn-tour-hole-out var(--hn-duration-
|
|
61
|
+
hn-tour-hole-out var(--hn-duration-base) var(--hn-ease-move);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@utility hn-tour-pointer {
|
|
66
|
+
position: fixed;
|
|
67
|
+
left: 0;
|
|
68
|
+
top: 0;
|
|
69
|
+
z-index: calc(var(--hn-z-overlay) + 2);
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
opacity: 0;
|
|
72
|
+
transition: opacity var(--hn-duration-fast) var(--hn-ease-move);
|
|
73
|
+
|
|
74
|
+
&[data-state='shown'] {
|
|
75
|
+
opacity: 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
& [data-hn-tour-pointer-glyph] {
|
|
79
|
+
transition: scale var(--hn-duration-fast) var(--hn-ease-press);
|
|
80
|
+
}
|
|
81
|
+
&[data-kind='mouse'] [data-hn-tour-pointer-glyph] {
|
|
82
|
+
transform-origin: 4px 2px;
|
|
83
|
+
}
|
|
84
|
+
&[data-pressed] [data-hn-tour-pointer-glyph] {
|
|
85
|
+
scale: 0.85;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
& [data-hn-tour-ripple],
|
|
89
|
+
& [data-hn-tour-pointer-ring] {
|
|
90
|
+
position: absolute;
|
|
91
|
+
left: 0;
|
|
92
|
+
top: 0;
|
|
93
|
+
width: 3rem;
|
|
94
|
+
height: 3rem;
|
|
95
|
+
border-radius: 9999px;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
}
|
|
98
|
+
& [data-hn-tour-ripple] {
|
|
99
|
+
border: 2px solid var(--hn-accent);
|
|
100
|
+
animation: hn-tour-ripple var(--hn-duration-slow) var(--hn-ease-enter) forwards;
|
|
101
|
+
}
|
|
102
|
+
& [data-hn-tour-pointer-ring] {
|
|
103
|
+
background-color: var(--hn-accent);
|
|
104
|
+
opacity: 0;
|
|
105
|
+
transform: translate(-50%, -50%);
|
|
106
|
+
}
|
|
107
|
+
&[data-pressed='long'] [data-hn-tour-pointer-ring] {
|
|
108
|
+
animation: hn-tour-press var(--hn-tour-press, 800ms) linear forwards;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (prefers-reduced-motion: reduce) {
|
|
112
|
+
& [data-hn-tour-ripple],
|
|
113
|
+
&[data-pressed='long'] [data-hn-tour-pointer-ring] {
|
|
114
|
+
animation: none;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@utility hn-tour-key {
|
|
120
|
+
position: fixed;
|
|
121
|
+
left: 0;
|
|
122
|
+
top: 0;
|
|
123
|
+
z-index: calc(var(--hn-z-overlay) + 2);
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
opacity: 0;
|
|
126
|
+
transition: opacity var(--hn-duration-fast) var(--hn-ease-move);
|
|
127
|
+
|
|
128
|
+
&[data-state='shown'] {
|
|
129
|
+
opacity: 1;
|
|
130
|
+
}
|
|
131
|
+
&[data-state='shown'] > * {
|
|
132
|
+
animation: hn-tour-key-press var(--hn-duration-base) var(--hn-ease-press);
|
|
28
133
|
}
|
|
29
134
|
}
|
|
@@ -17,6 +17,9 @@ export const image = tv({
|
|
|
17
17
|
lazy: {
|
|
18
18
|
true: 'transition-opacity duration-(--hn-duration-base) ease-enter motion-reduce:transition-none',
|
|
19
19
|
},
|
|
20
|
+
placeholder: {
|
|
21
|
+
true: '',
|
|
22
|
+
},
|
|
20
23
|
fit: {
|
|
21
24
|
cover: 'object-cover',
|
|
22
25
|
contain: 'object-contain',
|
|
@@ -25,6 +28,31 @@ export const image = tv({
|
|
|
25
28
|
'scale-down': 'object-scale-down',
|
|
26
29
|
},
|
|
27
30
|
},
|
|
31
|
+
compoundVariants: [
|
|
32
|
+
{
|
|
33
|
+
lazy: true,
|
|
34
|
+
placeholder: true,
|
|
35
|
+
class: 'duration-(--hn-duration-slow) ease-move',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
defaultVariants: { fit: 'cover' },
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const imageFrame = tv({
|
|
42
|
+
base: 'relative isolate block size-full',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const imagePlaceholder = tv({
|
|
46
|
+
base: 'bg-center bg-no-repeat',
|
|
47
|
+
variants: {
|
|
48
|
+
fit: {
|
|
49
|
+
cover: 'bg-cover',
|
|
50
|
+
contain: 'bg-contain',
|
|
51
|
+
fill: 'bg-size-[100%_100%]',
|
|
52
|
+
none: 'bg-cover',
|
|
53
|
+
'scale-down': 'bg-contain',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
28
56
|
defaultVariants: { fit: 'cover' },
|
|
29
57
|
})
|
|
30
58
|
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { tv } from '../lib/tv'
|
|
2
2
|
|
|
3
|
+
const layer = 'z-[calc(var(--hn-z-overlay)+1)]'
|
|
4
|
+
|
|
3
5
|
export const tourMask = tv({ base: 'hn-scrim hn-tour-mask' })
|
|
4
6
|
|
|
5
|
-
export const tourBlocker = tv({ base:
|
|
7
|
+
export const tourBlocker = tv({ base: `fixed ${layer}` })
|
|
6
8
|
|
|
7
9
|
export const tourCard = tv({
|
|
8
|
-
base:
|
|
10
|
+
base: `relative ${layer} flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-3 overflow-visible p-4 shadow-lg outline-none`,
|
|
9
11
|
})
|
|
10
12
|
|
|
11
13
|
export const tourCentered = tv({
|
|
12
|
-
base:
|
|
14
|
+
base: `pointer-events-none fixed inset-0 ${layer} grid place-items-center p-4 [&>*]:pointer-events-auto`,
|
|
13
15
|
})
|
|
14
16
|
|
|
15
17
|
export const tourBeacon = tv({
|
|
16
|
-
base:
|
|
18
|
+
base: `hn-focus-ring fixed ${layer} size-4 -translate-x-1/2 -translate-y-1/2 cursor-pointer rounded-full`,
|
|
17
19
|
})
|
|
18
20
|
|
|
19
21
|
export const tourBeaconPulse = tv({
|
|
@@ -27,3 +29,19 @@ export const tourBeaconDot = tv({
|
|
|
27
29
|
export const tourHeader = tv({ base: 'flex items-start gap-2' })
|
|
28
30
|
|
|
29
31
|
export const tourFooter = tv({ base: 'flex items-center gap-2' })
|
|
32
|
+
|
|
33
|
+
export const tourPointer = tv({ base: 'hn-tour-pointer' })
|
|
34
|
+
|
|
35
|
+
export const tourPointerArrow = tv({
|
|
36
|
+
base: 'fill-fg stroke-surface absolute top-0 size-6 -translate-x-1 -translate-y-0.5 drop-shadow-md',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const tourPointerTouch = tv({
|
|
40
|
+
base: 'bg-fg/30 ring-surface absolute top-0 block size-9 -translate-x-1/2 -translate-y-1/2 rounded-full shadow-md ring-2',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const tourKey = tv({ base: 'hn-tour-key' })
|
|
44
|
+
|
|
45
|
+
export const tourKeyCap = tv({
|
|
46
|
+
base: 'border-line bg-surface text-fg block min-w-8 -translate-x-1/2 rounded-md border px-2 py-1 text-center text-sm font-medium whitespace-nowrap shadow-md',
|
|
47
|
+
})
|