Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
segunadebayo committed Sep 25, 2024
1 parent 48c6caf commit a510334
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 19 deletions.
2 changes: 1 addition & 1 deletion examples/next-ts/pages/tour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function Page() {
steps: tourData,
}),
{
context: controls.context,
// context: controls.context,
},
)

Expand Down
2 changes: 1 addition & 1 deletion packages/machines/tour/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export type {
StepChangeDetails,
StepDetails,
StepEffectArgs,
StepInit,
StepBaseDetails as StepInit,
StepStatus,
} from "./tour.types"
50 changes: 42 additions & 8 deletions packages/machines/tour/src/tour.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { getPlacement } from "@zag-js/popper"
import { compact, isEqual, isString, nextIndex, prevIndex } from "@zag-js/utils"
import { createFocusTrap, type FocusTrap } from "focus-trap"
import { dom } from "./tour.dom"
import type { MachineContext, MachineState, StepInit, UserDefinedContext } from "./tour.types"
import type { MachineContext, MachineState, StepBaseDetails, UserDefinedContext } from "./tour.types"
import { getCenterRect, isEventInRect, offset } from "./utils/rect"
import { waitForFn } from "./utils/wait-for"

export function machine(userContext: UserDefinedContext) {
const ctx = compact(userContext)
Expand Down Expand Up @@ -198,12 +199,16 @@ export function machine(userContext: UserDefinedContext) {
const targetEl = ctx.currentStep?.target?.()
if (!targetEl) return

if (ctx.preventInteraction) targetEl.inert = true
if (ctx.preventInteraction) {
targetEl.inert = true
}

targetEl.setAttribute("data-tour-highlighted", "")

ctx._targetCleanup = () => {
if (ctx.preventInteraction) targetEl.inert = false
if (ctx.preventInteraction) {
targetEl.inert = false
}
targetEl.removeAttribute("data-tour-highlighted")
}
},
Expand Down Expand Up @@ -265,7 +270,7 @@ export function machine(userContext: UserDefinedContext) {
trapFocus(ctx) {
let trap: FocusTrap | undefined

const rafCleanup = raf(() => {
const cleanup = raf(() => {
const contentEl = dom.getContentEl(ctx)
if (!contentEl) return

Expand All @@ -285,7 +290,7 @@ export function machine(userContext: UserDefinedContext) {

return () => {
trap?.deactivate()
rafCleanup?.()
cleanup?.()
}
},
trackPlacement(ctx) {
Expand Down Expand Up @@ -356,25 +361,54 @@ const set = {

if (!step) {
ctx.step = null
invoke.stepChange(ctx)
return
}

if (isEqual(ctx.step, step.id)) return

const update = (data: Partial<StepInit>) => {
const update = (data: Partial<StepBaseDetails>) => {
ctx.steps[idx] = { ...step, ...data }
}

const next = () => {
const idx = nextIndex(ctx.steps, ctx.currentStepIndex)
ctx.step = ctx.steps[idx].id
invoke.stepChange(ctx)
}

const goto = (id: string) => {
const idx = ctx.steps.findIndex((s) => s.id === id)
ctx.step = ctx.steps[idx].id
invoke.stepChange(ctx)
}

const dismiss = () => {
ctx.step = null
invoke.stepChange(ctx)
ctx.onStatusChange?.({ status: "stopped", step: ctx.step })
}

const done = () => {
ctx.step = step.id
invoke.stepChange(ctx)
}

if (!step.effect) {
next()
done()
return
}

ctx._effectCleanup = step.effect({ next, update })
const waitFor = waitForFn(dom.getRootNode(ctx))

ctx._effectCleanup = step.effect({
done,
next,
update,
target: step.target,
dismiss,
goto,
waitFor,
})
},
}
15 changes: 12 additions & 3 deletions packages/machines/tour/src/tour.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ import type { CommonProperties, DirectionProperty, PropTypes, RequiredBy } from

export interface StepEffectArgs {
next(): void
update(args: Partial<StepInit>): void
goto(id: string): void
waitFor(fn: () => boolean | undefined): Promise<void>
dismiss(): void
done(): void
update(args: Partial<StepBaseDetails>): void
target?: () => HTMLElement | null
}

export interface StepInit {
export interface StepBaseDetails {
/**
* Function to return the target element to highlight
*/
Expand All @@ -39,7 +44,7 @@ export interface StepInit {
meta?: Record<string, any>
}

export interface StepDetails extends StepInit {
export interface StepDetails extends StepBaseDetails {
/**
* The unique identifier of the step
*/
Expand All @@ -48,6 +53,10 @@ export interface StepDetails extends StepInit {
* The effect to run before the step is shown
*/
effect?(args: StepEffectArgs): VoidFunction
/**
* Whether to show a backdrop behind the step
*/
backdrop?: boolean
}

export interface StepChangeDetails {
Expand Down
36 changes: 30 additions & 6 deletions packages/machines/tour/src/utils/rect.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getWindow } from "@zag-js/dom-query"
import type { MachineContext, Offset } from "../tour.types"

type Rect = Required<MachineContext["currentRect"]>
Expand All @@ -6,13 +7,36 @@ export function getCenterRect(size: MachineContext["boundarySize"]) {
return { x: size.width / 2, y: size.height / 2, width: 0, height: 0 }
}

function getFrameElement(win: Window): Element | null {
return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null
}

const normalizeEventPoint = (event: PointerEvent) => {
let clientX = event.clientX
let clientY = event.clientY

let currentWin = event.view || window
let currentIFrame = getFrameElement(currentWin)

while (currentIFrame) {
const iframeRect = currentIFrame.getBoundingClientRect()
const css = getComputedStyle(currentIFrame)
const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft))
const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop))

clientX += left
clientY += top

currentWin = getWindow(currentIFrame)
currentIFrame = getFrameElement(currentWin)
}

return { clientX, clientY }
}

export function isEventInRect(rect: Rect, event: PointerEvent) {
return (
rect.y <= event.clientY &&
event.clientY <= rect.y + rect.height &&
rect.x <= event.clientX &&
event.clientX <= rect.x + rect.width
)
const { clientX, clientY } = normalizeEventPoint(event)
return rect.y <= clientY && clientY <= rect.y + rect.height && rect.x <= clientX && clientX <= rect.x + rect.width
}

export function offset(r: Rect, i: Offset): Rect {
Expand Down
27 changes: 27 additions & 0 deletions packages/machines/tour/src/utils/wait-for.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getWindow } from "@zag-js/dom-query"

export const waitForFn = (rootNode: Document | ShadowRoot) => {
return function waitFor(fn: () => boolean | undefined) {
return new Promise<void>((resolve, reject) => {
const win = getWindow(rootNode)

const observer = new win.MutationObserver(() => {
if (fn()) {
resolve()
observer.disconnect()
}
})

observer.observe(rootNode, {
childList: true,
subtree: true,
characterData: true,
})

setTimeout(() => {
observer.disconnect()
reject(new Error("Timeout"))
}, 3000)
})
}
}
18 changes: 18 additions & 0 deletions shared/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,24 @@ export const tourData = [
title: "Step 1. Welcome",
description: "To the new world",
target: () => document.querySelector<HTMLElement>("#step-1"),
effect({ done, next, target }: any) {
const el = target()
done()
el?.addEventListener("click", next)
return () => el?.removeEventListener("click", next)
},
// actions: [
// { label: "Prev", action: "prev" },
// { label: "Next", action: "next" },
// {
// label: "Go to Project",
// action({ next, goto, dismiss, waitFor }: any) {
// waitFor(() => document.getElementById("dfdf")).then(() => {
// next()
// })
// },
// },
// ],
// effect({ next, update }: any) {
// const abort = new AbortController()

Expand Down

0 comments on commit a510334

Please sign in to comment.