diff --git a/.changeset/config.json b/.changeset/config.json
index e13042f0c2..7c3d4c9697 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -24,6 +24,7 @@
"preact-ts",
"svelte-ts",
"vanilla-ts",
+ "lit-ts",
"website"
]
}
diff --git a/e2e/_utils.ts b/e2e/_utils.ts
index 3ea3d7470d..2592d93aaf 100644
--- a/e2e/_utils.ts
+++ b/e2e/_utils.ts
@@ -1,15 +1,65 @@
import AxeBuilder from "@axe-core/playwright"
import { expect, type Locator, type Page } from "@playwright/test"
-export async function a11y(page: Page, selector = "[data-part=root]") {
+// Types and Framework Context Detection
+export type DomMode = "shadow-dom" | "light-dom"
+
+// The context is determined by environment variables at test runtime
+// VITE_DOM_MODE is the primary control (matches client-side behavior)
+// Examples:
+// - `VITE_DOM_MODE=shadow-dom FRAMEWORK=react npx playwright test` (force Shadow DOM in React)
+// - `VITE_DOM_MODE=light-dom FRAMEWORK=lit npx playwright test` (force Light DOM in Lit)
+// - `FRAMEWORK=lit npx playwright test` (default Lit behavior: Shadow DOM)
+export const DOM_MODE: DomMode =
+ process.env.VITE_DOM_MODE === "light-dom" ? "light-dom" : process.env.FRAMEWORK === "lit" ? "shadow-dom" : "light-dom"
+
+console.log(`Running E2E tests in '${DOM_MODE}' context for '${process.env.FRAMEWORK}'.`)
+
+/**
+ * Context-aware utility to locate a component part defined by a 'part' or 'data-part' attribute.
+ * This function abstracts the structural differences between Shadow DOM and Light DOM implementations.
+ *
+ * @param parent The root Playwright Page or a parent Locator to search within
+ * @param hostSelector A unique selector for the component's host/root element (e.g., 'accordion-page', '[data-testid="accordion-root"]')
+ * @param partName The name of the part to locate (e.g., 'trigger', 'content')
+ * @returns A Playwright Locator for the requested part
+ */
+export function getPart(parent: Page | Locator, hostSelector: string, partName: string): Locator {
+ // Use [part="..."] for CSS Shadow Parts standard, fallback to [data-part="..."]
+ const partSelector = `[part="${partName}"], [data-part="${partName}"]`
+
+ if (DOM_MODE === "shadow-dom") {
+ // For Shadow DOM, use a descendant combinator. Playwright's engine will
+ // pierce the shadow root of the element matching hostSelector.
+ return parent.locator(`${hostSelector} ${partSelector}`)
+ } else {
+ // For Light DOM, the structure might be a direct child or a deeper descendant.
+ // In many simple cases, the selector is identical to the shadow DOM version.
+ return parent.locator(`${hostSelector} ${partSelector}`)
+ }
+}
+
+/**
+ * Performs an accessibility scan, optionally scoped to a specific selector.
+ * @param page The Playwright Page object
+ * @param selector Optional selector to scope the scan (defaults to "[data-part=root]")
+ * @param hostSelector A CSS selector for a shadow host element
+ */
+export async function a11y(page: Page, selector = "[data-part=root]", hostSelector?: string) {
await page.waitForSelector(selector)
- const results = await new AxeBuilder({ page: page as any })
- .disableRules(["color-contrast"])
- .include(selector)
- .analyze()
+ let selection = new AxeBuilder({ page: page as any }).disableRules(["color-contrast"])
+
+ if (hostSelector && DOM_MODE === "shadow-dom") {
+ selection = selection.include(hostSelector)
+ }
+ if (selector) {
+ selection = selection.include(selector)
+ }
+
+ const accessibilityScanResults = await selection.analyze()
- expect(results.violations).toEqual([])
+ expect(accessibilityScanResults.violations).toEqual([])
}
export const testid = (part: string) => `[data-testid=${esc(part)}]`
diff --git a/e2e/checkbox.e2e.ts b/e2e/checkbox.e2e.ts
index bcd7fdf71b..219b0c7527 100644
--- a/e2e/checkbox.e2e.ts
+++ b/e2e/checkbox.e2e.ts
@@ -1,64 +1,66 @@
-import { expect, type Page, test } from "@playwright/test"
-import { a11y, controls, part, testid } from "./_utils"
+import { expect, test } from "@playwright/test"
+import { CheckboxModel } from "./models/checkbox.model"
-const root = part("root")
-const label = part("label")
-const control = part("control")
-const input = testid("hidden-input")
-
-const expectToBeChecked = async (page: Page) => {
- await expect(page.locator(root)).toHaveAttribute("data-state", "checked")
- await expect(page.locator(label)).toHaveAttribute("data-state", "checked")
- await expect(page.locator(control)).toHaveAttribute("data-state", "checked")
-}
+let I: CheckboxModel
test.beforeEach(async ({ page }) => {
- await page.goto("/checkbox")
+ I = new CheckboxModel(page)
+ await I.goto()
})
-test("should have no accessibility violation", async ({ page }) => {
- await a11y(page)
+test("should have no accessibility violation", async () => {
+ await I.checkAccessibility()
})
-test("should be checked when clicked", async ({ page }) => {
- await page.click(root)
- await expectToBeChecked(page)
+test("should be checked when clicked", async () => {
+ await I.root.click()
+ await I.expectToBeChecked()
})
test("should be focused when page is tabbed", async ({ page }) => {
await page.click("main")
await page.keyboard.press("Tab")
- await expect(page.locator(input)).toBeFocused()
- await expect(page.locator(control)).toHaveAttribute("data-focus", "")
+ await expect(I.input).toBeFocused()
+ await expect(I.control).toHaveAttribute("data-focus", "")
})
test("should be checked when spacebar is pressed while focused", async ({ page }) => {
await page.click("main")
await page.keyboard.press("Tab")
await page.keyboard.press(" ")
- await expectToBeChecked(page)
+ await I.expectToBeChecked()
})
-test("should have disabled attributes when disabled", async ({ page }) => {
- await controls(page).bool("disabled")
- await expect(page.locator(input)).toBeDisabled()
+test("should have disabled attributes when disabled", async () => {
+ await I.controls.bool("disabled")
+ await I.expectToBeDisabled()
})
test("should not be focusable when disabled", async ({ page }) => {
- await controls(page).bool("disabled")
+ await I.controls.bool("disabled")
await page.click("main")
await page.keyboard.press("Tab")
- await expect(page.locator(input)).not.toBeFocused()
+ await expect(I.input).not.toBeFocused()
})
test("input is not blurred on label click", async ({ page }) => {
let blurCount = 0
await page.exposeFunction("trackBlur", () => blurCount++)
- await page.locator(input).evaluate((input) => {
+ await I.input.evaluate((input) => {
input.addEventListener("blur", (window as any).trackBlur)
})
- await page.click(label)
- await page.click(label)
- await page.click(label)
+ await I.label.click()
+ await I.label.click()
+ await I.label.click()
expect(blurCount).toBe(0)
})
+
+test("reset form should restore initial state", async () => {
+ await expect(I.input).not.toBeChecked()
+
+ await I.label.click()
+ await expect(I.input).toBeChecked()
+
+ await I.resetButton.click()
+ await expect(I.input).not.toBeChecked()
+})
diff --git a/e2e/collapsible.e2e.ts b/e2e/collapsible.e2e.ts
index 5ef3218caf..1de285e6eb 100644
--- a/e2e/collapsible.e2e.ts
+++ b/e2e/collapsible.e2e.ts
@@ -1,30 +1,34 @@
import { expect, test } from "@playwright/test"
-import { a11y, part } from "./_utils"
+import { CollapsibleModel } from "./models/collapsible.model"
-const trigger = part("trigger")
-const content = part("content")
+let I: CollapsibleModel
test.describe("collapsible", () => {
test.beforeEach(async ({ page }) => {
- await page.goto("/collapsible")
+ I = new CollapsibleModel(page)
+ await I.goto()
})
- test("should have no accessibility violation", async ({ page }) => {
- await a11y(page)
+ test("should have no accessibility violation", async () => {
+ await I.checkAccessibility()
})
- test("[toggle] should be open when clicked", async ({ page }) => {
- await page.click(trigger)
- await expect(page.locator(content)).toBeVisible()
+ test("[toggle] should be open when clicked", async () => {
+ await I.clickTrigger()
+ await I.seeContent()
- await page.click(trigger)
- await expect(page.locator(content)).not.toBeVisible()
+ await I.clickTrigger()
+ await I.dontSeeContent()
})
- test.skip("[closed] content should not be reachable via tab key", async ({ page }) => {
- await page.click(trigger)
- await page.click(trigger)
- await page.keyboard.press("Tab")
- await expect(page.getByRole("button", { name: "Open" })).toBeFocused()
+ test("[closed] content should not be reachable via tab key", async () => {
+ await I.clickTrigger()
+ await I.seeContent()
+
+ await I.clickTrigger()
+ await I.dontSeeContent()
+
+ await I.pressKey("Tab")
+ await expect(I.host.getByTestId("open-button")).toBeFocused()
})
})
diff --git a/e2e/menu-nested.e2e.ts b/e2e/menu-nested.e2e.ts
index 9c0031cb0a..5560daa95a 100644
--- a/e2e/menu-nested.e2e.ts
+++ b/e2e/menu-nested.e2e.ts
@@ -1,50 +1,8 @@
-import { expect, type Page, test } from "@playwright/test"
-import { clickOutside, rect, testid } from "./_utils"
-
-const menu_1 = {
- trigger: testid("trigger"),
- menu: testid("menu"),
- sub_trigger: testid("more-tools"),
-}
-
-const menu_2 = {
- trigger: testid("more-tools"),
- menu: testid("more-tools-submenu"),
- sub_trigger: testid("open-nested"),
-}
-
-const menu_3 = {
- trigger: testid("open-nested"),
- menu: testid("open-nested-submenu"),
-}
-
-const expectToBeFocused = async (page: Page, id: string) => {
- return await expect(page.locator(id).first()).toHaveAttribute("data-highlighted", "")
-}
-
-const navigateToSubmenuTrigger = async (page: Page) => {
- await page.click(menu_1.trigger)
- await page.keyboard.press("ArrowDown")
- await page.keyboard.press("ArrowDown")
- await page.keyboard.press("ArrowDown")
- await page.keyboard.press("ArrowDown")
-}
-
-async function expectSubmenuToBeFocused(page: Page) {
- await expect(page.locator(menu_2.menu)).toBeVisible()
- await expect(page.locator(menu_2.menu)).toBeFocused()
- await expectToBeFocused(page, menu_2.trigger)
-}
-
-async function expectAllMenusToBeClosed(page: Page) {
- // close all
- await expect(page.locator(menu_1.menu)).toBeHidden()
- await expect(page.locator(menu_2.menu)).toBeHidden()
- await expect(page.locator(menu_3.menu)).toBeHidden()
-
- // focus trigger
- await expect(page.locator(menu_1.trigger)).toBeFocused()
-}
+import { expect, test } from "@playwright/test"
+import { MenuNestedModel } from "./models/menu-nested.model"
+import { clickOutside, rect } from "./_utils"
+
+let I: MenuNestedModel
test.describe("nested menu / pointer", async () => {
test.beforeEach(async ({ page }) => {
@@ -54,166 +12,179 @@ test.describe("nested menu / pointer", async () => {
test.describe("nested menu / keyboard navigation", async () => {
test.beforeEach(async ({ page }) => {
- await page.goto("/menu-nested")
+ I = new MenuNestedModel(page)
+ await I.goto()
})
- test("open submenu when moving focus to trigger", async ({ page }) => {
- await navigateToSubmenuTrigger(page)
- await expect(page.locator(menu_2.menu)).toBeHidden()
+ test("open submenu when moving focus to trigger", async () => {
+ await I.navigateToSubmenuTrigger()
+ await expect(I.menu2.menu).toBeHidden()
})
- test("open submenu with space", async ({ page }) => {
- await navigateToSubmenuTrigger(page)
- await page.keyboard.press("Space")
- await page.waitForTimeout(1)
- await expectSubmenuToBeFocused(page)
+ test("open submenu with space", async () => {
+ await I.navigateToSubmenuTrigger()
+ await I.page.keyboard.press("Space")
+ await I.page.waitForTimeout(1)
+ await I.expectSubmenuToBeFocused()
})
- test("open submenu with enter", async ({ page }) => {
- await navigateToSubmenuTrigger(page)
- await page.keyboard.press("Enter")
- await expectSubmenuToBeFocused(page)
+ test("open submenu with enter", async () => {
+ await I.navigateToSubmenuTrigger()
+ await I.page.keyboard.press("Enter")
+ await I.expectSubmenuToBeFocused()
})
- test("open submenu with arrow right", async ({ page }) => {
- await navigateToSubmenuTrigger(page)
- await page.keyboard.press("ArrowRight")
- await expectSubmenuToBeFocused(page)
+ test("open submenu with arrow right", async () => {
+ await I.navigateToSubmenuTrigger()
+ await I.page.keyboard.press("ArrowRight")
+ await I.expectSubmenuToBeFocused()
})
- test("close submenu with arrow left", async ({ page }) => {
- await navigateToSubmenuTrigger(page)
- await page.keyboard.press("Enter", { delay: 20 })
- await page.keyboard.press("ArrowLeft")
- await expect(page.locator(menu_2.menu)).toBeHidden()
+ test("close submenu with arrow left", async () => {
+ await I.navigateToSubmenuTrigger()
+ await I.page.keyboard.press("Enter", { delay: 20 })
+ await I.page.keyboard.press("ArrowLeft")
+ await expect(I.menu2.menu).toBeHidden()
})
})
test.describe("nested menu / keyboard typeahead", async () => {
test.beforeEach(async ({ page }) => {
- await page.goto("/menu-nested")
+ I = new MenuNestedModel(page)
+ await I.goto()
})
- test("parent menu", async ({ page }) => {
- await page.click(menu_1.trigger)
+ test("parent menu", async () => {
+ await I.menu1.trigger.click()
+ await expect(I.menu1.menu).toBeFocused()
- await page.keyboard.type("n")
- await expectToBeFocused(page, testid("new-file"))
+ await I.page.keyboard.type("n")
+ await I.expectToBeHighlighted(I.getTestId("new-file"))
- await page.keyboard.type("n")
- await expectToBeFocused(page, testid("new-tab"))
+ await I.page.keyboard.type("n")
+ await I.expectToBeHighlighted(I.getTestId("new-tab"))
- await page.keyboard.type("new w")
- await expectToBeFocused(page, testid("new-win"))
+ await I.page.keyboard.type("new w")
+ await I.expectToBeHighlighted(I.getTestId("new-win"))
})
- test.skip("nested menu", async ({ page }) => {
- await page.click(menu_1.trigger)
+ test("nested menu", async () => {
+ await I.menu1.trigger.click()
+ await expect(I.menu1.menu).toBeFocused()
- await page.keyboard.type("m")
- await expectToBeFocused(page, testid("more-tools"))
+ await I.page.keyboard.type("m")
+ await I.expectToBeHighlighted(I.getTestId("more-tools"))
// open submenu
- await page.keyboard.press("Enter")
- await expect(page.locator(menu_2.menu)).toBeVisible()
+ await I.page.keyboard.press("Enter")
+ await I.expectSubmenuToBeFocused()
- await page.keyboard.type("s")
- await expectToBeFocused(page, testid("switch-win"))
+ await I.page.keyboard.type("s")
+ await I.expectToBeHighlighted(I.getTestId("switch-win"))
})
})
-test.describe("nested menu / select item", async () => {
+test.describe("nested menu / select item", () => {
test.beforeEach(async ({ page }) => {
- await page.goto("/menu-nested")
+ I = new MenuNestedModel(page)
+ await I.goto()
})
- test("using keyboard", async ({ page }) => {
- await navigateToSubmenuTrigger(page)
- await page.keyboard.press("Enter", { delay: 10 })
+ test("using keyboard", async () => {
+ await I.navigateToSubmenuTrigger()
+ await I.page.keyboard.press("Enter")
+ await I.expectSubmenuToBeFocused()
// open menu 3
- await page.keyboard.press("ArrowDown")
- await page.keyboard.press("ArrowDown")
- await page.keyboard.press("ArrowDown")
- await page.keyboard.press("Enter", { delay: 10 })
+ await I.page.keyboard.press("ArrowDown")
+ await I.page.keyboard.press("ArrowDown")
+ await I.page.keyboard.press("ArrowDown")
+ await I.page.keyboard.press("Enter")
+ await expect(I.menu3.menu).toBeFocused()
// select first item in menu 3
- await page.keyboard.press("Enter", { delay: 10 })
- await expectAllMenusToBeClosed(page)
+ await I.page.keyboard.press("Enter")
+ await I.expectAllMenusToBeClosed()
})
- test("using pointer click", async ({ page }) => {
- await page.click(menu_1.trigger)
- await page.hover(menu_1.sub_trigger)
- await page.hover(menu_2.sub_trigger)
+ test("using pointer click", async () => {
+ await I.menu1.trigger.click()
+ await expect(I.menu1.menu).toBeFocused()
+ await I.menu1.subTrigger.hover()
+ await I.menu2.subTrigger.hover()
- await page.hover(testid("playground"))
- await page.click(testid("playground"))
- await expectAllMenusToBeClosed(page)
+ await I.getTestId("playground").hover()
+ await I.getTestId("playground").click()
+ await I.expectAllMenusToBeClosed()
})
- test("clicking outside or blur", async ({ page }) => {
- await page.click(menu_1.trigger)
- await page.hover(menu_2.trigger)
- await page.hover("body", { position: { x: 10, y: 10 } })
- await clickOutside(page)
- await expectAllMenusToBeClosed(page)
+ test("clicking outside or blur", async () => {
+ await I.menu1.trigger.click()
+ await I.menu2.trigger.hover()
+ await I.page.hover("body", { position: { x: 10, y: 10 } })
+ await clickOutside(I.page)
+ await I.expectAllMenusToBeClosed()
})
})
test.describe("nested menu / pointer movement", async () => {
test.beforeEach(async ({ page }) => {
- await page.goto("/menu-nested")
+ I = new MenuNestedModel(page)
+ await I.goto()
})
- test("should open submenu and not focus first item", async ({ page }) => {
- await page.click(menu_1.trigger)
- await page.hover(menu_1.sub_trigger)
+ test("should open submenu and not focus first item", async () => {
+ await I.menu1.trigger.click()
+ // await expect(I.menu1.menu).toBeFocused()
+ await I.menu1.subTrigger.hover()
- await expect(page.locator(menu_2.menu)).toBeVisible()
- await expect(page.locator(menu_2.menu)).toBeFocused()
- await expectToBeFocused(page, menu_2.trigger)
+ await expect(I.menu2.menu).toBeVisible()
+ await expect(I.menu2.menu).toBeFocused()
+ await I.expectToBeHighlighted(I.menu2.trigger)
- const focusedItemCount = await page.locator(menu_2.menu).locator("[data-focus]").count()
+ const focusedItemCount = await I.menu2.menu.locator("[data-focus]").count()
expect(focusedItemCount).toBe(0)
})
- test("should not close when moving pointer to submenu and back to parent trigger", async ({ page }) => {
- await page.click(menu_1.trigger)
+ test("should not close when moving pointer to submenu and back to parent trigger", async () => {
+ await I.menu1.trigger.click()
+
+ await I.menu1.subTrigger.hover()
- await page.hover(menu_1.sub_trigger)
- await page.hover(testid("save-page"))
- await page.hover(menu_1.sub_trigger)
+ await I.getTestId("save-page").hover()
+ await I.menu1.subTrigger.hover()
- await expect(page.locator(menu_1.menu)).toBeVisible()
- await expect(page.locator(menu_2.menu)).toBeVisible()
+ await expect(I.menu1.menu).toBeVisible()
+ await expect(I.menu2.menu).toBeVisible()
})
- test("should close submenu when moving pointer away", async ({ page }) => {
- await page.click(menu_1.trigger)
- await page.hover(menu_2.trigger)
+ test("should close submenu when moving pointer away", async () => {
+ await I.menu1.trigger.click()
+ await I.menu2.trigger.hover()
- const menu_el = page.locator(menu_2.trigger)
- const bbox = await rect(menu_el)
+ const bbox = await rect(I.menu2.trigger)
- await page.hover("body", { position: { x: bbox.x - 20, y: bbox.height / 2 + bbox.y } })
- await expect(page.locator(menu_2.menu)).toBeHidden()
- await expect(page.locator(menu_1.menu)).toBeFocused()
+ await I.page.hover("body", { position: { x: bbox.x - 20, y: bbox.height / 2 + bbox.y } })
+ await expect(I.menu2.menu).toBeHidden()
+ await expect(I.menu1.menu).toBeFocused()
})
- test("should close open submenu when moving pointer to parent menu item", async ({ page }) => {
- await page.click(menu_1.trigger)
- await page.hover(menu_2.trigger)
+ test("should close open submenu when moving pointer to parent menu item", async () => {
+ await I.menu1.trigger.click()
+ await expect(I.menu1.menu).toBeVisible()
+ await I.menu2.trigger.hover()
+ await expect(I.menu2.menu).toBeVisible()
- const menuitem = testid("new-tab")
+ const menuitem = I.getTestId("new-tab")
- await page.hover(menuitem)
+ // FIXME: .hover() causes "subtree intercepts pointer events" error in shadow-dom
+ await menuitem.hover()
// dispatch extra mouse movement to trigger hover
- await page.locator(menuitem).dispatchEvent("pointermove")
+ await menuitem.dispatchEvent("pointermove")
- await expect(page.locator(menu_2.menu)).toBeHidden()
- await expect(page.locator(menu_1.menu)).toBeVisible()
- await expect(page.locator(menu_1.menu)).toBeFocused()
- await expectToBeFocused(page, testid("new-tab"))
+ await expect(I.menu2.menu).toBeHidden()
+ await expect(I.menu1.menu).toBeVisible()
+ await expect(I.menu1.menu).toBeFocused()
+ await I.expectToBeHighlighted(I.getTestId("new-tab"))
})
})
diff --git a/e2e/menu-option.e2e.ts b/e2e/menu-option.e2e.ts
index fdda2353f7..9282fd8587 100644
--- a/e2e/menu-option.e2e.ts
+++ b/e2e/menu-option.e2e.ts
@@ -5,7 +5,7 @@ let I: MenuModel
test.describe("menu option", () => {
test.beforeEach(async ({ page }) => {
- I = new MenuModel(page)
+ I = new MenuModel(page, "menu-options-page")
await I.goto("/menu-options")
})
@@ -65,6 +65,7 @@ test.describe("menu option", () => {
// open the menu
await I.focusTrigger()
await I.pressKey("Enter")
+ await I.seeDropdownIsFocused()
// // navigate the 'Phone' item
await I.pressKey("ArrowDown", 4)
diff --git a/e2e/models/accordion.model.ts b/e2e/models/accordion.model.ts
index 4ed95274a2..4977fd824b 100644
--- a/e2e/models/accordion.model.ts
+++ b/e2e/models/accordion.model.ts
@@ -1,26 +1,24 @@
import { expect, type Page } from "@playwright/test"
-import { a11y, testid } from "../_utils"
+import { testid } from "../_utils"
import { Model } from "./model"
+const shadowHost = "accordion-page"
+
export class AccordionModel extends Model {
constructor(public page: Page) {
- super(page)
+ super(page, shadowHost)
}
goto() {
return this.page.goto("/accordion")
}
- checkAccessibility(selector?: string): Promise {
- return a11y(this.page, selector)
- }
-
getTrigger(id: string) {
- return this.page.locator(testid(`${id}:trigger`))
+ return this.host.locator(testid(`${id}:trigger`))
}
getContent(id: string) {
- return this.page.locator(testid(`${id}:content`))
+ return this.host.locator(testid(`${id}:content`))
}
async focusTrigger(id: string) {
diff --git a/e2e/models/checkbox.model.ts b/e2e/models/checkbox.model.ts
new file mode 100644
index 0000000000..05c6ff4d4c
--- /dev/null
+++ b/e2e/models/checkbox.model.ts
@@ -0,0 +1,47 @@
+import { expect, type Page } from "@playwright/test"
+import { part, testid } from "../_utils"
+import { Model } from "./model"
+
+const shadowHost = "checkbox-page"
+
+export class CheckboxModel extends Model {
+ constructor(public page: Page) {
+ super(page, shadowHost)
+ }
+
+ goto() {
+ return this.page.goto("/checkbox")
+ }
+
+ get root() {
+ return this.host.locator(part("root"))
+ }
+
+ get label() {
+ return this.host.locator(part("label"))
+ }
+
+ get control() {
+ return this.host.locator(part("control"))
+ }
+
+ get input() {
+ return this.host.locator(testid("hidden-input"))
+ }
+
+ get resetButton() {
+ return this.host.getByRole("button", { name: "Reset Form" })
+ }
+
+ async expectToBeChecked() {
+ await expect(this.root).toHaveAttribute("data-state", "checked")
+ await expect(this.label).toHaveAttribute("data-state", "checked")
+ await expect(this.control).toHaveAttribute("data-state", "checked")
+ }
+
+ async expectToBeDisabled() {
+ await expect(this.root).toHaveAttribute("data-disabled", "")
+ await expect(this.control).toHaveAttribute("data-disabled", "")
+ await expect(this.input).toBeDisabled()
+ }
+}
diff --git a/e2e/models/collapsible.model.ts b/e2e/models/collapsible.model.ts
new file mode 100644
index 0000000000..e3ad4f16c6
--- /dev/null
+++ b/e2e/models/collapsible.model.ts
@@ -0,0 +1,32 @@
+import { expect, type Page } from "@playwright/test"
+import { Model } from "./model"
+
+export class CollapsibleModel extends Model {
+ constructor(public page: Page) {
+ super(page, "collapsible-page")
+ }
+
+ goto(url = "/collapsible") {
+ return this.page.goto(url)
+ }
+
+ get trigger() {
+ return this.host.locator("[data-scope='collapsible'][data-part='trigger']")
+ }
+
+ get content() {
+ return this.host.locator("[data-scope='collapsible'][data-part='content']")
+ }
+
+ async clickTrigger() {
+ return this.trigger.click()
+ }
+
+ async seeContent() {
+ await expect(this.content).toBeVisible()
+ }
+
+ async dontSeeContent() {
+ await expect(this.content).not.toBeVisible()
+ }
+}
diff --git a/e2e/models/dialog.model.ts b/e2e/models/dialog.model.ts
index e2b369350f..44b36222ee 100644
--- a/e2e/models/dialog.model.ts
+++ b/e2e/models/dialog.model.ts
@@ -1,17 +1,20 @@
import { expect, type Page } from "@playwright/test"
-import { a11y } from "../_utils"
+import { testid } from "../_utils"
import { Model } from "./model"
+const shadowHost = "dialog-nested-page"
+
export class DialogModel extends Model {
constructor(
public page: Page,
private id: string,
) {
- super(page)
+ super(page, shadowHost)
}
- checkAccessibility() {
- return a11y(this.page, "[role=dialog]")
+ checkAccessibility(): Promise {
+ // return a11y(this.page, "[role=dialog]", shadowHost)
+ return super.checkAccessibility("[role=dialog]")
}
goto(url = "/dialog-nested") {
@@ -19,15 +22,15 @@ export class DialogModel extends Model {
}
private get trigger() {
- return this.page.locator(`[data-testid='trigger-${this.id}']`)
+ return this.host.locator(testid(`trigger-${this.id}`))
}
private get content() {
- return this.page.locator(`[data-testid='positioner-${this.id}']`)
+ return this.host.locator(testid(`positioner-${this.id}`))
}
private get closeTrigger() {
- return this.page.locator(`[data-testid='close-${this.id}']`)
+ return this.host.locator(testid(`close-${this.id}`))
}
clickTrigger(opts: { delay?: number } = {}) {
diff --git a/e2e/models/menu-nested.model.ts b/e2e/models/menu-nested.model.ts
new file mode 100644
index 0000000000..1ecc6f1dcf
--- /dev/null
+++ b/e2e/models/menu-nested.model.ts
@@ -0,0 +1,79 @@
+import { expect, type Locator, type Page } from "@playwright/test"
+import { Model } from "./model"
+import { testid } from "../_utils"
+
+export class MenuNestedModel extends Model {
+ constructor(
+ public page: Page,
+ shadowHost = "menu-nested-page",
+ ) {
+ super(page, shadowHost)
+ }
+
+ goto(url = "/menu-nested") {
+ return this.page.goto(url)
+ }
+
+ getTestId(id: string) {
+ return this.host.locator(testid(id))
+ }
+
+ get menu1() {
+ return {
+ trigger: this.getTestId("trigger"),
+ menu: this.getTestId("menu"),
+ subTrigger: this.getTestId("more-tools"),
+ }
+ }
+
+ get menu2() {
+ return {
+ trigger: this.getTestId("more-tools"),
+ menu: this.getTestId("more-tools-submenu"),
+ subTrigger: this.getTestId("open-nested"),
+ }
+ }
+
+ get menu3() {
+ return {
+ trigger: this.getTestId("open-nested"),
+ menu: this.getTestId("open-nested-submenu"),
+ }
+ }
+
+ async expectToBeHighlighted(locator: Locator) {
+ await expect(locator).toHaveAttribute("data-highlighted", "")
+ }
+
+ async expectItemToBeHighlighted(id: string) {
+ return this.expectToBeHighlighted(this.getTestId(id))
+ }
+
+ async navigateToSubmenuTrigger() {
+ await this.menu1.trigger.click()
+ await expect(this.menu1.menu).toBeFocused()
+ await this.page.keyboard.press("ArrowDown")
+ await this.page.keyboard.press("ArrowDown")
+ await this.page.keyboard.press("ArrowDown")
+ await this.page.keyboard.press("ArrowDown")
+ await this.expectToBeHighlighted(this.menu2.trigger)
+ // await this.page.keyboard.type("m")
+ // await this.expectItemToBeFocused("more-tools")
+ }
+
+ async expectSubmenuToBeFocused() {
+ await expect(this.menu2.menu).toBeVisible()
+ await expect(this.menu2.menu).toBeFocused()
+ await this.expectToBeHighlighted(this.menu2.trigger)
+ }
+
+ async expectAllMenusToBeClosed() {
+ // close all
+ await expect(this.menu1.menu).toBeHidden()
+ await expect(this.menu2.menu).toBeHidden()
+ await expect(this.menu3.menu).toBeHidden()
+
+ // focus trigger
+ await expect(this.menu1.trigger).toBeFocused()
+ }
+}
diff --git a/e2e/models/menu.model.ts b/e2e/models/menu.model.ts
index 4632d96c9a..5028e837a7 100644
--- a/e2e/models/menu.model.ts
+++ b/e2e/models/menu.model.ts
@@ -1,14 +1,18 @@
import { expect, type Page } from "@playwright/test"
-import { a11y, isInViewport } from "../_utils"
+import { isInViewport } from "../_utils"
import { Model } from "./model"
export class MenuModel extends Model {
- constructor(public page: Page) {
- super(page)
+ constructor(
+ public page: Page,
+ shadowHost = "menu-page",
+ ) {
+ super(page, shadowHost)
}
- checkAccessibility() {
- return a11y(this.page, "main")
+ checkAccessibility(): Promise {
+ // return a11y(this.page, "main", shadowHost)
+ return super.checkAccessibility("main")
}
goto(url = "/menu") {
@@ -16,23 +20,23 @@ export class MenuModel extends Model {
}
private get trigger() {
- return this.page.locator("[data-scope=menu][data-part=trigger]")
+ return this.host.locator("[data-scope=menu][data-part=trigger]")
}
private get contextTrigger() {
- return this.page.locator("[data-scope=menu][data-part=context-trigger]")
+ return this.host.locator("[data-scope=menu][data-part=context-trigger]")
}
private get content() {
- return this.page.locator("[data-scope=menu][data-part=content]")
+ return this.host.locator("[data-scope=menu][data-part=content]")
}
getItem = (text: string) => {
- return this.page.locator(`[data-part=item]`, { hasText: text })
+ return this.host.locator(`[data-part=item]`, { hasText: text })
}
get highlightedItem() {
- return this.page.locator("[data-part=item][data-highlighted]")
+ return this.host.locator("[data-part=item][data-highlighted]")
}
type(input: string) {
@@ -100,7 +104,7 @@ export class MenuModel extends Model {
}
seeMenuIsPositioned = async () => {
- const positioner = this.page.locator("[data-scope=menu][data-part=positioner]")
+ const positioner = this.host.locator("[data-scope=menu][data-part=positioner]")
await expect(positioner).toHaveCSS("--x", /\d+px/)
await expect(positioner).toHaveCSS("--y", /\d+px/)
}
diff --git a/e2e/models/model.ts b/e2e/models/model.ts
index 4a808c46f5..b338a3e205 100644
--- a/e2e/models/model.ts
+++ b/e2e/models/model.ts
@@ -1,8 +1,15 @@
import { expect, type Page } from "@playwright/test"
-import { a11y, clickOutside, clickViz, controls, repeat } from "../_utils"
+import { a11y, clickOutside, clickViz, controls, DOM_MODE, repeat } from "../_utils"
export class Model {
- constructor(public page: Page) {}
+ constructor(
+ public page: Page,
+ private shadowHost?: string,
+ ) {}
+
+ get host() {
+ return DOM_MODE === "shadow-dom" && this.shadowHost ? this.page.locator(this.shadowHost) : this.page
+ }
get controls() {
return controls(this.page)
@@ -17,7 +24,7 @@ export class Model {
}
checkAccessibility(selector?: string) {
- return a11y(this.page, selector)
+ return a11y(this.page, selector, this.shadowHost)
}
pressKey(key: string, times = 1) {
diff --git a/e2e/models/popover.model.ts b/e2e/models/popover.model.ts
index 4ad475cb77..c1e199bc20 100644
--- a/e2e/models/popover.model.ts
+++ b/e2e/models/popover.model.ts
@@ -1,14 +1,12 @@
import { expect, type Page } from "@playwright/test"
-import { a11y, testid } from "../_utils"
+import { testid } from "../_utils"
import { Model } from "./model"
+const shadowHost = "popover-page"
+
export class PopoverModel extends Model {
constructor(public page: Page) {
- super(page)
- }
-
- checkAccessibility() {
- return a11y(this.page)
+ super(page, shadowHost)
}
goto() {
@@ -16,31 +14,31 @@ export class PopoverModel extends Model {
}
get trigger() {
- return this.page.locator("[data-scope=popover][data-part=trigger]")
+ return this.host.locator("[data-scope=popover][data-part=trigger]")
}
get content() {
- return this.page.locator("[data-scope=popover][data-part=content]")
+ return this.host.locator("[data-scope=popover][data-part=content]")
}
get closeTrigger() {
- return this.page.locator("[data-scope=popover][data-part=close-trigger]")
+ return this.content.locator("[data-scope=popover][data-part=close-trigger]")
}
get buttonBefore() {
- return this.page.locator(testid("button-before"))
+ return this.host.locator(testid("button-before"))
}
get buttonAfter() {
- return this.page.locator(testid("button-after"))
+ return this.host.locator(testid("button-after"))
}
get link() {
- return this.page.locator(testid("focusable-link"))
+ return this.content.locator(testid("focusable-link"))
}
get plainText() {
- return this.page.locator(testid("plain-text"))
+ return this.host.locator(testid("plain-text"))
}
clickClose() {
diff --git a/e2e/models/radio-group.model.ts b/e2e/models/radio-group.model.ts
index 0cb5e45436..cc72bfea87 100644
--- a/e2e/models/radio-group.model.ts
+++ b/e2e/models/radio-group.model.ts
@@ -3,7 +3,7 @@ import { Model } from "./model"
export class RadioGroupModel extends Model {
constructor(public page: Page) {
- super(page)
+ super(page, "radio-group-page")
}
goto(url = "/radio-group") {
@@ -11,19 +11,19 @@ export class RadioGroupModel extends Model {
}
get root() {
- return this.page.locator("[data-scope='radio-group'][data-part='root']")
+ return this.host.locator("[data-scope='radio-group'][data-part='root']")
}
get label() {
- return this.page.locator("[data-scope='radio-group'][data-part='label']")
+ return this.host.locator("[data-scope='radio-group'][data-part='label']")
}
getRadio(value: string) {
return {
- radio: this.page.getByTestId(`radio-${value}`),
- label: this.page.getByTestId(`label-${value}`),
- input: this.page.getByTestId(`input-${value}`),
- control: this.page.getByTestId(`control-${value}`),
+ radio: this.host.getByTestId(`radio-${value}`),
+ label: this.host.getByTestId(`label-${value}`),
+ input: this.host.getByTestId(`input-${value}`),
+ control: this.host.getByTestId(`control-${value}`),
}
}
diff --git a/e2e/models/slider.model.ts b/e2e/models/slider.model.ts
index e71556b350..ddc5d440cc 100644
--- a/e2e/models/slider.model.ts
+++ b/e2e/models/slider.model.ts
@@ -1,14 +1,13 @@
import { expect, type Page } from "@playwright/test"
-import { a11y, rect } from "../_utils"
+import { rect } from "../_utils"
import { Model } from "./model"
export class SliderModel extends Model {
- constructor(public page: Page) {
- super(page)
- }
-
- checkAccessibility() {
- return a11y(this.page)
+ constructor(
+ public page: Page,
+ shadowHost = "slider-page",
+ ) {
+ super(page, shadowHost)
}
goto(url = "/slider") {
@@ -16,15 +15,15 @@ export class SliderModel extends Model {
}
getThumb(index = 0) {
- return this.page.locator(`[data-scope='slider'][data-part='thumb'][data-index='${index}']`)
+ return this.host.locator(`[data-scope='slider'][data-part='thumb'][data-index='${index}']`)
}
get track() {
- return this.page.locator("[data-scope='slider'][data-part='track']")
+ return this.host.locator("[data-scope='slider'][data-part='track']")
}
get output() {
- return this.page.locator("[data-scope='slider'][data-part='value-text']")
+ return this.host.locator("[data-scope='slider'][data-part='value-text']")
}
focusThumb(index?: number) {
diff --git a/e2e/models/switch.model.ts b/e2e/models/switch.model.ts
index be9ba41e51..938ad0ddf2 100644
--- a/e2e/models/switch.model.ts
+++ b/e2e/models/switch.model.ts
@@ -1,14 +1,12 @@
import { expect, type Page } from "@playwright/test"
-import { a11y, repeat } from "../_utils"
+import { repeat } from "../_utils"
import { Model } from "./model"
+const shadowHost = "switch-page"
+
export class SwitchModel extends Model {
constructor(public page: Page) {
- super(page)
- }
-
- checkAccessibility() {
- return a11y(this.page)
+ super(page, shadowHost)
}
goto(url = "/switch") {
@@ -16,19 +14,19 @@ export class SwitchModel extends Model {
}
get root() {
- return this.page.locator("[data-scope='switch'][data-part='root']")
+ return this.host.locator("[data-scope='switch'][data-part='root']")
}
get label() {
- return this.page.locator("[data-scope='switch'][data-part='label']")
+ return this.host.locator("[data-scope='switch'][data-part='label']")
}
get control() {
- return this.page.locator("[data-scope='switch'][data-part='control']")
+ return this.host.locator("[data-scope='switch'][data-part='control']")
}
get input() {
- return this.page.locator("[data-scope='switch'][data-part='root'] input")
+ return this.host.locator("[data-scope='switch'][data-part='root'] input")
}
async clickCheckbox() {
diff --git a/e2e/models/tabs.model.ts b/e2e/models/tabs.model.ts
index dfae098881..bbf485cba4 100644
--- a/e2e/models/tabs.model.ts
+++ b/e2e/models/tabs.model.ts
@@ -1,14 +1,12 @@
import { expect, type Page } from "@playwright/test"
-import { a11y, testid } from "../_utils"
+import { testid } from "../_utils"
import { Model } from "./model"
+const shadowHost = "tabs-page"
+
export class TabsModel extends Model {
constructor(public page: Page) {
- super(page)
- }
-
- checkAccessibility() {
- return a11y(this.page)
+ super(page, shadowHost)
}
goto() {
@@ -16,11 +14,11 @@ export class TabsModel extends Model {
}
private getTabTrigger = (id: string) => {
- return this.page.locator(testid(`${id}-tab`))
+ return this.host.locator(testid(`${id}-tab`))
}
private getTabContent = (id: string) => {
- return this.page.locator(testid(`${id}-tab-panel`))
+ return this.host.locator(testid(`${id}-tab-panel`))
}
clickTab = async (id: string) => {
diff --git a/e2e/models/toggle-group.model.ts b/e2e/models/toggle-group.model.ts
index 18e79bd122..dd75be3ce0 100644
--- a/e2e/models/toggle-group.model.ts
+++ b/e2e/models/toggle-group.model.ts
@@ -2,15 +2,17 @@ import { type Page, expect } from "@playwright/test"
import { Model } from "./model"
import { part } from "../_utils"
+const shadowHost = "toggle-group-page"
+
type Item = "bold" | "italic" | "underline"
export class ToggleGroupModel extends Model {
constructor(page: Page) {
- super(page)
+ super(page, shadowHost)
}
private __item(item: Item) {
- return this.page.locator(part("item")).nth(["bold", "italic", "underline"].indexOf(item))
+ return this.host.locator(part("item")).nth(["bold", "italic", "underline"].indexOf(item))
}
clickItem(item: Item) {
diff --git a/e2e/popover.e2e.ts b/e2e/popover.e2e.ts
index bb136ad027..c54598d056 100644
--- a/e2e/popover.e2e.ts
+++ b/e2e/popover.e2e.ts
@@ -92,4 +92,13 @@ test.describe("popover", () => {
await I.seeButtonBeforeIsFocused()
await I.dontSeeContent()
})
+
+ test("[focus] should close popover when focus moves to button-after element", async () => {
+ await I.clickTrigger()
+ await I.seeContent()
+ await I.seeLinkIsFocused()
+ await I.pressKey("Tab", 3)
+ await I.seeButtonAfterIsFocused()
+ await I.dontSeeContent()
+ })
})
diff --git a/examples/lit-ts/.gitignore b/examples/lit-ts/.gitignore
new file mode 100644
index 0000000000..a547bf36d8
--- /dev/null
+++ b/examples/lit-ts/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/examples/lit-ts/index.html b/examples/lit-ts/index.html
new file mode 100644
index 0000000000..c240aa0398
--- /dev/null
+++ b/examples/lit-ts/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Zag.js + Lit
+
+
+
+
+
+
diff --git a/examples/lit-ts/package.json b/examples/lit-ts/package.json
new file mode 100644
index 0000000000..6cad639913
--- /dev/null
+++ b/examples/lit-ts/package.json
@@ -0,0 +1,98 @@
+{
+ "name": "lit-ts",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port 3004",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@types/form-serialize": "0.7.4",
+ "typescript": "5.9.2",
+ "vite": "7.1.3"
+ },
+ "dependencies": {
+ "@internationalized/date": "3.9.0",
+ "@open-wc/lit-helpers": "0.7.0",
+ "@zag-js/accordion": "workspace:*",
+ "@zag-js/anatomy": "workspace:*",
+ "@zag-js/anatomy-icons": "workspace:*",
+ "@zag-js/angle-slider": "workspace:*",
+ "@zag-js/aria-hidden": "workspace:*",
+ "@zag-js/async-list": "workspace:*",
+ "@zag-js/auto-resize": "workspace:*",
+ "@zag-js/avatar": "workspace:*",
+ "@zag-js/carousel": "workspace:*",
+ "@zag-js/checkbox": "workspace:*",
+ "@zag-js/clipboard": "workspace:*",
+ "@zag-js/collapsible": "workspace:*",
+ "@zag-js/collection": "workspace:*",
+ "@zag-js/color-picker": "workspace:*",
+ "@zag-js/color-utils": "workspace:*",
+ "@zag-js/combobox": "workspace:*",
+ "@zag-js/core": "workspace:*",
+ "@zag-js/date-picker": "workspace:*",
+ "@zag-js/date-utils": "workspace:*",
+ "@zag-js/dialog": "workspace:*",
+ "@zag-js/dismissable": "workspace:*",
+ "@zag-js/docs": "workspace:*",
+ "@zag-js/dom-query": "workspace:*",
+ "@zag-js/editable": "workspace:*",
+ "@zag-js/file-upload": "workspace:*",
+ "@zag-js/file-utils": "workspace:*",
+ "@zag-js/floating-panel": "workspace:*",
+ "@zag-js/focus-trap": "workspace:*",
+ "@zag-js/focus-visible": "workspace:*",
+ "@zag-js/highlight-word": "workspace:*",
+ "@zag-js/hover-card": "workspace:*",
+ "@zag-js/i18n-utils": "workspace:*",
+ "@zag-js/interact-outside": "workspace:*",
+ "@zag-js/json-tree-utils": "workspace:*",
+ "@zag-js/listbox": "workspace:*",
+ "@zag-js/lit": "workspace:*",
+ "@zag-js/live-region": "workspace:*",
+ "@zag-js/menu": "workspace:*",
+ "@zag-js/navigation-menu": "workspace:*",
+ "@zag-js/number-input": "workspace:*",
+ "@zag-js/pagination": "workspace:*",
+ "@zag-js/password-input": "workspace:*",
+ "@zag-js/pin-input": "workspace:*",
+ "@zag-js/popover": "workspace:*",
+ "@zag-js/popper": "workspace:*",
+ "@zag-js/presence": "workspace:*",
+ "@zag-js/progress": "workspace:*",
+ "@zag-js/qr-code": "workspace:*",
+ "@zag-js/radio-group": "workspace:*",
+ "@zag-js/rating-group": "workspace:*",
+ "@zag-js/rect-utils": "workspace:*",
+ "@zag-js/remove-scroll": "workspace:*",
+ "@zag-js/scroll-snap": "workspace:*",
+ "@zag-js/select": "workspace:*",
+ "@zag-js/shared": "workspace:*",
+ "@zag-js/signature-pad": "workspace:*",
+ "@zag-js/slider": "workspace:*",
+ "@zag-js/splitter": "workspace:*",
+ "@zag-js/steps": "workspace:*",
+ "@zag-js/store": "workspace:*",
+ "@zag-js/stringify-state": "workspace:*",
+ "@zag-js/switch": "workspace:*",
+ "@zag-js/tabs": "workspace:*",
+ "@zag-js/tags-input": "workspace:*",
+ "@zag-js/timer": "workspace:*",
+ "@zag-js/toast": "workspace:*",
+ "@zag-js/toggle": "workspace:*",
+ "@zag-js/toggle-group": "workspace:*",
+ "@zag-js/tooltip": "workspace:*",
+ "@zag-js/tour": "workspace:*",
+ "@zag-js/tree-view": "workspace:*",
+ "@zag-js/types": "workspace:*",
+ "@zag-js/utils": "workspace:*",
+ "form-serialize": "0.7.2",
+ "lit": "3.3.1",
+ "lucide": "0.542.0",
+ "match-sorter": "8.1.0",
+ "nanoid": "^5.1.5"
+ }
+}
diff --git a/examples/lit-ts/public/vite.svg b/examples/lit-ts/public/vite.svg
new file mode 100644
index 0000000000..e7b8dfb1b2
--- /dev/null
+++ b/examples/lit-ts/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/lit-ts/src/components/state-visualizer.ts b/examples/lit-ts/src/components/state-visualizer.ts
new file mode 100644
index 0000000000..b7cbf9e958
--- /dev/null
+++ b/examples/lit-ts/src/components/state-visualizer.ts
@@ -0,0 +1,53 @@
+import { LitElement, html, unsafeCSS } from "lit"
+import { customElement, property } from "lit/decorators.js"
+import { unsafeHTML } from "lit/directives/unsafe-html.js"
+import type { MachineSchema, Service } from "@zag-js/core"
+import { highlightState } from "@zag-js/stringify-state"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+
+@customElement("state-visualizer")
+export class StateVisualizer extends LitElement {
+ static styles = unsafeCSS(styleLayout)
+
+ @property({ attribute: false })
+ state?: Service
+
+ @property({ type: String })
+ label?: string
+
+ @property({ attribute: false })
+ omit?: string[]
+
+ @property({ attribute: false })
+ context?: Array
+
+ render() {
+ const service = this.state
+
+ if (!service) {
+ return html``
+ }
+
+ const obj = {
+ state: service.state.get(),
+ event: service.event.current(),
+ previousEvent: service.event.previous(),
+ context: this.context
+ ? Object.fromEntries(this.context.map((key) => [key, service.context.get(key)]))
+ : undefined,
+ }
+
+ const highlighted = highlightState(obj, this.omit)
+
+ return html`
+
+
+
+ ${this.label || "Visualizer"}
+ ${unsafeHTML(highlighted)}
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/components/toolbar.ts b/examples/lit-ts/src/components/toolbar.ts
new file mode 100644
index 0000000000..0bf4d47bd0
--- /dev/null
+++ b/examples/lit-ts/src/components/toolbar.ts
@@ -0,0 +1,140 @@
+import { LitElement, html, unsafeCSS } from "lit"
+import { customElement, property, state } from "lit/decorators.js"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import type { ControlsController } from "../lib/controls-controller"
+
+@customElement("zag-toolbar")
+export class Toolbar extends LitElement {
+ static styles = unsafeCSS(styleLayout)
+
+ @property({ attribute: false })
+ controls?: ControlsController
+
+ @state()
+ private activeTab = 0
+
+ render() {
+ const hasControls = !!this.controls
+
+ return html`
+
+ `
+ }
+
+ private renderControls() {
+ const { controls } = this
+ if (!controls) return ""
+
+ return html`
+
+ ${controls.getControlKeys().map((key) => {
+ const config = controls.getControlConfig(key)
+ const value = controls.getValue(key)
+ const { type, label = key, options, placeholder, min, max } = config
+
+ switch (type) {
+ case "boolean":
+ return html`
+
+ {
+ const target = e.target as HTMLInputElement
+ controls.setState(key, target.checked)
+ }}
+ />
+ ${label}
+
+ `
+ case "string":
+ return html`
+
+ ${label}
+ {
+ if (e.key === "Enter") {
+ const target = e.target as HTMLInputElement
+ controls.setState(key, target.value)
+ }
+ }}
+ />
+
+ `
+ case "select":
+ return html`
+
+ ${label}
+ {
+ const target = e.target as HTMLSelectElement
+ controls.setState(key, target.value)
+ }}
+ >
+ -----
+ ${options?.map((option: string) => html` ${option} `)}
+
+
+ `
+ case "number":
+ return html`
+
+ ${label}
+ {
+ if (e.key === "Enter") {
+ const target = e.target as HTMLInputElement
+ const val = parseFloat(target.value)
+ controls.setState(key, isNaN(val) ? 0 : val)
+ }
+ }}
+ />
+
+ `
+ default:
+ return ""
+ }
+ })}
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/lib/controls-controller.ts b/examples/lit-ts/src/lib/controls-controller.ts
new file mode 100644
index 0000000000..a612456439
--- /dev/null
+++ b/examples/lit-ts/src/lib/controls-controller.ts
@@ -0,0 +1,46 @@
+import { type ControlRecord, deepGet, deepSet, getControlDefaults } from "@zag-js/shared"
+import type { ReactiveController, ReactiveControllerHost } from "lit"
+
+export class ControlsController implements ReactiveController {
+ private host: ReactiveControllerHost
+ private state: any
+ private config: T
+
+ constructor(host: ReactiveControllerHost, config: T) {
+ this.host = host
+ this.config = config
+ this.state = getControlDefaults(config)
+ host.addController(this)
+ }
+
+ hostConnected() {
+ // Nothing needed here for now
+ }
+
+ hostDisconnected() {
+ // Nothing needed here for now
+ }
+
+ get context() {
+ return this.state
+ }
+
+ setState(key: string, value: any) {
+ const newState = structuredClone(this.state)
+ deepSet(newState, key, value)
+ this.state = newState
+ this.host.requestUpdate()
+ }
+
+ getValue(key: string) {
+ return deepGet(this.state, key)
+ }
+
+ getControlKeys() {
+ return Object.keys(this.config)
+ }
+
+ getControlConfig(key: string) {
+ return this.config[key] as any
+ }
+}
diff --git a/examples/lit-ts/src/lib/page-element.ts b/examples/lit-ts/src/lib/page-element.ts
new file mode 100644
index 0000000000..fd54e4a99f
--- /dev/null
+++ b/examples/lit-ts/src/lib/page-element.ts
@@ -0,0 +1,27 @@
+import { LitElement } from "lit"
+
+// Detect DOM mode - use Vite's import.meta.env for client-side access
+// These are injected at build time by Vite
+const DOM_MODE = import.meta.env?.VITE_DOM_MODE === "light-dom" ? "light-dom" : "shadow-dom"
+
+/**
+ * Base class for page components that automatically switches between Shadow DOM and Light DOM
+ * based on the testing context.
+ *
+ * Environment Variables:
+ * - DOM_MODE=shadow-dom: Force Shadow DOM regardless of framework
+ * - DOM_MODE=light-dom: Force Light DOM regardless of framework
+ * - FRAMEWORK=lit: Default to Shadow DOM (can be overridden by DOM_MODE)
+ * - Default: Light DOM for CSS compatibility
+ */
+export class PageElement extends LitElement {
+ constructor() {
+ super()
+
+ // Use Light DOM by default for CSS compatibility, unless explicitly testing Shadow DOM
+ if (DOM_MODE === "light-dom") {
+ this.createRenderRoot = () => this
+ }
+ // When DOM_MODE === 'shadow-dom', use default Lit behavior (Shadow DOM)
+ }
+}
diff --git a/examples/lit-ts/src/main.css b/examples/lit-ts/src/main.css
new file mode 100644
index 0000000000..acfaffcd06
--- /dev/null
+++ b/examples/lit-ts/src/main.css
@@ -0,0 +1,4 @@
+.component-page {
+ flex: auto;
+ display: flex;
+}
diff --git a/examples/lit-ts/src/main.ts b/examples/lit-ts/src/main.ts
new file mode 100644
index 0000000000..49d2998e6d
--- /dev/null
+++ b/examples/lit-ts/src/main.ts
@@ -0,0 +1,167 @@
+import { LitElement, html } from "lit"
+import { customElement, property } from "lit/decorators.js"
+import { routesData } from "@zag-js/shared"
+
+import "@zag-js/shared/src/style.css"
+import "./main.css"
+
+// Import toolbar components
+import "./components/toolbar"
+import "./components/state-visualizer"
+
+// Import all page components
+import "./pages/accordion"
+import "./pages/checkbox"
+import "./pages/collapsible"
+import "./pages/dialog"
+import "./pages/dialog-nested"
+import "./pages/menu"
+import "./pages/menu-nested"
+import "./pages/menu-options"
+import "./pages/popover"
+import "./pages/radio-group"
+import "./pages/range-slider"
+import "./pages/slider"
+import "./pages/switch"
+import "./pages/tabs"
+import "./pages/toggle"
+import "./pages/toggle-group"
+
+// Sort alphabetically in place (Why not in shared/?)
+routesData.sort((a, b) => a.label.localeCompare(b.label))
+
+@customElement("zag-app")
+export class ZagApp extends LitElement {
+ // Light dom (no shadow root) due to css
+ protected createRenderRoot() {
+ return this
+ }
+
+ @property({ type: String })
+ currentPath = window.location.pathname
+
+ connectedCallback() {
+ super.connectedCallback()
+ this.updatePath()
+ window.addEventListener("popstate", this.updatePath)
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback()
+ window.removeEventListener("popstate", this.updatePath)
+ }
+
+ private updatePath = () => {
+ this.currentPath = window.location.pathname
+ }
+
+ private navigate(path: string) {
+ window.history.pushState({}, "", path)
+ this.currentPath = path
+ }
+
+ private renderContent() {
+ switch (this.currentPath) {
+ case "/accordion":
+ return html` `
+ case "/checkbox":
+ return html` `
+ case "/collapsible":
+ return html` `
+ case "/dialog":
+ return html` `
+ case "/dialog-nested":
+ return html` `
+ case "/menu":
+ return html` `
+ case "/menu-nested":
+ return html` `
+ case "/menu-options":
+ return html` `
+ case "/popover":
+ return html` `
+ case "/radio-group":
+ return html` `
+ case "/range-slider":
+ return html` `
+ case "/slider":
+ return html` `
+ case "/switch":
+ return html` `
+ case "/tabs":
+ return html` `
+ case "/toggle":
+ return html` `
+ case "/toggle-group":
+ return html` `
+ default:
+ return this.renderHome()
+ }
+ }
+
+ private renderHome() {
+ return html`
+
+
Zag.js + Lit
+
Select a component from the sidebar to see it in action.
+
+ `
+ }
+
+ render() {
+ const routes = routesData.filter((route) =>
+ // Only show routes we have implemented
+ [
+ "/accordion",
+ "/checkbox",
+ "/collapsible",
+ "/dialog",
+ "/dialog-nested",
+ "/menu",
+ "/menu-nested",
+ "/menu-options",
+ "/popover",
+ "/radio-group",
+ "/range-slider",
+ "/slider",
+ "/switch",
+ "/tabs",
+ "/toggle",
+ "/toggle-group",
+ ].includes(route.path),
+ )
+
+ return html`
+
+
+ ${this.renderContent()}
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/accordion.ts b/examples/lit-ts/src/pages/accordion.ts
new file mode 100644
index 0000000000..0d05379578
--- /dev/null
+++ b/examples/lit-ts/src/pages/accordion.ts
@@ -0,0 +1,58 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as accordion from "@zag-js/accordion"
+import { accordionControls, accordionData } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/accordion.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { ArrowRight, createElement } from "lucide"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("accordion-page")
+export class AccordionPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, accordionControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, accordion.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ ...this.controls.context,
+ }))
+
+ render() {
+ const api = accordion.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+ ${accordionData.map(
+ (item) => html`
+
+
+
+ ${item.label}
+ ${createElement(ArrowRight)}
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna aliqua.
+
+
+ `,
+ )}
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/checkbox.ts b/examples/lit-ts/src/pages/checkbox.ts
new file mode 100644
index 0000000000..07274ccea4
--- /dev/null
+++ b/examples/lit-ts/src/pages/checkbox.ts
@@ -0,0 +1,61 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as checkbox from "@zag-js/checkbox"
+import { checkboxControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/checkbox.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("checkbox-page")
+export class CheckboxPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, checkboxControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, checkbox.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ name: "checkbox",
+ ...this.controls.context,
+ }))
+
+ private handleFormChange = (e: Event) => {
+ const form = e.currentTarget as HTMLFormElement
+ const formData = new FormData(form)
+ const result = Object.fromEntries(formData.entries())
+ console.log(result)
+ }
+
+ render() {
+ const api = checkbox.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/collapsible.ts b/examples/lit-ts/src/pages/collapsible.ts
new file mode 100644
index 0000000000..0fef118441
--- /dev/null
+++ b/examples/lit-ts/src/pages/collapsible.ts
@@ -0,0 +1,62 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as collapsible from "@zag-js/collapsible"
+import { collapsibleControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/collapsible.css?inline"
+import styleKeyframes from "@zag-js/shared/src/css/keyframes.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+import { ChevronDown, createElement } from "lucide"
+
+@customElement("collapsible-page")
+export class CollapsiblePage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleKeyframes + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, collapsibleControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, collapsible.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ ...this.controls.context,
+ }))
+
+ render() {
+ const api = collapsible.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+ Collapsible Trigger
+ ${createElement(ChevronDown)}
+
+
+
+ Lorem dfd dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
+ dolore magna sfsd. Ut enim ad minimdfd v eniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+ mollit anim id est laborum. Some Link
+
+
+
+
+
+
Toggle Controls
+
api.setOpen(true)}>Open
+
api.setOpen(false)}>Close
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/dialog-nested.ts b/examples/lit-ts/src/pages/dialog-nested.ts
new file mode 100644
index 0000000000..871ec408f7
--- /dev/null
+++ b/examples/lit-ts/src/pages/dialog-nested.ts
@@ -0,0 +1,84 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as dialog from "@zag-js/dialog"
+import styleComponent from "@zag-js/shared/src/css/dialog.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { PageElement } from "../lib/page-element"
+
+@customElement("dialog-nested-page")
+export class DialogNestedPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private machine1Id = nanoid(5)
+ private machine2Id = nanoid(5)
+
+ // Dialog 1
+ private machine1 = new MachineController(this, dialog.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machine1Id,
+ }))
+
+ // Dialog 2
+ private machine2 = new MachineController(this, dialog.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machine2Id,
+ }))
+
+ render() {
+ const parentDialog = dialog.connect(this.machine1.service, normalizeProps)
+ const childDialog = dialog.connect(this.machine2.service, normalizeProps)
+
+ return html`
+
+
+
Open Dialog
+
+
+
+ ${parentDialog.open
+ ? html`
+
+
+
+
Edit profile
+
+ Make changes to your profile here. Click save when you are done.
+
+
X
+
+
Save Changes
+
+
Open Nested
+
+ ${childDialog.open
+ ? html`
+
+
+
+
Nested
+ X
+ parentDialog.setOpen(false)}>
+ Close Dialog 1
+
+
+
+ `
+ : ""}
+
+
+ `
+ : ""}
+
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/dialog.ts b/examples/lit-ts/src/pages/dialog.ts
new file mode 100644
index 0000000000..8cc84fcced
--- /dev/null
+++ b/examples/lit-ts/src/pages/dialog.ts
@@ -0,0 +1,55 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as dialog from "@zag-js/dialog"
+import styleComponent from "@zag-js/shared/src/css/dialog.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { PageElement } from "../lib/page-element"
+
+@customElement("dialog-page")
+export class DialogPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, dialog.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ }))
+
+ render() {
+ const api = dialog.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+ Click me
+
+ ${api.open
+ ? html`
+
+
+
+
Edit profile
+
+ Make changes to your profile here. Click save when you are done.
+
+
+
+ Save
+
+
Close
+
+
+ `
+ : ""}
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/menu-nested.ts b/examples/lit-ts/src/pages/menu-nested.ts
new file mode 100644
index 0000000000..6bf5417a31
--- /dev/null
+++ b/examples/lit-ts/src/pages/menu-nested.ts
@@ -0,0 +1,106 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as menu from "@zag-js/menu"
+import { menuData } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/menu.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { PageElement } from "../lib/page-element"
+
+@customElement("menu-nested-page")
+export class MenuNestedPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private rootId = nanoid(5)
+ private subId = nanoid(5)
+ private sub2Id = nanoid(5)
+
+ private rootMachine = new MachineController(this, menu.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.rootId,
+ }))
+
+ private subMachine = new MachineController(this, menu.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.subId,
+ }))
+
+ private sub2Machine = new MachineController(this, menu.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.sub2Id,
+ }))
+
+ protected firstUpdated() {
+ const root = menu.connect(this.rootMachine.service, normalizeProps)
+ const sub = menu.connect(this.subMachine.service, normalizeProps)
+ const sub2 = menu.connect(this.sub2Machine.service, normalizeProps)
+
+ // Set up parent-child relationships
+ root.setChild(this.subMachine.service)
+ sub.setParent(this.rootMachine.service)
+
+ sub.setChild(this.sub2Machine.service)
+ sub2.setParent(this.subMachine.service)
+ }
+
+ render() {
+ const root = menu.connect(this.rootMachine.service, normalizeProps)
+ const sub = menu.connect(this.subMachine.service, normalizeProps)
+ const sub2 = menu.connect(this.sub2Machine.service, normalizeProps)
+
+ const triggerItemProps = root.getTriggerItemProps(sub)
+ const triggerItem2Props = sub.getTriggerItemProps(sub2)
+
+ const [level1, level2, level3] = menuData
+
+ return html`
+
+
+
Click me
+
+
+
+
+ ${level1.map((item) => {
+ const props = item.trigger ? triggerItemProps : root.getItemProps({ value: item.value })
+ return html` ${item.label} `
+ })}
+
+
+
+
+
+
+ ${level2.map((item) => {
+ const props = item.trigger ? triggerItem2Props : sub.getItemProps({ value: item.value })
+ return html` ${item.label} `
+ })}
+
+
+
+
+
+
+ ${level3.map(
+ (item) => html`
+
+ ${item.label}
+
+ `,
+ )}
+
+
+
+
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/menu-options.ts b/examples/lit-ts/src/pages/menu-options.ts
new file mode 100644
index 0000000000..1161fb6e1b
--- /dev/null
+++ b/examples/lit-ts/src/pages/menu-options.ts
@@ -0,0 +1,102 @@
+import { html, unsafeCSS } from "lit"
+import { customElement, state } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as menu from "@zag-js/menu"
+import { menuOptionData, menuControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/menu.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("menu-options-page")
+export class MenuOptionsPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, menuControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, menu.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ ...this.controls.context,
+ }))
+
+ @state()
+ private order = ""
+
+ @state()
+ private type: string[] = []
+
+ private setOrder = (value: string, checked: boolean) => {
+ this.order = checked ? value : ""
+ }
+
+ private setType = (value: string, checked: boolean) => {
+ if (checked) {
+ this.type = [...this.type, value]
+ } else {
+ this.type = this.type.filter((x) => x !== value)
+ }
+ }
+
+ render() {
+ const api = menu.connect(this.machine.service, normalizeProps)
+
+ const radios = menuOptionData.order.map((item) => ({
+ type: "radio" as const,
+ name: "order",
+ value: item.value,
+ label: item.label,
+ checked: this.order === item.value,
+ onCheckedChange: (checked: boolean) => this.setOrder(item.value, checked),
+ }))
+
+ const checkboxes = menuOptionData.type.map((item) => ({
+ type: "checkbox" as const,
+ name: "type",
+ value: item.value,
+ label: item.label,
+ checked: this.type.includes(item.value),
+ onCheckedChange: (checked: boolean) => this.setType(item.value, checked),
+ }))
+
+ return html`
+
+
+
+ Actions â–¾
+
+
+
+
+ ${radios.map(
+ (item) => html`
+
+ ✅
+ ${item.label}
+
+ `,
+ )}
+
+ ${checkboxes.map(
+ (item) => html`
+
+ ✅
+ ${item.label}
+
+ `,
+ )}
+
+
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/menu.ts b/examples/lit-ts/src/pages/menu.ts
new file mode 100644
index 0000000000..b95cbfe59d
--- /dev/null
+++ b/examples/lit-ts/src/pages/menu.ts
@@ -0,0 +1,57 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as menu from "@zag-js/menu"
+import { menuControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/menu.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("menu-page")
+export class MenuPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, menuControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, menu.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ onSelect: console.log,
+ ...this.controls.context,
+ }))
+
+ render() {
+ const api = menu.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/page.css b/examples/lit-ts/src/pages/page.css
new file mode 100644
index 0000000000..45d77b862f
--- /dev/null
+++ b/examples/lit-ts/src/pages/page.css
@@ -0,0 +1,3 @@
+:host {
+ outline: none !important;
+}
diff --git a/examples/lit-ts/src/pages/popover.ts b/examples/lit-ts/src/pages/popover.ts
new file mode 100644
index 0000000000..23f42e1ac4
--- /dev/null
+++ b/examples/lit-ts/src/pages/popover.ts
@@ -0,0 +1,70 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as popover from "@zag-js/popover"
+import { popoverControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/popover.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("popover-page")
+export class PopoverPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, popoverControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, popover.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ ...this.controls.context,
+ }))
+
+ render() {
+ const api = popover.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
Button :before
+
+
+ Click me
+ >
+
+
+
anchor
+
+ ${api.open
+ ? html`
+
+ `
+ : ""}
+
I am just text
+
Button :after
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/radio-group.ts b/examples/lit-ts/src/pages/radio-group.ts
new file mode 100644
index 0000000000..2947727515
--- /dev/null
+++ b/examples/lit-ts/src/pages/radio-group.ts
@@ -0,0 +1,71 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as radio from "@zag-js/radio-group"
+import { radioControls, radioData } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/radio-group.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("radio-group-page")
+export class RadioGroupPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, radioControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, radio.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ name: "fruit",
+ ...this.controls.context,
+ }))
+
+ private handleFormChange = (e: Event) => {
+ const form = e.currentTarget as HTMLFormElement
+ const formData = new FormData(form)
+ const result = Object.fromEntries(formData.entries())
+ console.log(result)
+ }
+
+ render() {
+ const api = radio.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/range-slider.ts b/examples/lit-ts/src/pages/range-slider.ts
new file mode 100644
index 0000000000..56e045dd9c
--- /dev/null
+++ b/examples/lit-ts/src/pages/range-slider.ts
@@ -0,0 +1,76 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as slider from "@zag-js/slider"
+import { sliderControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/slider.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import serialize from "form-serialize"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("range-slider-page")
+export class RangeSliderPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, sliderControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, slider.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ name: "quantity",
+ defaultValue: [10, 60],
+ ...this.controls.context,
+ }))
+
+ private handleFormChange = (e: Event) => {
+ const form = e.currentTarget as HTMLFormElement
+ const formData = serialize(form, { hash: true })
+ console.log(formData)
+ }
+
+ render() {
+ const api = slider.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/slider.ts b/examples/lit-ts/src/pages/slider.ts
new file mode 100644
index 0000000000..2067f82885
--- /dev/null
+++ b/examples/lit-ts/src/pages/slider.ts
@@ -0,0 +1,76 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as slider from "@zag-js/slider"
+import { sliderControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/slider.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import serialize from "form-serialize"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("slider-page")
+export class SliderPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, sliderControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, slider.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ name: "quantity",
+ defaultValue: [0],
+ ...this.controls.context,
+ }))
+
+ private handleFormChange = (e: Event) => {
+ const form = e.currentTarget as HTMLFormElement
+ const formData = serialize(form, { hash: true })
+ console.log(formData)
+ }
+
+ render() {
+ const api = slider.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/switch.ts b/examples/lit-ts/src/pages/switch.ts
new file mode 100644
index 0000000000..f02734f644
--- /dev/null
+++ b/examples/lit-ts/src/pages/switch.ts
@@ -0,0 +1,47 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as zagSwitch from "@zag-js/switch"
+import { switchControls } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/switch.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("switch-page")
+export class SwitchPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, switchControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, zagSwitch.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ name: "switch",
+ ...this.controls.context,
+ }))
+
+ render() {
+ const api = zagSwitch.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+
+
+
+ Feature is ${api.checked ? "enabled" : "disabled"}
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/tabs.ts b/examples/lit-ts/src/pages/tabs.ts
new file mode 100644
index 0000000000..d1c80d6be2
--- /dev/null
+++ b/examples/lit-ts/src/pages/tabs.ts
@@ -0,0 +1,60 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as tabs from "@zag-js/tabs"
+import { tabsControls, tabsData } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/tabs.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("tabs-page")
+export class TabsPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, tabsControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, tabs.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ defaultValue: "nils",
+ ...this.controls.context,
+ }))
+
+ render() {
+ const api = tabs.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+
+
+ ${tabsData.map(
+ (data) => html`
+
+ ${data.label}
+
+ `,
+ )}
+
+ ${tabsData.map(
+ (data) => html`
+
+
${data.content}
+ ${data.id === "agnes" ? html`
` : null}
+
+ `,
+ )}
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/toggle-group.ts b/examples/lit-ts/src/pages/toggle-group.ts
new file mode 100644
index 0000000000..b68f78b5c4
--- /dev/null
+++ b/examples/lit-ts/src/pages/toggle-group.ts
@@ -0,0 +1,45 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as toggleGroup from "@zag-js/toggle-group"
+import { toggleGroupControls, toggleGroupData } from "@zag-js/shared"
+import styleComponent from "@zag-js/shared/src/css/toggle-group.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { nanoid } from "nanoid"
+import { ControlsController } from "../lib/controls-controller"
+import { PageElement } from "../lib/page-element"
+
+@customElement("toggle-group-page")
+export class ToggleGroupPage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private controls = new ControlsController(this, toggleGroupControls)
+ private machineId = nanoid(5)
+
+ private machine = new MachineController(this, toggleGroup.machine, () => ({
+ getRootNode: () => this.shadowRoot || this.ownerDocument,
+ id: this.machineId,
+ ...this.controls.context,
+ }))
+
+ render() {
+ const api = toggleGroup.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+ Outside
+
+ ${toggleGroupData.map(
+ (item) => html`${item.label} `,
+ )}
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/pages/toggle.ts b/examples/lit-ts/src/pages/toggle.ts
new file mode 100644
index 0000000000..0afce7eca4
--- /dev/null
+++ b/examples/lit-ts/src/pages/toggle.ts
@@ -0,0 +1,33 @@
+import { html, unsafeCSS } from "lit"
+import { customElement } from "lit/decorators.js"
+import { spread } from "@open-wc/lit-helpers"
+import * as toggle from "@zag-js/toggle"
+import styleComponent from "@zag-js/shared/src/css/toggle.css?inline"
+import styleLayout from "@zag-js/shared/src/css/layout.css?inline"
+import stylePage from "./page.css?inline"
+import { MachineController, normalizeProps } from "@zag-js/lit"
+import { Bold, createElement } from "lucide"
+import { PageElement } from "../lib/page-element"
+
+@customElement("toggle-page")
+export class TogglePage extends PageElement {
+ static styles = unsafeCSS(styleComponent + styleLayout + stylePage)
+
+ private machine = new MachineController(this, toggle.machine)
+
+ render() {
+ const api = toggle.connect(this.machine.service, normalizeProps)
+
+ return html`
+
+
+ ${createElement(Bold)}
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/examples/lit-ts/src/vite-env.d.ts b/examples/lit-ts/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/examples/lit-ts/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/lit-ts/tsconfig.json b/examples/lit-ts/tsconfig.json
new file mode 100644
index 0000000000..833d304523
--- /dev/null
+++ b/examples/lit-ts/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "experimentalDecorators": true,
+ "useDefineForClassFields": false,
+ "module": "ESNext",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src/**/*.ts"]
+}
diff --git a/examples/next-ts/pages/collapsible.tsx b/examples/next-ts/pages/collapsible.tsx
index 18de760cd3..d1c052e841 100644
--- a/examples/next-ts/pages/collapsible.tsx
+++ b/examples/next-ts/pages/collapsible.tsx
@@ -40,7 +40,9 @@ export default function Page() {
Toggle Controls
-
api.setOpen(true)}>Open
+
api.setOpen(true)}>
+ Open
+
api.setOpen(false)}>Close
diff --git a/examples/nuxt-ts/app/pages/collapsible.vue b/examples/nuxt-ts/app/pages/collapsible.vue
index 6b8fcbcfe5..8de743bbca 100644
--- a/examples/nuxt-ts/app/pages/collapsible.vue
+++ b/examples/nuxt-ts/app/pages/collapsible.vue
@@ -35,6 +35,12 @@ const api = computed(() => collapsible.connect(service, normalizeProps))
+
+
+
Toggle Controls
+
Open
+
Close
+
diff --git a/examples/solid-ts/src/routes/collapsible.tsx b/examples/solid-ts/src/routes/collapsible.tsx
index dfa27a5e75..fe896187f3 100644
--- a/examples/solid-ts/src/routes/collapsible.tsx
+++ b/examples/solid-ts/src/routes/collapsible.tsx
@@ -37,7 +37,9 @@ export default function Page() {
Toggle Controls
-
api().setOpen(true)}>Open
+
api().setOpen(true)}>
+ Open
+
api().setOpen(false)}>Close
diff --git a/examples/svelte-ts/src/routes/collapsible.svelte b/examples/svelte-ts/src/routes/collapsible.svelte
index ed1ba4e47f..d3cbf1ed3c 100644
--- a/examples/svelte-ts/src/routes/collapsible.svelte
+++ b/examples/svelte-ts/src/routes/collapsible.svelte
@@ -41,7 +41,7 @@
Toggle Controls
-
api.setOpen(true)}>Open
+
api.setOpen(true)}>Open
api.setOpen(false)}>Close
diff --git a/package.json b/package.json
index 26afdea8b1..0a1d732820 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"e2e-react": "cross-env FRAMEWORK=react playwright test",
"e2e-vue": "cross-env FRAMEWORK=vue playwright test",
"e2e-solid": "cross-env FRAMEWORK=solid playwright test",
+ "e2e-lit": "cross-env FRAMEWORK=lit playwright test",
"generate-machine": "plop machine && pnpm sync-pkgs",
"generate-util": "plop utility && pnpm sync-pkgs",
"typecheck": "pnpm packages -- typecheck",
@@ -45,7 +46,8 @@
"test-svelte": "cd packages/frameworks/svelte && pnpm exec vitest --passWithNoTests",
"test-solid": "cd packages/frameworks/solid && pnpm exec vitest --passWithNoTests",
"test-vue": "cd packages/frameworks/vue && pnpm exec vitest --passWithNoTests",
- "test": "pnpm test-js --run && pnpm test-react --run && pnpm test-svelte --run && pnpm test-solid --run && pnpm test-vue --run",
+ "test-lit": "cd packages/frameworks/lit && pnpm exec vitest --passWithNoTests",
+ "test": "pnpm test-js --run && pnpm test-react --run && pnpm test-svelte --run && pnpm test-solid --run && pnpm test-vue --run && pnpm test-lit --run",
"slack": "tsx scripts/slack.ts",
"play": "tsx scripts/play.ts",
"document-types": "tsx scripts/typedocs.ts",
diff --git a/packages/core/src/merge-props.ts b/packages/core/src/merge-props.ts
index d1e0e8ab63..bf12553319 100644
--- a/packages/core/src/merge-props.ts
+++ b/packages/core/src/merge-props.ts
@@ -38,36 +38,46 @@ type TupleTypes = T[number]
type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never
-export function mergeProps(...args: T[]): UnionToIntersection> {
- let result: Props = {}
-
- for (let props of args) {
- for (let key in result) {
- if (key.startsWith("on") && typeof result[key] === "function" && typeof props[key] === "function") {
- result[key] = callAll(props[key], result[key])
- continue
- }
+interface MergePropsOptions {
+ eventPrefix?: string
+}
- if (key === "className" || key === "class") {
- result[key] = clsx(result[key], props[key])
- continue
- }
+export type MergePropsFunction = (...args: T[]) => UnionToIntersection>
- if (key === "style") {
- result[key] = css(result[key], props[key])
- continue
- }
+export function createMergeProps({ eventPrefix = "on" }: MergePropsOptions = {}): MergePropsFunction {
+ return function mergeProps(...args: T[]): UnionToIntersection> {
+ let result: Props = {}
- result[key] = props[key] !== undefined ? props[key] : result[key]
- }
+ for (let props of args) {
+ for (let key in result) {
+ if (key.startsWith(eventPrefix) && typeof result[key] === "function" && typeof props[key] === "function") {
+ result[key] = callAll(props[key], result[key])
+ continue
+ }
+
+ if (key === "className" || key === "class") {
+ result[key] = clsx(result[key], props[key])
+ continue
+ }
- // Add props from b that are not in a
- for (let key in props) {
- if (result[key] === undefined) {
- result[key] = props[key]
+ if (key === "style") {
+ result[key] = css(result[key], props[key])
+ continue
+ }
+
+ result[key] = props[key] !== undefined ? props[key] : result[key]
+ }
+
+ // Add props from b that are not in a
+ for (let key in props) {
+ if (result[key] === undefined) {
+ result[key] = props[key]
+ }
}
}
- }
- return result as any
+ return result as any
+ }
}
+
+export const mergeProps = createMergeProps()
diff --git a/packages/frameworks/lit/CHANGELOG.md b/packages/frameworks/lit/CHANGELOG.md
new file mode 100644
index 0000000000..1423fc6bf4
--- /dev/null
+++ b/packages/frameworks/lit/CHANGELOG.md
@@ -0,0 +1,2302 @@
+# @zag-js/solid
+
+## 1.22.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.22.1
+ - @zag-js/store@1.22.1
+ - @zag-js/types@1.22.1
+ - @zag-js/utils@1.22.1
+
+## 1.22.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.22.0
+ - @zag-js/store@1.22.0
+ - @zag-js/types@1.22.0
+ - @zag-js/utils@1.22.0
+
+## 1.21.9
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.9
+ - @zag-js/store@1.21.9
+ - @zag-js/types@1.21.9
+ - @zag-js/utils@1.21.9
+
+## 1.21.8
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.8
+ - @zag-js/store@1.21.8
+ - @zag-js/types@1.21.8
+ - @zag-js/utils@1.21.8
+
+## 1.21.7
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.7
+ - @zag-js/store@1.21.7
+ - @zag-js/types@1.21.7
+ - @zag-js/utils@1.21.7
+
+## 1.21.6
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.6
+ - @zag-js/store@1.21.6
+ - @zag-js/types@1.21.6
+ - @zag-js/utils@1.21.6
+
+## 1.21.5
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.5
+ - @zag-js/store@1.21.5
+ - @zag-js/types@1.21.5
+ - @zag-js/utils@1.21.5
+
+## 1.21.4
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.4
+ - @zag-js/store@1.21.4
+ - @zag-js/types@1.21.4
+ - @zag-js/utils@1.21.4
+
+## 1.21.3
+
+### Patch Changes
+
+- [`7ff4117`](https://github.com/chakra-ui/zag/commit/7ff41177cfde7aeb92605f796a112de9079353a9) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Improve runtime performance of components by removing refs/events
+ from stateful to non-stateful objects.
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.3
+ - @zag-js/store@1.21.3
+ - @zag-js/types@1.21.3
+ - @zag-js/utils@1.21.3
+
+## 1.21.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.2
+ - @zag-js/store@1.21.2
+ - @zag-js/types@1.21.2
+ - @zag-js/utils@1.21.2
+
+## 1.21.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.1
+ - @zag-js/store@1.21.1
+ - @zag-js/types@1.21.1
+ - @zag-js/utils@1.21.1
+
+## 1.21.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.21.0
+ - @zag-js/store@1.21.0
+ - @zag-js/types@1.21.0
+ - @zag-js/utils@1.21.0
+
+## 1.20.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.20.1
+ - @zag-js/store@1.20.1
+ - @zag-js/types@1.20.1
+ - @zag-js/utils@1.20.1
+
+## 1.20.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.20.0
+ - @zag-js/store@1.20.0
+ - @zag-js/types@1.20.0
+ - @zag-js/utils@1.20.0
+
+## 1.19.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.19.0
+ - @zag-js/store@1.19.0
+ - @zag-js/types@1.19.0
+ - @zag-js/utils@1.19.0
+
+## 1.18.5
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.18.5
+ - @zag-js/store@1.18.5
+ - @zag-js/types@1.18.5
+ - @zag-js/utils@1.18.5
+
+## 1.18.4
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.18.4
+ - @zag-js/store@1.18.4
+ - @zag-js/types@1.18.4
+ - @zag-js/utils@1.18.4
+
+## 1.18.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.18.3
+ - @zag-js/store@1.18.3
+ - @zag-js/types@1.18.3
+ - @zag-js/utils@1.18.3
+
+## 1.18.2
+
+### Patch Changes
+
+- Updated dependencies [[`11843e6`](https://github.com/chakra-ui/zag/commit/11843e6adf62b906006890c8003b38da2850c8ee)]:
+ - @zag-js/utils@1.18.2
+ - @zag-js/core@1.18.2
+ - @zag-js/store@1.18.2
+ - @zag-js/types@1.18.2
+
+## 1.18.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.18.1
+ - @zag-js/store@1.18.1
+ - @zag-js/types@1.18.1
+ - @zag-js/utils@1.18.1
+
+## 1.18.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.18.0
+ - @zag-js/store@1.18.0
+ - @zag-js/types@1.18.0
+ - @zag-js/utils@1.18.0
+
+## 1.17.4
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.17.4
+ - @zag-js/store@1.17.4
+ - @zag-js/types@1.17.4
+ - @zag-js/utils@1.17.4
+
+## 1.17.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.17.3
+ - @zag-js/store@1.17.3
+ - @zag-js/types@1.17.3
+ - @zag-js/utils@1.17.3
+
+## 1.17.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.17.2
+ - @zag-js/store@1.17.2
+ - @zag-js/types@1.17.2
+ - @zag-js/utils@1.17.2
+
+## 1.17.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.17.1
+ - @zag-js/store@1.17.1
+ - @zag-js/types@1.17.1
+ - @zag-js/utils@1.17.1
+
+## 1.17.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.17.0
+ - @zag-js/store@1.17.0
+ - @zag-js/types@1.17.0
+ - @zag-js/utils@1.17.0
+
+## 1.16.0
+
+### Patch Changes
+
+- Updated dependencies [[`6f6c8f3`](https://github.com/chakra-ui/zag/commit/6f6c8f329d9eb9d9889eff4317c84a4f41d4bfb2)]:
+ - @zag-js/types@1.16.0
+ - @zag-js/core@1.16.0
+ - @zag-js/store@1.16.0
+ - @zag-js/utils@1.16.0
+
+## 1.15.7
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.7
+ - @zag-js/store@1.15.7
+ - @zag-js/types@1.15.7
+ - @zag-js/utils@1.15.7
+
+## 1.15.6
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.6
+ - @zag-js/store@1.15.6
+ - @zag-js/types@1.15.6
+ - @zag-js/utils@1.15.6
+
+## 1.15.5
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.5
+ - @zag-js/store@1.15.5
+ - @zag-js/types@1.15.5
+ - @zag-js/utils@1.15.5
+
+## 1.15.4
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.4
+ - @zag-js/store@1.15.4
+ - @zag-js/types@1.15.4
+ - @zag-js/utils@1.15.4
+
+## 1.15.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.3
+ - @zag-js/store@1.15.3
+ - @zag-js/types@1.15.3
+ - @zag-js/utils@1.15.3
+
+## 1.15.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.2
+ - @zag-js/store@1.15.2
+ - @zag-js/types@1.15.2
+ - @zag-js/utils@1.15.2
+
+## 1.15.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.1
+ - @zag-js/store@1.15.1
+ - @zag-js/types@1.15.1
+ - @zag-js/utils@1.15.1
+
+## 1.15.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.15.0
+ - @zag-js/store@1.15.0
+ - @zag-js/types@1.15.0
+ - @zag-js/utils@1.15.0
+
+## 1.14.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.14.0
+ - @zag-js/store@1.14.0
+ - @zag-js/types@1.14.0
+ - @zag-js/utils@1.14.0
+
+## 1.13.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.13.1
+ - @zag-js/store@1.13.1
+ - @zag-js/types@1.13.1
+ - @zag-js/utils@1.13.1
+
+## 1.13.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.13.0
+ - @zag-js/store@1.13.0
+ - @zag-js/types@1.13.0
+ - @zag-js/utils@1.13.0
+
+## 1.12.4
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.12.4
+ - @zag-js/store@1.12.4
+ - @zag-js/types@1.12.4
+ - @zag-js/utils@1.12.4
+
+## 1.12.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.12.3
+ - @zag-js/store@1.12.3
+ - @zag-js/types@1.12.3
+ - @zag-js/utils@1.12.3
+
+## 1.12.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.12.2
+ - @zag-js/store@1.12.2
+ - @zag-js/types@1.12.2
+ - @zag-js/utils@1.12.2
+
+## 1.12.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.12.1
+ - @zag-js/store@1.12.1
+ - @zag-js/types@1.12.1
+ - @zag-js/utils@1.12.1
+
+## 1.12.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.12.0
+ - @zag-js/store@1.12.0
+ - @zag-js/types@1.12.0
+ - @zag-js/utils@1.12.0
+
+## 1.11.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.11.0
+ - @zag-js/store@1.11.0
+ - @zag-js/types@1.11.0
+ - @zag-js/utils@1.11.0
+
+## 1.10.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.10.0
+ - @zag-js/store@1.10.0
+ - @zag-js/types@1.10.0
+ - @zag-js/utils@1.10.0
+
+## 1.9.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.9.3
+ - @zag-js/store@1.9.3
+ - @zag-js/types@1.9.3
+ - @zag-js/utils@1.9.3
+
+## 1.9.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.9.2
+ - @zag-js/store@1.9.2
+ - @zag-js/types@1.9.2
+ - @zag-js/utils@1.9.2
+
+## 1.9.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.9.1
+ - @zag-js/store@1.9.1
+ - @zag-js/types@1.9.1
+ - @zag-js/utils@1.9.1
+
+## 1.9.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.9.0
+ - @zag-js/store@1.9.0
+ - @zag-js/types@1.9.0
+ - @zag-js/utils@1.9.0
+
+## 1.8.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.8.2
+ - @zag-js/store@1.8.2
+ - @zag-js/types@1.8.2
+ - @zag-js/utils@1.8.2
+
+## 1.8.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.8.1
+ - @zag-js/store@1.8.1
+ - @zag-js/types@1.8.1
+ - @zag-js/utils@1.8.1
+
+## 1.8.0
+
+### Patch Changes
+
+- [`66f7828`](https://github.com/chakra-ui/zag/commit/66f7828541102fcf4f0fba05bb241e20a5ed45cb) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - [Internal] Support `ref` and `cleanup` on bindable function to
+ help create using state compositions
+
+- Updated dependencies [[`66f7828`](https://github.com/chakra-ui/zag/commit/66f7828541102fcf4f0fba05bb241e20a5ed45cb)]:
+ - @zag-js/core@1.8.0
+ - @zag-js/store@1.8.0
+ - @zag-js/types@1.8.0
+ - @zag-js/utils@1.8.0
+
+## 1.7.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.7.0
+ - @zag-js/store@1.7.0
+ - @zag-js/types@1.7.0
+ - @zag-js/utils@1.7.0
+
+## 1.6.2
+
+### Patch Changes
+
+- [#2377](https://github.com/chakra-ui/zag/pull/2377)
+ [`e5ba28a`](https://github.com/chakra-ui/zag/commit/e5ba28a01ccab8afa2f11a82b67031a82e2675f5) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Ensure machine has started before processing events.
+
+- Updated dependencies []:
+ - @zag-js/core@1.6.2
+ - @zag-js/store@1.6.2
+ - @zag-js/types@1.6.2
+ - @zag-js/utils@1.6.2
+
+## 1.6.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.6.1
+ - @zag-js/store@1.6.1
+ - @zag-js/types@1.6.1
+ - @zag-js/utils@1.6.1
+
+## 1.6.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.6.0
+ - @zag-js/store@1.6.0
+ - @zag-js/types@1.6.0
+ - @zag-js/utils@1.6.0
+
+## 1.5.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.5.0
+ - @zag-js/store@1.5.0
+ - @zag-js/types@1.5.0
+ - @zag-js/utils@1.5.0
+
+## 1.4.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.4.2
+ - @zag-js/store@1.4.2
+ - @zag-js/types@1.4.2
+ - @zag-js/utils@1.4.2
+
+## 1.4.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.4.1
+ - @zag-js/store@1.4.1
+ - @zag-js/types@1.4.1
+ - @zag-js/utils@1.4.1
+
+## 1.4.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.4.0
+ - @zag-js/store@1.4.0
+ - @zag-js/types@1.4.0
+ - @zag-js/utils@1.4.0
+
+## 1.3.3
+
+### Patch Changes
+
+- Updated dependencies [[`66ba41b`](https://github.com/chakra-ui/zag/commit/66ba41bb10b232ff08e3cfbfc6cbf2a1c7449e21)]:
+ - @zag-js/utils@1.3.3
+ - @zag-js/core@1.3.3
+ - @zag-js/store@1.3.3
+ - @zag-js/types@1.3.3
+
+## 1.3.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.3.2
+ - @zag-js/store@1.3.2
+ - @zag-js/types@1.3.2
+ - @zag-js/utils@1.3.2
+
+## 1.3.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.3.1
+ - @zag-js/store@1.3.1
+ - @zag-js/types@1.3.1
+ - @zag-js/utils@1.3.1
+
+## 1.3.0
+
+### Minor Changes
+
+- [`c92f847`](https://github.com/chakra-ui/zag/commit/c92f84728ae473ac4c437009cbd79125747e5dd0) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Support `reenter:true` in machine transitions
+
+### Patch Changes
+
+- [`01566a1`](https://github.com/chakra-ui/zag/commit/01566a171ef426410b29b881fe1014bd26c2f86f) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where machines that hold complex objects
+
+- Updated dependencies []:
+ - @zag-js/core@1.3.0
+ - @zag-js/store@1.3.0
+ - @zag-js/types@1.3.0
+ - @zag-js/utils@1.3.0
+
+## 1.2.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.2.1
+ - @zag-js/store@1.2.1
+ - @zag-js/types@1.2.1
+ - @zag-js/utils@1.2.1
+
+## 1.2.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.2.0
+ - @zag-js/store@1.2.0
+ - @zag-js/types@1.2.0
+ - @zag-js/utils@1.2.0
+
+## 1.1.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.1.0
+ - @zag-js/store@1.1.0
+ - @zag-js/types@1.1.0
+ - @zag-js/utils@1.1.0
+
+## 1.0.2
+
+### Patch Changes
+
+- [`08d4b92`](https://github.com/chakra-ui/zag/commit/08d4b926a136d0098f04d631c8be0f66579bfb20) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where `undefined` values were not filtered out before
+ resolving props
+
+- Updated dependencies []:
+ - @zag-js/core@1.0.2
+ - @zag-js/store@1.0.2
+ - @zag-js/types@1.0.2
+ - @zag-js/utils@1.0.2
+
+## 1.0.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@1.0.1
+ - @zag-js/store@1.0.1
+ - @zag-js/types@1.0.1
+ - @zag-js/utils@1.0.1
+
+## 1.0.0
+
+### Patch Changes
+
+- Updated dependencies [[`b1caa44`](https://github.com/chakra-ui/zag/commit/b1caa44085e7f1da0ad24fc7b25178081811646c)]:
+ - @zag-js/core@1.0.0
+ - @zag-js/store@1.0.0
+ - @zag-js/types@1.0.0
+ - @zag-js/utils@1.0.0
+
+## 0.82.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.82.2
+ - @zag-js/store@0.82.2
+ - @zag-js/types@0.82.2
+
+## 0.82.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.82.1
+ - @zag-js/store@0.82.1
+ - @zag-js/types@0.82.1
+
+## 0.82.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.82.0
+ - @zag-js/store@0.82.0
+ - @zag-js/types@0.82.0
+
+## 0.81.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.81.2
+ - @zag-js/store@0.81.2
+ - @zag-js/types@0.81.2
+
+## 0.81.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.81.1
+ - @zag-js/store@0.81.1
+ - @zag-js/types@0.81.1
+
+## 0.81.0
+
+### Patch Changes
+
+- Updated dependencies [[`552e55d`](https://github.com/chakra-ui/zag/commit/552e55db4ec8c0fa86c5b7e5ce3ad08eb350ca68)]:
+ - @zag-js/types@0.81.0
+ - @zag-js/core@0.81.0
+ - @zag-js/store@0.81.0
+
+## 0.80.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.80.0
+ - @zag-js/store@0.80.0
+ - @zag-js/types@0.80.0
+
+## 0.79.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.79.3
+ - @zag-js/store@0.79.3
+ - @zag-js/types@0.79.3
+
+## 0.79.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.79.2
+ - @zag-js/store@0.79.2
+ - @zag-js/types@0.79.2
+
+## 0.79.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.79.1
+ - @zag-js/store@0.79.1
+ - @zag-js/types@0.79.1
+
+## 0.79.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.79.0
+ - @zag-js/store@0.79.0
+ - @zag-js/types@0.79.0
+
+## 0.78.3
+
+### Patch Changes
+
+- Updated dependencies [[`5584a83`](https://github.com/chakra-ui/zag/commit/5584a833151ee9f2c2ef9c07b6d699addfbca18e)]:
+ - @zag-js/store@0.78.3
+ - @zag-js/core@0.78.3
+ - @zag-js/types@0.78.3
+
+## 0.78.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.78.2
+ - @zag-js/store@0.78.2
+ - @zag-js/types@0.78.2
+
+## 0.78.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.78.1
+ - @zag-js/store@0.78.1
+ - @zag-js/types@0.78.1
+
+## 0.78.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.78.0
+ - @zag-js/store@0.78.0
+ - @zag-js/types@0.78.0
+
+## 0.77.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.77.1
+ - @zag-js/store@0.77.1
+ - @zag-js/types@0.77.1
+
+## 0.77.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.77.0
+ - @zag-js/store@0.77.0
+ - @zag-js/types@0.77.0
+
+## 0.76.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.76.0
+ - @zag-js/store@0.76.0
+ - @zag-js/types@0.76.0
+
+## 0.75.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.75.0
+ - @zag-js/store@0.75.0
+ - @zag-js/types@0.75.0
+
+## 0.74.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.74.2
+ - @zag-js/store@0.74.2
+ - @zag-js/types@0.74.2
+
+## 0.74.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.74.1
+ - @zag-js/store@0.74.1
+ - @zag-js/types@0.74.1
+
+## 0.74.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.74.0
+ - @zag-js/store@0.74.0
+ - @zag-js/types@0.74.0
+
+## 0.73.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.73.1
+ - @zag-js/store@0.73.1
+ - @zag-js/types@0.73.1
+
+## 0.73.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.73.0
+ - @zag-js/store@0.73.0
+ - @zag-js/types@0.73.0
+
+## 0.72.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.72.0
+ - @zag-js/store@0.72.0
+ - @zag-js/types@0.72.0
+
+## 0.71.0
+
+### Minor Changes
+
+- [`b3a251e`](https://github.com/chakra-ui/zag/commit/b3a251e5e10b9b27af353e0f41117329846b14e9) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - We no longer ship `src` files in the packages.
+
+### Patch Changes
+
+- Updated dependencies [[`b3a251e`](https://github.com/chakra-ui/zag/commit/b3a251e5e10b9b27af353e0f41117329846b14e9)]:
+ - @zag-js/core@0.71.0
+ - @zag-js/store@0.71.0
+ - @zag-js/types@0.71.0
+
+## 0.70.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.70.0
+ - @zag-js/store@0.70.0
+ - @zag-js/types@0.70.0
+
+## 0.69.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.69.0
+ - @zag-js/store@0.69.0
+ - @zag-js/types@0.69.0
+
+## 0.68.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.68.1
+ - @zag-js/store@0.68.1
+ - @zag-js/types@0.68.1
+
+## 0.68.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.68.0
+ - @zag-js/store@0.68.0
+ - @zag-js/types@0.68.0
+
+## 0.67.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.67.0
+ - @zag-js/store@0.67.0
+ - @zag-js/types@0.67.0
+
+## 0.66.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.66.1
+ - @zag-js/store@0.66.1
+ - @zag-js/types@0.66.1
+
+## 0.66.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.66.0
+ - @zag-js/store@0.66.0
+ - @zag-js/types@0.66.0
+
+## 0.65.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.65.1
+ - @zag-js/store@0.65.1
+ - @zag-js/types@0.65.1
+
+## 0.65.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.65.0
+ - @zag-js/store@0.65.0
+ - @zag-js/types@0.65.0
+
+## 0.64.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.64.0
+ - @zag-js/store@0.64.0
+ - @zag-js/types@0.64.0
+
+## 0.63.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.63.0
+ - @zag-js/store@0.63.0
+ - @zag-js/types@0.63.0
+
+## 0.62.1
+
+### Patch Changes
+
+- Updated dependencies [[`5644790`](https://github.com/chakra-ui/zag/commit/564479081d37cd06bc38043fccf9c229379a1531)]:
+ - @zag-js/core@0.62.1
+ - @zag-js/store@0.62.1
+ - @zag-js/types@0.62.1
+
+## 0.62.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.62.0
+ - @zag-js/store@0.62.0
+ - @zag-js/types@0.62.0
+
+## 0.61.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.61.1
+ - @zag-js/store@0.61.1
+ - @zag-js/types@0.61.1
+
+## 0.61.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.61.0
+ - @zag-js/store@0.61.0
+ - @zag-js/types@0.61.0
+
+## 0.60.0
+
+### Patch Changes
+
+- Updated dependencies [[`49bf73b`](https://github.com/chakra-ui/zag/commit/49bf73b7119bdd5dfd40d33119c3543626e201f0)]:
+ - @zag-js/store@0.60.0
+ - @zag-js/core@0.60.0
+ - @zag-js/types@0.60.0
+
+## 0.59.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.59.0
+ - @zag-js/store@0.59.0
+ - @zag-js/types@0.59.0
+
+## 0.58.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.58.3
+ - @zag-js/store@0.58.3
+ - @zag-js/types@0.58.3
+
+## 0.58.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.58.2
+ - @zag-js/store@0.58.2
+ - @zag-js/types@0.58.2
+
+## 0.58.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.58.1
+ - @zag-js/store@0.58.1
+ - @zag-js/types@0.58.1
+
+## 0.58.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.58.0
+ - @zag-js/store@0.58.0
+ - @zag-js/types@0.58.0
+
+## 0.57.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.57.0
+ - @zag-js/store@0.57.0
+ - @zag-js/types@0.57.0
+
+## 0.56.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.56.1
+ - @zag-js/store@0.56.1
+ - @zag-js/types@0.56.1
+
+## 0.56.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.56.0
+ - @zag-js/store@0.56.0
+ - @zag-js/types@0.56.0
+
+## 0.55.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.55.0
+ - @zag-js/store@0.55.0
+ - @zag-js/types@0.55.0
+
+## 0.54.0
+
+### Patch Changes
+
+- Updated dependencies [[`590c177`](https://github.com/chakra-ui/zag/commit/590c1779f5208fb99114c872175e779508f2f96d)]:
+ - @zag-js/core@0.54.0
+ - @zag-js/store@0.54.0
+ - @zag-js/types@0.54.0
+
+## 0.53.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.53.0
+ - @zag-js/store@0.53.0
+ - @zag-js/types@0.53.0
+
+## 0.52.0
+
+### Patch Changes
+
+- [`cec00aa`](https://github.com/chakra-ui/zag/commit/cec00aa477b827daeb5fed773364ffa73d107f26) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Fix SSR issue in Solid.js where spreading `readOnly: false` adds
+ the `readonly` attribute on editable elements, making them uneditable.
+- Updated dependencies []:
+ - @zag-js/core@0.52.0
+ - @zag-js/store@0.52.0
+ - @zag-js/types@0.52.0
+
+## 0.51.2
+
+### Patch Changes
+
+- Updated dependencies [[`62eb21b`](https://github.com/chakra-ui/zag/commit/62eb21b60355dd0645936baf4692315134e7488c)]:
+ - @zag-js/core@0.51.2
+ - @zag-js/store@0.51.2
+ - @zag-js/types@0.51.2
+
+## 0.51.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.51.1
+ - @zag-js/store@0.51.1
+ - @zag-js/types@0.51.1
+
+## 0.51.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.51.0
+ - @zag-js/store@0.51.0
+ - @zag-js/types@0.51.0
+
+## 0.50.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.50.0
+ - @zag-js/store@0.50.0
+ - @zag-js/types@0.50.0
+
+## 0.49.0
+
+### Patch Changes
+
+- Updated dependencies [[`c8aeca4`](https://github.com/chakra-ui/zag/commit/c8aeca475b078806c2765659668d843037746ba6)]:
+ - @zag-js/store@0.49.0
+ - @zag-js/core@0.49.0
+ - @zag-js/types@0.49.0
+
+## 0.48.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.48.0
+ - @zag-js/store@0.48.0
+ - @zag-js/types@0.48.0
+
+## 0.47.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.47.0
+ - @zag-js/store@0.47.0
+ - @zag-js/types@0.47.0
+
+## 0.46.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.46.0
+ - @zag-js/store@0.46.0
+ - @zag-js/types@0.46.0
+
+## 0.45.0
+
+### Minor Changes
+
+- [`ccb34b5`](https://github.com/chakra-ui/zag/commit/ccb34b5268e5e93083ad2ad7edbffa0c64ac2657) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Rewrite `mergeProps` to prevent issues with children that read
+ from context, and ensure props are always up-to-date.
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.45.0
+ - @zag-js/store@0.45.0
+ - @zag-js/types@0.45.0
+
+## 0.44.0
+
+### Minor Changes
+
+- [`198f525`](https://github.com/chakra-ui/zag/commit/198f5253a09eac721c5bae9e468588f9d93ea7bb) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - [Breaking] Refactor `mergeProps` from solid-js to ensure
+ consistent merging of props with other frameworks. The previous implementation was returning a Proxy object which was
+ causing issues.
+
+ Now it returns a signal that can be called to get the merged props. Under the hood, it uses the `createMemo` function
+ from `solid-js`.
+
+ **Before**
+
+ ```js
+ const props = mergeProps({ a: 1 }, { a: 2 })
+ props // Proxy { a: 2 }
+ ```
+
+ **After**
+
+ ```js
+ const props = mergeProps({ a: 1 }, { a: 2 })
+ props() // { a: 2 }
+ ```
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.44.0
+ - @zag-js/store@0.44.0
+ - @zag-js/types@0.44.0
+
+## 0.43.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.43.0
+ - @zag-js/store@0.43.0
+ - @zag-js/types@0.43.0
+
+## 0.42.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.42.0
+ - @zag-js/store@0.42.0
+ - @zag-js/types@0.42.0
+
+## 0.41.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.41.0
+ - @zag-js/store@0.41.0
+ - @zag-js/types@0.41.0
+
+## 0.40.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.40.0
+ - @zag-js/store@0.40.0
+ - @zag-js/types@0.40.0
+
+## 0.39.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.39.0
+ - @zag-js/store@0.39.0
+ - @zag-js/types@0.39.0
+
+## 0.38.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.38.1
+ - @zag-js/store@0.38.1
+ - @zag-js/types@0.38.1
+
+## 0.38.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.38.0
+ - @zag-js/store@0.38.0
+ - @zag-js/types@0.38.0
+
+## 0.37.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.37.3
+ - @zag-js/store@0.37.3
+ - @zag-js/types@0.37.3
+
+## 0.37.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.37.2
+ - @zag-js/store@0.37.2
+ - @zag-js/types@0.37.2
+
+## 0.37.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.37.1
+ - @zag-js/store@0.37.1
+ - @zag-js/types@0.37.1
+
+## 0.37.0
+
+### Patch Changes
+
+- Updated dependencies [[`2a024fb`](https://github.com/chakra-ui/zag/commit/2a024fbd2e98343218d4d658e91f1d8c751e1a4d)]:
+ - @zag-js/types@0.37.0
+ - @zag-js/core@0.37.0
+ - @zag-js/store@0.37.0
+
+## 0.36.3
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.36.3
+ - @zag-js/store@0.36.3
+ - @zag-js/types@0.36.3
+
+## 0.36.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.36.2
+ - @zag-js/store@0.36.2
+ - @zag-js/types@0.36.2
+
+## 0.36.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.36.1
+ - @zag-js/store@0.36.1
+ - @zag-js/types@0.36.1
+
+## 0.36.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.36.0
+ - @zag-js/store@0.36.0
+ - @zag-js/types@0.36.0
+
+## 0.35.0
+
+### Patch Changes
+
+- Updated dependencies [[`0216161`](https://github.com/chakra-ui/zag/commit/0216161fd3d429409abc96941d33a0c333ef8d36)]:
+ - @zag-js/store@0.35.0
+ - @zag-js/core@0.35.0
+ - @zag-js/types@0.35.0
+
+## 0.34.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.34.0
+ - @zag-js/store@0.34.0
+ - @zag-js/types@0.34.0
+
+## 0.33.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.33.2
+ - @zag-js/store@0.33.2
+ - @zag-js/types@0.33.2
+
+## 0.33.1
+
+### Patch Changes
+
+- Updated dependencies [[`80af758`](https://github.com/chakra-ui/zag/commit/80af758900606b43afc5b1e23edbf043a5e085ae)]:
+ - @zag-js/store@0.33.1
+ - @zag-js/core@0.33.1
+ - @zag-js/types@0.33.1
+
+## 0.33.0
+
+### Patch Changes
+
+- Updated dependencies [[`7872cdf`](https://github.com/chakra-ui/zag/commit/7872cdf8aeb28b9a30cd4a016bd12e5366054511)]:
+ - @zag-js/core@0.33.0
+ - @zag-js/store@0.33.0
+ - @zag-js/types@0.33.0
+
+## 0.32.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.32.1
+ - @zag-js/store@0.32.1
+ - @zag-js/types@0.32.1
+
+## 0.32.0
+
+### Patch Changes
+
+- [#1095](https://github.com/chakra-ui/zag/pull/1095)
+ [`651346b`](https://github.com/chakra-ui/zag/commit/651346b1cd280b3882253425e9054caf985f83a7) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Refactor useSnapshot, useService and useMachine to track context
+ changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.32.0
+ - @zag-js/store@0.32.0
+ - @zag-js/types@0.32.0
+
+## 0.31.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.31.1
+ - @zag-js/store@0.31.1
+ - @zag-js/types@0.31.1
+
+## 0.31.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.31.0
+ - @zag-js/store@0.31.0
+ - @zag-js/types@0.31.0
+
+## 0.30.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.30.0
+ - @zag-js/store@0.30.0
+ - @zag-js/types@0.30.0
+
+## 0.29.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.29.0
+ - @zag-js/store@0.29.0
+ - @zag-js/types@0.29.0
+
+## 0.28.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.28.1
+ - @zag-js/store@0.28.1
+ - @zag-js/types@0.28.1
+
+## 0.28.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.28.0
+ - @zag-js/store@0.28.0
+ - @zag-js/types@0.28.0
+
+## 0.27.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.27.1
+ - @zag-js/store@0.27.1
+ - @zag-js/types@0.27.1
+
+## 0.27.0
+
+### Patch Changes
+
+- Updated dependencies [[`152b0a78`](https://github.com/chakra-ui/zag/commit/152b0a78b6ba18442f38164ce90789bc243f6e00)]:
+ - @zag-js/core@0.27.0
+ - @zag-js/store@0.27.0
+ - @zag-js/types@0.27.0
+
+## 0.26.0
+
+### Minor Changes
+
+- [`56ca6a97`](https://github.com/chakra-ui/zag/commit/56ca6a977695fb3993558664f8e185de6ab7eb77) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Remove support for pressable machine in favor of using native
+ button. We no longer want to maintain this machine due to the internal complexity across devices and browsers.
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.26.0
+ - @zag-js/store@0.26.0
+ - @zag-js/types@0.26.0
+
+## 0.25.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.25.0
+ - @zag-js/store@0.25.0
+ - @zag-js/types@0.25.0
+
+## 0.24.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.24.0
+ - @zag-js/store@0.24.0
+ - @zag-js/types@0.24.0
+
+## 0.23.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.23.0
+ - @zag-js/store@0.23.0
+ - @zag-js/types@0.23.0
+
+## 0.22.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.22.0
+ - @zag-js/store@0.22.0
+ - @zag-js/types@0.22.0
+
+## 0.21.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.21.0
+ - @zag-js/store@0.21.0
+ - @zag-js/types@0.21.0
+
+## 0.20.0
+
+### Patch Changes
+
+- [`942db6ca`](https://github.com/chakra-ui/zag/commit/942db6caf9f699d6af56929c835b10ae80cfbc85) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Remove toggle machine
+
+- Updated dependencies [[`9a3a82f0`](https://github.com/chakra-ui/zag/commit/9a3a82f0b3738beda59c313fafd51360e6b0322f),
+ [`942db6ca`](https://github.com/chakra-ui/zag/commit/942db6caf9f699d6af56929c835b10ae80cfbc85)]:
+ - @zag-js/types@0.20.0
+ - @zag-js/core@0.20.0
+ - @zag-js/store@0.20.0
+
+## 0.19.1
+
+### Patch Changes
+
+- Updated dependencies [[`3f0b6a19`](https://github.com/chakra-ui/zag/commit/3f0b6a19dcf9779846efb2bc093235299301bbdb)]:
+ - @zag-js/core@0.19.1
+ - @zag-js/store@0.19.1
+ - @zag-js/types@0.19.1
+
+## 0.19.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.19.0
+ - @zag-js/store@0.19.0
+ - @zag-js/types@0.19.0
+
+## 0.18.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.18.0
+ - @zag-js/store@0.18.0
+ - @zag-js/types@0.18.0
+
+## 0.17.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.17.0
+ - @zag-js/store@0.17.0
+ - @zag-js/types@0.17.0
+
+## 0.16.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.16.0
+ - @zag-js/store@0.16.0
+ - @zag-js/types@0.16.0
+
+## 0.15.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.15.0
+ - @zag-js/store@0.15.0
+ - @zag-js/types@0.15.0
+
+## 0.14.0
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.14.0
+ - @zag-js/store@0.14.0
+ - @zag-js/types@0.14.0
+
+## 0.13.0
+
+### Patch Changes
+
+- Updated dependencies [[`4a2d8b77`](https://github.com/chakra-ui/zag/commit/4a2d8b77d1e71ad6b6c10134bc4186db6e6c0414)]:
+ - @zag-js/core@0.13.0
+ - @zag-js/store@0.13.0
+ - @zag-js/types@0.13.0
+
+## 0.12.0
+
+### Patch Changes
+
+- [`1da42934`](https://github.com/chakra-ui/zag/commit/1da429345166a0c00602f305cfd8ac11c5b14c10) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Export `PropType` utility for usage in the `PublicApi`
+
+- Updated dependencies []:
+ - @zag-js/core@0.12.0
+ - @zag-js/store@0.12.0
+ - @zag-js/types@0.12.0
+
+## 0.11.2
+
+### Patch Changes
+
+- [`0a2af673`](https://github.com/chakra-ui/zag/commit/0a2af67370de5ad4a4fd501f51c78aa76e6f3bf2) Thanks
+ [@cschroeter](https://github.com/cschroeter)! - Export missing types
+
+- Updated dependencies []:
+ - @zag-js/core@0.11.2
+ - @zag-js/store@0.11.2
+ - @zag-js/types@0.11.2
+
+## 0.11.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.11.1
+ - @zag-js/store@0.11.1
+ - @zag-js/types@0.11.1
+
+## 0.11.0
+
+### Patch Changes
+
+- [`4f371874`](https://github.com/chakra-ui/zag/commit/4f3718742dc88a2cd8726bdd889c9bbde94f5bce) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Rebuild all packages using tsup
+
+- Updated dependencies [[`4f371874`](https://github.com/chakra-ui/zag/commit/4f3718742dc88a2cd8726bdd889c9bbde94f5bce)]:
+ - @zag-js/store@0.11.0
+ - @zag-js/types@0.11.0
+ - @zag-js/core@0.11.0
+
+## 0.10.5
+
+### Patch Changes
+
+- [`622eea18`](https://github.com/chakra-ui/zag/commit/622eea1834d575d2ddd225e96121561b334597eb) Thanks
+ [@cschroeter](https://github.com/cschroeter)! - Fix an issue with type declarations
+
+- Updated dependencies []:
+ - @zag-js/core@0.10.5
+ - @zag-js/store@0.10.5
+ - @zag-js/types@0.10.5
+
+## 0.10.4
+
+### Patch Changes
+
+- [`2e2524e9`](https://github.com/chakra-ui/zag/commit/2e2524e9cfdb829037da8073d7fc5ad895556672) Thanks
+ [@cschroeter](https://github.com/cschroeter)! - Declare solid-js, react and vue as external dependencies
+
+- Updated dependencies []:
+ - @zag-js/core@0.10.4
+ - @zag-js/store@0.10.4
+ - @zag-js/types@0.10.4
+
+## 0.10.3
+
+### Patch Changes
+
+- [`c59a8dec`](https://github.com/chakra-ui/zag/commit/c59a8dec15ab57d218823bfe7af6d723972be6c7) Thanks
+ [@cschroeter](https://github.com/cschroeter)! - Use vite to build packages
+
+- Updated dependencies [[`c59a8dec`](https://github.com/chakra-ui/zag/commit/c59a8dec15ab57d218823bfe7af6d723972be6c7)]:
+ - @zag-js/core@0.10.3
+ - @zag-js/store@0.10.3
+ - @zag-js/types@0.10.3
+
+## 0.10.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.10.2
+ - @zag-js/store@0.10.2
+ - @zag-js/types@0.10.2
+
+## 0.10.1
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.10.1
+ - @zag-js/store@0.10.1
+ - @zag-js/types@0.10.1
+
+## 0.10.0
+
+### Patch Changes
+
+- Updated dependencies [[`2a1fb4a0`](https://github.com/chakra-ui/zag/commit/2a1fb4a0740e6ad8e2902265e14597f087007675)]:
+ - @zag-js/types@0.10.0
+ - @zag-js/core@0.10.0
+ - @zag-js/store@0.10.0
+
+## 0.9.2
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @zag-js/core@0.9.2
+ - @zag-js/store@0.9.2
+ - @zag-js/types@0.9.2
+
+## 0.9.1
+
+### Patch Changes
+
+- [`8469daa1`](https://github.com/chakra-ui/zag/commit/8469daa15fd7f2c0a80869a8715b0342bd3c355f) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Force release every package to fix regression
+
+- Updated dependencies [[`8469daa1`](https://github.com/chakra-ui/zag/commit/8469daa15fd7f2c0a80869a8715b0342bd3c355f)]:
+ - @zag-js/core@0.9.1
+ - @zag-js/store@0.9.1
+ - @zag-js/types@0.9.1
+
+## 0.8.0
+
+### Minor Changes
+
+- [`bb037fb9`](https://github.com/chakra-ui/zag/commit/bb037fb985c54fe508d274c7e8fa0b4c7b20909d) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Expose propTraps from solid package
+
+### Patch Changes
+
+- [`f17f0363`](https://github.com/chakra-ui/zag/commit/f17f036309bda6c11199a49ce09e5f35fd880a71) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Update solid-js version
+
+## 0.7.0
+
+### Patch Changes
+
+- [`413cdf18`](https://github.com/chakra-ui/zag/commit/413cdf180f718469c9c8b879a43aa4501d1ae59c) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Improve reactivity of `mergeProps`
+
+- Updated dependencies [[`413cdf18`](https://github.com/chakra-ui/zag/commit/413cdf180f718469c9c8b879a43aa4501d1ae59c)]:
+ - @zag-js/core@0.7.0
+
+## 0.5.0
+
+### Patch Changes
+
+- Updated dependencies [[`ec07ff35`](https://github.com/chakra-ui/zag/commit/ec07ff3590916ebcb4450b64207370ee2af9d3d1),
+ [`54377b1c`](https://github.com/chakra-ui/zag/commit/54377b1c4ed85deb06453a00648b7c2c1f0c72df)]:
+ - @zag-js/core@0.5.0
+ - @zag-js/types@0.5.0
+
+## 0.3.1
+
+### Patch Changes
+
+- Updated dependencies [[`30dbeb28`](https://github.com/chakra-ui/zag/commit/30dbeb282f7901c33518097a0e1dd9a857f7efb0)]:
+ - @zag-js/core@0.2.12
+
+## 0.3.0
+
+### Minor Changes
+
+- [`51ca61aa`](https://github.com/chakra-ui/zag/commit/51ca61aab3b2c8f188fa87f4a4f06ece673cf240) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Add support for passing an accessor or signal as transient
+ context.
+
+ ```jsx
+ function Component(props) {
+ const [state, send] = useMachine(machine({ id: createUniqueId() }), {
+ context: createMemo(() => ({
+ max: props.max,
+ min: props.min,
+ })),
+ })
+ }
+ ```
+
+### Patch Changes
+
+- Updated dependencies [[`1e10b1f4`](https://github.com/chakra-ui/zag/commit/1e10b1f40016f5c9bdf0924a3470b9383c0dbce2)]:
+ - @zag-js/core@0.2.11
+
+## 0.2.10
+
+### Patch Changes
+
+- [`5277f653`](https://github.com/chakra-ui/zag/commit/5277f65311c46e5792f605021d58b3b7e7dc3eaa) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Update dependencies to latest versions
+
+- Updated dependencies [[`5277f653`](https://github.com/chakra-ui/zag/commit/5277f65311c46e5792f605021d58b3b7e7dc3eaa)]:
+ - @zag-js/store@0.2.8
+ - @zag-js/core@0.2.10
+
+## 0.2.9
+
+### Patch Changes
+
+- Updated dependencies [[`df27f257`](https://github.com/chakra-ui/zag/commit/df27f257f53d194013b528342d3d9aef994d0d5c)]:
+ - @zag-js/core@0.2.9
+ - @zag-js/store@0.2.7
+
+## 0.2.8
+
+### Patch Changes
+
+- Updated dependencies [[`28dd7680`](https://github.com/chakra-ui/zag/commit/28dd768067f153e1f142154c8a8ce9bbde3746e2)]:
+ - @zag-js/store@0.2.6
+ - @zag-js/core@0.2.8
+
+## 0.2.7
+
+### Patch Changes
+
+- [`6957678d`](https://github.com/chakra-ui/zag/commit/6957678d2f00f4d219e791dffed91446e64211e7) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Switch to `es2020` to support `import.meta.env`
+
+- [`fef822b9`](https://github.com/chakra-ui/zag/commit/fef822b91a4a9dbfc3c1e8f88a89727a3231326a) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Add `style` to prop types in `normalizeProps`
+
+- Updated dependencies [[`f7bb988a`](https://github.com/chakra-ui/zag/commit/f7bb988aaeda6c6caebe95823f4cd44baa0d5e78),
+ [`6957678d`](https://github.com/chakra-ui/zag/commit/6957678d2f00f4d219e791dffed91446e64211e7),
+ [`fef822b9`](https://github.com/chakra-ui/zag/commit/fef822b91a4a9dbfc3c1e8f88a89727a3231326a)]:
+ - @zag-js/core@0.2.7
+ - @zag-js/store@0.2.5
+ - @zag-js/types@0.3.4
+
+## 0.2.6
+
+### Patch Changes
+
+- Updated dependencies [[`80de0b7c`](https://github.com/chakra-ui/zag/commit/80de0b7c7f888a254a3e1fec2da5338e235bc699),
+ [`88ccbbed`](https://github.com/chakra-ui/zag/commit/88ccbbed937acf10d4338e2c6d7f1e6b9eb538c8)]:
+ - @zag-js/core@0.2.6
+ - @zag-js/store@0.2.4
+
+## 0.2.5
+
+### Patch Changes
+
+- Updated dependencies [[`c1f609df`](https://github.com/chakra-ui/zag/commit/c1f609dfabbc31c296ebdc1e89480313130f832b),
+ [`6e6f0f4d`](https://github.com/chakra-ui/zag/commit/6e6f0f4d757b63b045af15639e7ae101c25514da),
+ [`c7e85e20`](https://github.com/chakra-ui/zag/commit/c7e85e20d4d08b56852768becf2fc5f7f4275dcc)]:
+ - @zag-js/types@0.3.3
+ - @zag-js/store@0.2.3
+ - @zag-js/core@0.2.5
+
+## 0.2.4
+
+### Patch Changes
+
+- [#462](https://github.com/chakra-ui/zag/pull/462)
+ [`f8c47a2b`](https://github.com/chakra-ui/zag/commit/f8c47a2b4442bfadc4d98315a8c1ac4aa4020822) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Update packages to use explicit `exports` field in `package.json`
+
+- Updated dependencies [[`4c98f016`](https://github.com/chakra-ui/zag/commit/4c98f016ae3d48b1b74f4dc8c302ef9a1c664260),
+ [`f8c47a2b`](https://github.com/chakra-ui/zag/commit/f8c47a2b4442bfadc4d98315a8c1ac4aa4020822)]:
+ - @zag-js/core@0.2.4
+ - @zag-js/store@0.2.2
+ - @zag-js/types@0.3.2
+
+## 0.2.3
+
+### Patch Changes
+
+- [`9332f1ca`](https://github.com/chakra-ui/zag/commit/9332f1caf5122c16a3edb48e20664b04714d226c) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Bump dependencies
+
+- Updated dependencies [[`9d936614`](https://github.com/chakra-ui/zag/commit/9d93661439f10a550c154e9f290905d32e8f509b)]:
+ - @zag-js/core@0.2.3
+ - @zag-js/store@0.2.1
+
+## 0.2.2
+
+### Patch Changes
+
+- [`44feef0b`](https://github.com/chakra-ui/zag/commit/44feef0bdf312e27d6faf1aa8ab0ecff0281108c) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Provide deep merge utility for nested context values
+
+- Updated dependencies [[`44feef0b`](https://github.com/chakra-ui/zag/commit/44feef0bdf312e27d6faf1aa8ab0ecff0281108c),
+ [`810e7d85`](https://github.com/chakra-ui/zag/commit/810e7d85274a26e0fe76dbdb2829fd7ab7f982a6),
+ [`e328b306`](https://github.com/chakra-ui/zag/commit/e328b306bf06d151fff4907a7e8e1160f07af855),
+ [`65976dd5`](https://github.com/chakra-ui/zag/commit/65976dd51902b1c4a4460cd196467156a705a999)]:
+ - @zag-js/core@0.2.2
+ - @zag-js/types@0.3.1
+
+## 0.2.1
+
+### Patch Changes
+
+- [#384](https://github.com/chakra-ui/zag/pull/384)
+ [`4aa6955f`](https://github.com/chakra-ui/zag/commit/4aa6955fab7ff6fee8545dcf491576640c69c64e) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Improve support for updating the internal machine options.
+
+ Fix react controlled context.
+
+- [#381](https://github.com/chakra-ui/zag/pull/381)
+ [`21775db5`](https://github.com/chakra-ui/zag/commit/21775db5ac318b095f603e7030ec7645e104f663) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Omit undefined values passed in machine's context
+
+- Updated dependencies [[`4aa6955f`](https://github.com/chakra-ui/zag/commit/4aa6955fab7ff6fee8545dcf491576640c69c64e)]:
+ - @zag-js/core@0.2.1
+
+## 0.2.0
+
+### Minor Changes
+
+- [#375](https://github.com/chakra-ui/zag/pull/375)
+ [`9cb4e9de`](https://github.com/chakra-ui/zag/commit/9cb4e9de28a3c6666860bc068c86be67a3b1a2ca) Thanks
+ [@darrylblake](https://github.com/darrylblake)! - Ensures code is transpiled with `es2019` target for environments
+ that don't support `es2020` and up, i.e. Cypress.
+
+### Patch Changes
+
+- Updated dependencies [[`9cb4e9de`](https://github.com/chakra-ui/zag/commit/9cb4e9de28a3c6666860bc068c86be67a3b1a2ca)]:
+ - @zag-js/core@0.2.0
+ - @zag-js/store@0.2.0
+ - @zag-js/types@0.3.0
+
+## 0.1.17
+
+### Patch Changes
+
+- [`52552156`](https://github.com/chakra-ui/zag/commit/52552156ded1b00f873576f52b11d0414f5dfee7) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Force new release
+
+- Updated dependencies [[`52552156`](https://github.com/chakra-ui/zag/commit/52552156ded1b00f873576f52b11d0414f5dfee7)]:
+ - @zag-js/core@0.1.12
+ - @zag-js/store@0.1.4
+ - @zag-js/types@0.2.7
+
+## 0.1.16
+
+### Patch Changes
+
+- [#325](https://github.com/chakra-ui/zag/pull/325)
+ [`c0cc303e`](https://github.com/chakra-ui/zag/commit/c0cc303e9824ea395c06d9faa699d23e19ef6538) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Switch packages to use ESM and `type=module`
+
+- Updated dependencies [[`61c11646`](https://github.com/chakra-ui/zag/commit/61c116467c1758bdda7efe1f27d4ed26e7d44624),
+ [`c0cc303e`](https://github.com/chakra-ui/zag/commit/c0cc303e9824ea395c06d9faa699d23e19ef6538)]:
+ - @zag-js/core@0.1.11
+ - @zag-js/store@0.1.3
+ - @zag-js/types@0.2.6
+
+## 0.1.15
+
+### Patch Changes
+
+- [`55e6a55c`](https://github.com/chakra-ui/zag/commit/55e6a55c37a60eea5caa446270cd1f6012d7363d) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Bump all packages
+
+- Updated dependencies [[`ce97956c`](https://github.com/chakra-ui/zag/commit/ce97956c0586ce842f7b082dd71cc6d68909ad58),
+ [`55e6a55c`](https://github.com/chakra-ui/zag/commit/55e6a55c37a60eea5caa446270cd1f6012d7363d)]:
+ - @zag-js/core@0.1.10
+ - @zag-js/store@0.1.2
+ - @zag-js/types@0.2.5
+
+## 0.1.14
+
+### Patch Changes
+
+- Updated dependencies [[`1d30333e`](https://github.com/chakra-ui/zag/commit/1d30333e0d3011707950adab26878cde9ed1c242)]:
+ - @zag-js/types@0.2.4
+
+## 0.1.13
+
+### Patch Changes
+
+- [#224](https://github.com/chakra-ui/zag/pull/224)
+ [`b7eb3f20`](https://github.com/chakra-ui/zag/commit/b7eb3f204cda6ac913b66787c27942294abfb0ee) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Update framework dependencies
+
+## 0.1.12
+
+### Patch Changes
+
+- [`2a2566b8`](https://github.com/chakra-ui/zag/commit/2a2566b8be1441ae98215bec594e4c996f3b8aaf) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Trigger new version due to changes in build chain
+
+- Updated dependencies [[`2a2566b8`](https://github.com/chakra-ui/zag/commit/2a2566b8be1441ae98215bec594e4c996f3b8aaf)]:
+ - @zag-js/core@0.1.9
+ - @zag-js/store@0.1.1
+ - @zag-js/types@0.2.3
+
+## 0.1.11
+
+### Patch Changes
+
+- [#195](https://github.com/chakra-ui/zag/pull/195)
+ [`90f2e443`](https://github.com/chakra-ui/zag/commit/90f2e44376f012d14e3703d6959392e4f3bdddd0) Thanks
+ [@anubra266](https://github.com/anubra266)! - Improve SSR by omitting `useSetup` step.
+
+* [#197](https://github.com/chakra-ui/zag/pull/197)
+ [`4ea550d9`](https://github.com/chakra-ui/zag/commit/4ea550d9983e0d20af123481f256cc5cf03d2358) Thanks
+ [@anubra266](https://github.com/anubra266)! - Remove `useSetup` hook
+
+* Updated dependencies [[`c5872be2`](https://github.com/chakra-ui/zag/commit/c5872be2fe057675fb8c7c64ed2c10b99daf697e)]:
+ - @zag-js/core@0.1.8
+
+## 0.1.10
+
+### Patch Changes
+
+- [#178](https://github.com/chakra-ui/zag/pull/178)
+ [`1abed11b`](https://github.com/chakra-ui/zag/commit/1abed11bda7fc56fd3f77c3b842e89a934ee3253) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - BREAKING 💥: Refactor connect function in favor of uniform APIs
+ across frameworks
+
+ Due to the fact that we tried to make "React" the baseline, there was a lot of inherent complexity in how we managed
+ types in the codebase.
+
+ We've now removed the `PropTypes` export in favor of passing `normalizeProps` in the `api.connect` function. This is
+ now required for React as well.
+
+ You can remove the `` generic and Zag will auto-infer the types from `normalizeProps`.
+
+ **For Vue and Solid**
+
+ ```diff
+ -api.connect(state, send, normalizeProps)
+ +api.connect(state, send, normalizeProps)
+ ```
+
+ **For React**
+
+ ```diff
+ -api.connect(state, send)
+ +api.connect(state, send, normalizeProps)
+ ```
+
+* [`3a53a1e9`](https://github.com/chakra-ui/zag/commit/3a53a1e97306a9fedf1706b95f8e38b03750c2f3) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Refactor to use local `@zag-js/store` package
+
+- [`664e61f9`](https://github.com/chakra-ui/zag/commit/664e61f94844f0405b7e646e4a30b8f0f737f21c) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Pin dependency versions
+
+* [`a630876a`](https://github.com/chakra-ui/zag/commit/a630876ac2c0544aed2f3694a50f175799d3464d) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Normalize the default checked and default value props
+
+* Updated dependencies [[`1abed11b`](https://github.com/chakra-ui/zag/commit/1abed11bda7fc56fd3f77c3b842e89a934ee3253),
+ [`3a53a1e9`](https://github.com/chakra-ui/zag/commit/3a53a1e97306a9fedf1706b95f8e38b03750c2f3),
+ [`3a53a1e9`](https://github.com/chakra-ui/zag/commit/3a53a1e97306a9fedf1706b95f8e38b03750c2f3)]:
+ - @zag-js/core@0.1.7
+ - @zag-js/store@0.1.0
+
+## 0.1.9
+
+### Patch Changes
+
+- [#143](https://github.com/chakra-ui/zag/pull/143)
+ [`ea8c878f`](https://github.com/chakra-ui/zag/commit/ea8c878f8e6f8b09aed30d0284ada66aa5700761) Thanks
+ [@renovate](https://github.com/apps/renovate)! - chore(deps): update dependency solid-js to v1.4.4
+
+## 0.1.8
+
+### Patch Changes
+
+- Updated dependencies [[`5982d826`](https://github.com/chakra-ui/zag/commit/5982d826126a7b83252fcd0b0479079fccb62189)]:
+ - @zag-js/core@0.1.6
+
+## 0.1.7
+
+### Patch Changes
+
+- [`3e920136`](https://github.com/chakra-ui/zag/commit/3e920136c537445a36cf0d04045de1d8ff037ecf) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Expose type utilities to frameworks
+
+* [`9ebe6b45`](https://github.com/chakra-ui/zag/commit/9ebe6b455bfc1b7bf1ad8f770d70ea7656b6c1fe) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Remove unneeded `Promise.resolve(...)`
+
+* Updated dependencies [[`0d3065e9`](https://github.com/chakra-ui/zag/commit/0d3065e94d707d3161d901576421beae66c32aba),
+ [`587cbec9`](https://github.com/chakra-ui/zag/commit/587cbec9b32ee9e8faef5ceeefb779231b152018)]:
+ - @zag-js/core@0.1.5
+
+## 0.1.6
+
+### Patch Changes
+
+- [#89](https://github.com/chakra-ui/zag/pull/89)
+ [`a71d5d2a`](https://github.com/chakra-ui/zag/commit/a71d5d2a984e4293ebeb55944e27df20492ad1c0) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Add incremental support for shadow root in machines
+
+- Updated dependencies [[`bcf247f1`](https://github.com/chakra-ui/zag/commit/bcf247f18afa5413a7b008f5ab5cbd3665350cb9),
+ [`a71d5d2a`](https://github.com/chakra-ui/zag/commit/a71d5d2a984e4293ebeb55944e27df20492ad1c0)]:
+ - @zag-js/core@0.1.4
+
+## 0.1.5
+
+### Patch Changes
+
+- Updated dependencies [[`46ef565`](https://github.com/chakra-ui/zag/commit/46ef5659a855a382af1e5b0e24d35d03466cfb22)]:
+ - @zag-js/core@0.1.3
+
+## 0.1.4
+
+### Patch Changes
+
+- Updated dependencies [[`3f715bd`](https://github.com/chakra-ui/zag/commit/3f715bdc4f52cdbf71ce9a22a3fc20d31c5fea89)]:
+ - @zag-js/core@0.1.2
+
+## 0.1.3
+
+### Patch Changes
+
+- [#62](https://github.com/chakra-ui/zag/pull/62)
+ [`e4441c6`](https://github.com/chakra-ui/zag/commit/e4441c6f1fae0f7d8391f0f1403138c70bbc6b1a) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Widen type for `element` type in `PropTypes`
+
+## 0.1.2
+
+### Patch Changes
+
+- Updated dependencies [[`8ef855e`](https://github.com/chakra-ui/zag/commit/8ef855efdf8aaca4355c816cc446bc745e34ec54)]:
+ - @zag-js/core@0.1.1
+
+## 0.1.1
+
+### Patch Changes
+
+- [`3e145c1`](https://github.com/chakra-ui/zag/commit/3e145c185d598766aae420f724c7759390cb0404) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Export `mergeProps` utility from framework packages
+
+## 0.1.0
+
+### Minor Changes
+
+- [`157aadc`](https://github.com/chakra-ui/zag/commit/157aadc3ac572d2289432efe32ae3f15a2be4ad1) Thanks
+ [@segunadebayo](https://github.com/segunadebayo)! - Initial release
+
+### Patch Changes
+
+- Updated dependencies [[`157aadc`](https://github.com/chakra-ui/zag/commit/157aadc3ac572d2289432efe32ae3f15a2be4ad1)]:
+ - @zag-js/core@0.1.0
diff --git a/packages/frameworks/lit/package.json b/packages/frameworks/lit/package.json
new file mode 100644
index 0000000000..6de9d34f38
--- /dev/null
+++ b/packages/frameworks/lit/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "@zag-js/lit",
+ "version": "1.22.1",
+ "description": "The lit wrapper for zag",
+ "keywords": [
+ "ui-machines",
+ "state-machines",
+ "zag",
+ "lit",
+ "controller",
+ "web-components"
+ ],
+ "author": "Segun Adebayo ",
+ "homepage": "https://github.com/chakra-ui/zag#readme",
+ "license": "MIT",
+ "repository": "https://github.com/chakra-ui/zag/tree/main/packages/frameworks/lit",
+ "sideEffects": false,
+ "files": [
+ "dist"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "bugs": {
+ "url": "https://github.com/chakra-ui/zag/issues"
+ },
+ "dependencies": {
+ "@solid-primitives/keyed": "^1.5.2",
+ "@zag-js/utils": "workspace:*",
+ "@zag-js/core": "workspace:*",
+ "@zag-js/store": "workspace:*",
+ "@zag-js/types": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/jsdom": "^21.1.7",
+ "solid-js": "1.9.9",
+ "lit": "^3.0.0",
+ "clean-package": "2.2.0",
+ "@solidjs/testing-library": "^0.8.10",
+ "@testing-library/jest-dom": "^6.8.0",
+ "jsdom": "^26.1.0"
+ },
+ "peerDependencies": {
+ "solid-js": ">=1.1.3",
+ "lit": ">=3.0.0"
+ },
+ "scripts": {
+ "build": "tsup",
+ "lint": "eslint src",
+ "typecheck": "tsc --noEmit",
+ "test": "vitest",
+ "prepack": "clean-package",
+ "postpack": "clean-package restore"
+ },
+ "clean-package": "../../../clean-package.config.json",
+ "main": "src/index.ts"
+}
diff --git a/packages/frameworks/lit/src/bindable.ts b/packages/frameworks/lit/src/bindable.ts
new file mode 100644
index 0000000000..34347acefc
--- /dev/null
+++ b/packages/frameworks/lit/src/bindable.ts
@@ -0,0 +1,58 @@
+import type { Bindable, BindableParams } from "@zag-js/core"
+import { proxy } from "@zag-js/store"
+import { isFunction } from "@zag-js/utils"
+
+export function bindable(props: () => BindableParams): Bindable {
+ const initial = props().value ?? props().defaultValue
+
+ if (props().debug) {
+ console.log(`[bindable > ${props().debug}] initial`, initial)
+ }
+
+ const eq = props().isEqual ?? Object.is
+
+ const store = proxy({ value: initial as T })
+
+ const controlled = () => props().value !== undefined
+
+ return {
+ initial,
+ ref: store,
+ get() {
+ return controlled() ? (props().value as T) : store.value
+ },
+ set(nextValue: T | ((prev: T) => T)) {
+ const prev = store.value
+ const next = isFunction(nextValue) ? nextValue(prev as T) : nextValue
+
+ if (props().debug) {
+ console.log(`[bindable > ${props().debug}] setValue`, { next, prev })
+ }
+
+ if (!controlled()) store.value = next
+ if (!eq(next, prev)) {
+ props().onChange?.(next, prev)
+ }
+ },
+ invoke(nextValue: T, prevValue: T) {
+ props().onChange?.(nextValue, prevValue)
+ },
+ hash(value: T) {
+ return props().hash?.(value) ?? String(value)
+ },
+ }
+}
+
+bindable.cleanup = (_fn: VoidFunction) => {
+ // No-op in vanilla implementation
+}
+
+bindable.ref = (defaultValue: T) => {
+ let value = defaultValue
+ return {
+ get: () => value,
+ set: (next: T) => {
+ value = next
+ },
+ }
+}
diff --git a/packages/frameworks/lit/src/index.ts b/packages/frameworks/lit/src/index.ts
new file mode 100644
index 0000000000..79e6c13681
--- /dev/null
+++ b/packages/frameworks/lit/src/index.ts
@@ -0,0 +1,4 @@
+export * from "./machine"
+export { MachineController } from "./machine-controller"
+export { mergeProps } from "./merge-props"
+export * from "./normalize-props"
diff --git a/packages/frameworks/lit/src/machine-controller.ts b/packages/frameworks/lit/src/machine-controller.ts
new file mode 100644
index 0000000000..7d2db30351
--- /dev/null
+++ b/packages/frameworks/lit/src/machine-controller.ts
@@ -0,0 +1,37 @@
+import type { Machine, MachineSchema } from "@zag-js/core"
+import type { ReactiveController, ReactiveControllerHost } from "lit"
+import { LitMachine } from "./machine"
+
+export class MachineController implements ReactiveController {
+ private machine: LitMachine
+
+ constructor(
+ private host: ReactiveControllerHost,
+ machineConfig: Machine,
+ getProps?: () => Partial,
+ ) {
+ this.machine = new LitMachine(machineConfig, getProps)
+
+ // Register for lifecycle updates
+ host.addController(this)
+ }
+
+ hostConnected() {
+ this.machine.subscribe(() => {
+ this.host.requestUpdate()
+ })
+ }
+
+ hostUpdated(): void {
+ // Start the machine after the initial html has been rendered
+ this.machine.start()
+ }
+
+ hostDisconnected() {
+ this.machine.stop()
+ }
+
+ get service() {
+ return this.machine.service
+ }
+}
diff --git a/packages/frameworks/lit/src/machine.ts b/packages/frameworks/lit/src/machine.ts
new file mode 100644
index 0000000000..904905ab80
--- /dev/null
+++ b/packages/frameworks/lit/src/machine.ts
@@ -0,0 +1,340 @@
+import type {
+ ActionsOrFn,
+ Bindable,
+ BindableContext,
+ BindableRefs,
+ ChooseFn,
+ ComputedFn,
+ EffectsOrFn,
+ GuardFn,
+ Machine,
+ MachineSchema,
+ Params,
+ PropFn,
+ Scope,
+ Service,
+} from "@zag-js/core"
+import { createScope, INIT_STATE, MachineStatus } from "@zag-js/core"
+import { subscribe } from "@zag-js/store"
+import { compact, identity, isEqual, isFunction, isString, runIfFn, toArray, warn } from "@zag-js/utils"
+import { bindable } from "./bindable"
+import { createRefs } from "./refs"
+
+export class LitMachine {
+ private readonly scope: Scope
+ private readonly ctx: BindableContext
+ private readonly prop: PropFn
+ private readonly state: Bindable
+ private readonly refs: BindableRefs
+ private readonly computed: ComputedFn
+
+ private event: any = { type: "" }
+ private previousEvent: any
+
+ private readonly effects = new Map()
+ private transition: any = null
+
+ private cleanups: VoidFunction[] = []
+ private subscriptions: Array<(service: Service) => void> = []
+ private trackers: { deps: any[]; fn: any }[] = []
+ private status = MachineStatus.NotStarted
+
+ private readonly getEvent = () => ({
+ ...this.event,
+ current: () => this.event,
+ previous: () => this.previousEvent,
+ })
+
+ private readonly getState = () => ({
+ ...this.state,
+ matches: (...values: T["state"][]) => values.includes(this.state.get()),
+ hasTag: (tag: T["tag"]) => !!this.machine.states[this.state.get() as T["state"]]?.tags?.includes(tag),
+ })
+
+ private readonly debug = (...args: any[]) => {
+ if (this.machine.debug) console.log(...args)
+ }
+
+ constructor(
+ private readonly machine: Machine,
+ userProps: Partial | (() => Partial) = {},
+ ) {
+ const self = this
+
+ // create scope
+ const { id, ids, getRootNode } = runIfFn(userProps) as any
+ this.scope = createScope({ id, ids, getRootNode })
+
+ // create prop
+ this.prop = (key) => {
+ const __props = runIfFn(userProps)
+ const props: any = machine.props?.({ props: compact(__props), scope: self.scope }) ?? __props
+ return props[key] as any
+ }
+
+ // create context
+ const context: any = machine.context?.({
+ prop: self.prop,
+ bindable,
+ scope: self.scope,
+ flush(fn: VoidFunction) {
+ queueMicrotask(fn) // requestUpdate?
+ },
+ getContext: () => self.ctx as any,
+ getComputed: () => self.computed as any,
+ getRefs: () => self.refs as any,
+ getEvent: self.getEvent,
+ })
+
+ // subscribe to context changes
+ if (context) {
+ Object.values(context).forEach((item: any) => {
+ const unsub = subscribe(item.ref, () => this.publish())
+ this.cleanups.push(unsub)
+ })
+ }
+
+ // context function
+ this.ctx = {
+ get(key) {
+ return context?.[key].get()
+ },
+ set(key, value) {
+ context?.[key].set(value)
+ },
+ initial(key) {
+ return context?.[key].initial
+ },
+ hash(key) {
+ const current = context?.[key].get()
+ return context?.[key].hash(current)
+ },
+ }
+
+ this.computed = (key) =>
+ machine.computed?.[key]({
+ context: self.ctx as any,
+ event: self.getEvent(),
+ prop: self.prop,
+ refs: self.refs,
+ scope: self.scope,
+ computed: self.computed as any,
+ }) ?? ({} as any)
+
+ this.refs = createRefs(self.machine.refs?.({ prop: self.prop, context: self.ctx }) ?? {})
+
+ // state
+ this.state = bindable(() => ({
+ defaultValue: self.machine.initialState({ prop: self.prop }),
+ onChange: (nextState, prevState) => {
+ // compute effects: exit -> transition -> enter
+
+ // exit effects
+ if (prevState) {
+ const exitEffects = self.effects.get(prevState)
+ exitEffects?.()
+ self.effects.delete(prevState)
+ }
+
+ // exit actions
+ if (prevState) {
+ // @ts-ignore
+ self.action(self.machine.states[prevState]?.exit)
+ }
+
+ // transition actions
+ self.action(self.transition?.actions)
+
+ // enter effect
+ // @ts-ignore
+ const cleanup = self.effect(self.machine.states[nextState]?.effects)
+ if (cleanup) self.effects.set(nextState as string, cleanup)
+
+ // root entry actions
+ if (prevState === INIT_STATE) {
+ self.action(self.machine.entry)
+ const cleanup = self.effect(self.machine.effects)
+ if (cleanup) self.effects.set(INIT_STATE, cleanup)
+ }
+
+ // enter actions
+ // @ts-ignore
+ self.action(self.machine.states[nextState]?.entry)
+ },
+ }))
+
+ this.cleanups.push(subscribe(this.state.ref, () => self.publish()))
+ }
+
+ private readonly send = (event: any) => {
+ queueMicrotask(() => {
+ // check status inside microtask to prevent race condition
+ if (this.status !== MachineStatus.Started) return
+
+ this.previousEvent = this.event
+ this.event = event
+
+ this.debug("send", event)
+
+ let currentState = this.state.get()
+
+ const transitions =
+ // @ts-ignore
+ this.machine.states[currentState].on?.[event.type] ??
+ // @ts-ignore
+ this.machine.on?.[event.type]
+
+ const transition = this.choose(transitions)
+ if (!transition) return
+
+ // save current transition
+ this.transition = transition
+ const target = transition.target ?? currentState
+
+ this.debug("transition", transition)
+
+ const changed = target !== currentState
+ if (changed) {
+ // state change is high priority
+ this.state.set(target)
+ } else {
+ // call transition actions
+ this.action(transition.actions)
+ }
+ })
+ }
+
+ private readonly action = (keys: ActionsOrFn | undefined) => {
+ const strs = isFunction(keys) ? keys(this.getParams()) : keys
+ if (!strs) return
+ const fns = strs.map((s) => {
+ const fn = this.machine.implementations?.actions?.[s]
+ if (!fn) warn(`[zag-js] No implementation found for action "${JSON.stringify(s)}"`)
+ return fn
+ })
+ for (const fn of fns) {
+ fn?.(this.getParams())
+ }
+ }
+
+ private readonly guard = (str: T["guard"] | GuardFn) => {
+ if (isFunction(str)) return str(this.getParams())
+ return this.machine.implementations?.guards?.[str](this.getParams())
+ }
+
+ private readonly effect = (keys: EffectsOrFn | undefined) => {
+ const strs = isFunction(keys) ? keys(this.getParams()) : keys
+ if (!strs) return
+ const fns = strs.map((s) => {
+ const fn = this.machine.implementations?.effects?.[s]
+ if (!fn) warn(`[zag-js] No implementation found for effect "${JSON.stringify(s)}"`)
+ return fn
+ })
+ const cleanups: VoidFunction[] = []
+ for (const fn of fns) {
+ const cleanup = fn?.(this.getParams())
+ if (cleanup) cleanups.push(cleanup)
+ }
+ return () => cleanups.forEach((fn) => fn?.())
+ }
+
+ private readonly choose: ChooseFn = (transitions) => {
+ return toArray(transitions).find((t: any) => {
+ let result = !t.guard
+ if (isString(t.guard)) result = !!this.guard(t.guard)
+ else if (isFunction(t.guard)) result = t.guard(this.getParams())
+ return result
+ })
+ }
+
+ start() {
+ if (this.status === MachineStatus.Started) return
+
+ this.status = MachineStatus.Started
+ this.debug("initializing...")
+ this.state.invoke(this.state.initial!, INIT_STATE)
+ this.setupTrackers()
+ }
+
+ stop() {
+ if (this.status === MachineStatus.Stopped) return
+
+ // run exit effects
+ this.effects.forEach((fn) => fn?.())
+ this.effects.clear()
+ this.transition = null
+ this.action(this.machine.exit)
+
+ // unsubscribe from all subscriptions
+ this.cleanups.forEach((unsub) => unsub())
+ this.cleanups = []
+ this.trackers = []
+ this.subscriptions = []
+
+ this.status = MachineStatus.Stopped
+ this.debug("unmounting...")
+ }
+
+ readonly subscribe = (fn: (service: Service) => void) => {
+ this.subscriptions.push(fn)
+ // return unsubscribe function
+ return () => {
+ const index = this.subscriptions.indexOf(fn)
+ if (index > -1) {
+ this.subscriptions.splice(index, 1)
+ }
+ }
+ }
+
+ get service(): Service {
+ return {
+ state: this.getState(),
+ send: this.send,
+ context: this.ctx,
+ prop: this.prop,
+ scope: this.scope,
+ refs: this.refs,
+ computed: this.computed,
+ event: this.getEvent(),
+ getStatus: () => this.status,
+ }
+ }
+
+ private readonly publish = () => {
+ this.callTrackers()
+ this.subscriptions.forEach((fn) => fn(this.service))
+ }
+
+ private readonly setupTrackers = () => {
+ this.machine.watch?.(this.getParams())
+ }
+
+ private readonly callTrackers = () => {
+ this.trackers.forEach(({ deps, fn }) => {
+ const next = deps.map((dep) => dep())
+ if (!isEqual(fn.prev, next)) {
+ fn()
+ fn.prev = next
+ }
+ })
+ }
+
+ private readonly getParams = (): Params => ({
+ state: this.getState(),
+ context: this.ctx,
+ event: this.getEvent(),
+ prop: this.prop,
+ send: this.send,
+ action: this.action,
+ guard: this.guard,
+ track: (deps: any[], fn: any) => {
+ fn.prev = deps.map((dep) => dep())
+ this.trackers.push({ deps, fn })
+ },
+ refs: this.refs,
+ computed: this.computed,
+ flush: identity, // requestUpdate?
+ scope: this.scope,
+ choose: this.choose,
+ })
+}
diff --git a/packages/frameworks/lit/src/merge-props.ts b/packages/frameworks/lit/src/merge-props.ts
new file mode 100644
index 0000000000..a761aad5fe
--- /dev/null
+++ b/packages/frameworks/lit/src/merge-props.ts
@@ -0,0 +1,4 @@
+import { createMergeProps } from "@zag-js/core"
+
+// Create mergeProps function for Lit's event syntax (@event instead of onEvent)
+export const mergeProps = createMergeProps({ eventPrefix: "@" })
diff --git a/packages/frameworks/lit/src/normalize-props.ts b/packages/frameworks/lit/src/normalize-props.ts
new file mode 100644
index 0000000000..026b323236
--- /dev/null
+++ b/packages/frameworks/lit/src/normalize-props.ts
@@ -0,0 +1,149 @@
+import { createNormalizer } from "@zag-js/types"
+import { isObject } from "@zag-js/utils"
+
+// Lit uses built-in template binding syntax for prop normalization:
+// - 'attribute-name': value -> attribute
+// - '?boolean-attr': true -> boolean attribute
+// - '@event-name': handler -> event listener
+// - '.property': value -> property assignment
+
+// TODO: Improve types
+type LitElementProps = {
+ [key: string]: any
+ style?: string | Record
+}
+
+type RequiredElements =
+ | "button"
+ | "label"
+ | "input"
+ | "textarea"
+ | "img"
+ | "output"
+ | "select"
+ | "rect"
+ | "circle"
+ | "svg"
+ | "path"
+
+export type PropTypes = Record & {
+ element: LitElementProps
+ style: Record
+}
+
+const propMap: Record = {
+ onFocus: "onfocusin",
+ onBlur: "onfocusout",
+ onDoubleClick: "ondblclick",
+ onChange: "oninput",
+ defaultChecked: "checked",
+ defaultValue: "value",
+ defaultSelected: "selected",
+ htmlFor: "for",
+ className: "class",
+}
+
+// Properties that should be set as properties, not attributes
+const propertyNames = new Set([
+ "value",
+ "checked",
+ "selected",
+ "disabled",
+ "readonly",
+ "multiple",
+ "hidden",
+ "contenteditable",
+ "draggable",
+ "spellcheck",
+ "autocomplete",
+])
+
+const svgAttributes = new Set(
+ "viewBox,preserveAspectRatio,fillRule,clipPath,clipRule,strokeWidth,strokeLinecap,strokeLinejoin,strokeDasharray,strokeDashoffset,strokeMiterlimit".split(
+ ",",
+ ),
+)
+
+const shouldPreserveCase = (key: string): boolean => {
+ return (
+ key.startsWith("data-") ||
+ key.startsWith("aria-") ||
+ key.includes(":") || // XML namespaced
+ key.startsWith("xml") || // XML attributes
+ svgAttributes.has(key)
+ )
+}
+
+export function toStyleString(style: Record): string {
+ let string = ""
+
+ for (let key in style) {
+ /**
+ * Ignore null and undefined values.
+ */
+ const value = style[key]
+ if (value === null || value === undefined) continue
+
+ /**
+ * Convert camelCase to kebab-case except for CSS custom properties.
+ */
+ if (!key.startsWith("--")) {
+ key = key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
+ }
+
+ string += `${key}:${value};`
+ }
+
+ return string
+}
+
+type Dict = Record
+
+export const normalizeProps = createNormalizer((props: Dict) => {
+ const normalized: Dict = {}
+
+ for (let key in props) {
+ const value = props[key]
+
+ // Handle style objects
+ if (key === "style" && isObject(value)) {
+ normalized["style"] = toStyleString(value)
+ continue
+ }
+
+ // Map React-style prop names
+ if (key in propMap) {
+ key = propMap[key]
+ }
+
+ // Convert to Lit event listener syntax: @eventname
+ if (key.startsWith("on") && typeof value === "function") {
+ normalized[`@${key.toLowerCase().slice(2)}`] = value
+ continue
+ }
+
+ // Handle boolean attributes with ? prefix
+ if (typeof value === "boolean") {
+ // ARIA booleans must be string values "true" or "false"
+ if (key.startsWith("aria-")) {
+ normalized[key] = value.toString()
+ continue
+ }
+
+ normalized[`?${key.toLowerCase()}`] = value
+ continue
+ }
+
+ // Handle properties that should be set as properties with . prefix
+ if (propertyNames.has(key.toLowerCase())) {
+ normalized[`.${key.toLowerCase()}`] = value
+ continue
+ }
+
+ // Everything else as attribute
+ const attrKey = shouldPreserveCase(key) ? key : key.toLowerCase()
+ normalized[attrKey] = value
+ }
+
+ return normalized
+})
diff --git a/packages/frameworks/lit/src/refs.ts b/packages/frameworks/lit/src/refs.ts
new file mode 100644
index 0000000000..81057207f3
--- /dev/null
+++ b/packages/frameworks/lit/src/refs.ts
@@ -0,0 +1,11 @@
+export function createRefs(refs: T) {
+ const ref = { current: refs }
+ return {
+ get(key: K): T[K] {
+ return ref.current[key]
+ },
+ set(key: K, value: T[K]) {
+ ref.current[key] = value
+ },
+ }
+}
diff --git a/packages/frameworks/lit/src/track.ts b/packages/frameworks/lit/src/track.ts
new file mode 100644
index 0000000000..c532a581c2
--- /dev/null
+++ b/packages/frameworks/lit/src/track.ts
@@ -0,0 +1,30 @@
+import { isEqual, isFunction } from "@zag-js/utils"
+import { createEffect } from "solid-js"
+
+function access(v: T | (() => T)): T {
+ if (isFunction(v)) return v()
+ return v
+}
+
+export const createTrack = (deps: any[], effect: VoidFunction) => {
+ let prevDeps: any[] = []
+ let isFirstRun = true
+ createEffect(() => {
+ if (isFirstRun) {
+ prevDeps = deps.map((d) => access(d))
+ isFirstRun = false
+ return
+ }
+ let changed = false
+ for (let i = 0; i < deps.length; i++) {
+ if (!isEqual(prevDeps[i], access(deps[i]))) {
+ changed = true
+ break
+ }
+ }
+ if (changed) {
+ prevDeps = deps.map((d) => access(d))
+ effect()
+ }
+ })
+}
diff --git a/packages/frameworks/lit/test-toggle.html b/packages/frameworks/lit/test-toggle.html
new file mode 100644
index 0000000000..cd48f48e3b
--- /dev/null
+++ b/packages/frameworks/lit/test-toggle.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Lit Toggle Test
+
+
+
+
+
+
diff --git a/packages/frameworks/lit/tests/machine.test.ts b/packages/frameworks/lit/tests/machine.test.ts
new file mode 100644
index 0000000000..51c59a5674
--- /dev/null
+++ b/packages/frameworks/lit/tests/machine.test.ts
@@ -0,0 +1,428 @@
+// TODO: These tests were AI generated and need review
+// - Fix TypeScript issues
+// - Verify test correctness and expectations
+// - Ensure tests match real Lit component lifecycle
+
+import { createMachine } from "@zag-js/core"
+import { MachineController } from "../src"
+
+// Mock LitElement for testing
+class MockLitElement {
+ updateRequested = 0
+ controllers: any[] = []
+
+ requestUpdate() {
+ this.updateRequested++
+ }
+
+ addController(controller: any) {
+ this.controllers.push(controller)
+ }
+
+ removeController(controller: any) {
+ const index = this.controllers.indexOf(controller)
+ if (index >= 0) {
+ this.controllers.splice(index, 1)
+ }
+ }
+}
+
+function renderMachine(machine: any, props: any = {}) {
+ const host = new MockLitElement()
+ const controller = new MachineController(host as any, machine, () => props)
+
+ // Simulate hostConnected
+ controller.hostConnected()
+
+ const send = async (event: any) => {
+ controller.service.send(event)
+ await Promise.resolve()
+ }
+
+ const { service } = controller
+
+ return { controller, host, send, service }
+}
+
+describe("LitMachine", () => {
+ test("initial state", () => {
+ const machine = createMachine({
+ initialState() {
+ return "foo"
+ },
+ states: {
+ foo: {
+ on: {
+ NEXT: { target: "bar" },
+ },
+ },
+ bar: {},
+ },
+ })
+
+ const { service } = renderMachine(machine)
+
+ expect(service.state.get()).toBe("foo")
+ })
+
+ test("initial entry action", async () => {
+ const fooEntry = vi.fn()
+ const rootEntry = vi.fn()
+
+ const machine = createMachine({
+ initialState() {
+ return "foo"
+ },
+ entry: ["rootEntry"],
+ states: {
+ foo: {
+ entry: ["fooEntry"],
+ },
+ },
+ implementations: {
+ actions: {
+ fooEntry,
+ rootEntry,
+ },
+ },
+ })
+
+ renderMachine(machine)
+ await Promise.resolve()
+
+ expect(fooEntry).toHaveBeenCalledOnce()
+ expect(rootEntry).toHaveBeenCalledOnce()
+ })
+
+ test("current state and context", () => {
+ const machine = createMachine({
+ initialState() {
+ return "test"
+ },
+ context({ bindable }) {
+ return { foo: bindable(() => ({ defaultValue: "bar" })) }
+ },
+ states: {
+ test: {},
+ },
+ })
+
+ const { service } = renderMachine(machine)
+
+ expect(service.state.get()).toEqual("test")
+ expect(service.context.get("foo")).toEqual("bar")
+ })
+
+ test("send event", async () => {
+ let done = vi.fn()
+ const machine = createMachine({
+ initialState() {
+ return "test"
+ },
+ context({ bindable }) {
+ return { foo: bindable(() => ({ defaultValue: "bar" })) }
+ },
+ states: {
+ test: {
+ on: {
+ CHANGE: { target: "success" },
+ },
+ },
+ success: {
+ entry: ["done"],
+ },
+ },
+ implementations: {
+ actions: {
+ done,
+ },
+ },
+ })
+
+ const { send } = renderMachine(machine)
+ await Promise.resolve()
+
+ await send({ type: "CHANGE" })
+ expect(done).toHaveBeenCalledOnce()
+ })
+
+ test("state tags", async () => {
+ const machine = createMachine({
+ initialState() {
+ return "green"
+ },
+ states: {
+ green: {
+ tags: ["go"],
+ on: {
+ TIMER: {
+ target: "yellow",
+ },
+ },
+ },
+ yellow: {
+ tags: ["go"],
+ on: {
+ TIMER: {
+ target: "red",
+ },
+ },
+ },
+ red: {
+ tags: ["stop"],
+ },
+ },
+ })
+
+ const { service, send } = renderMachine(machine)
+ await Promise.resolve()
+
+ expect(service.state.hasTag("go")).toBeTruthy()
+
+ await send({ type: "TIMER" })
+ expect(service.state.get()).toBe("yellow")
+ expect(service.state.hasTag("go")).toBeTruthy()
+
+ await send({ type: "TIMER" })
+ expect(service.state.get()).toBe("red")
+ expect(service.state.hasTag("go")).toBeFalsy()
+ })
+
+ test("computed", async () => {
+ const machine = createMachine({
+ initialState() {
+ return "test"
+ },
+ states: {
+ test: {
+ on: {
+ UPDATE: {
+ actions: ["setValue"],
+ },
+ },
+ },
+ },
+ context({ bindable }) {
+ return { value: bindable(() => ({ defaultValue: "bar" })) }
+ },
+ computed: {
+ length: ({ context }) => context.get("value").length,
+ },
+ implementations: {
+ actions: {
+ setValue: ({ context }) => context.set("value", "hello"),
+ },
+ },
+ })
+
+ const { service, send } = renderMachine(machine)
+ await Promise.resolve()
+
+ expect(service.computed("length")).toEqual(3)
+
+ await send({ type: "UPDATE" })
+ expect(service.computed("length")).toEqual(5)
+ })
+
+ test("watch", async () => {
+ const notify = vi.fn()
+ const machine = createMachine({
+ initialState() {
+ return "test"
+ },
+ states: {
+ test: {
+ on: {
+ UPDATE: {
+ actions: ["setValue"],
+ },
+ },
+ },
+ },
+ context({ bindable }) {
+ return { value: bindable(() => ({ defaultValue: "bar" })) }
+ },
+ watch({ track, context, action }) {
+ track([() => context.get("value")], () => {
+ action(["notify"])
+ })
+ },
+ implementations: {
+ actions: {
+ setValue: ({ context }) => context.set("value", "hello"),
+ notify,
+ },
+ },
+ })
+
+ const { send } = renderMachine(machine)
+
+ // send update twice and expect notify to be called once (since the value is the same)
+ await send({ type: "UPDATE" })
+ await send({ type: "UPDATE" })
+ expect(notify).toHaveBeenCalledOnce()
+ })
+
+ test("guard: basic", async () => {
+ const machine = createMachine({
+ props() {
+ return { max: 1 }
+ },
+ initialState() {
+ return "test"
+ },
+
+ context({ bindable }) {
+ return { count: bindable(() => ({ defaultValue: 0 })) }
+ },
+
+ states: {
+ test: {
+ on: {
+ INCREMENT: {
+ guard: "isBelowMax",
+ actions: ["increment"],
+ },
+ },
+ },
+ },
+
+ implementations: {
+ guards: {
+ isBelowMax: ({ prop, context }) => prop("max") > context.get("count"),
+ },
+ actions: {
+ increment: ({ context }) => context.set("count", context.get("count") + 1),
+ },
+ },
+ })
+
+ const { service, send } = renderMachine(machine, { max: 1 })
+ await Promise.resolve()
+
+ await send({ type: "INCREMENT" })
+ expect(service.context.get("count")).toEqual(1)
+
+ await send({ type: "INCREMENT" })
+ expect(service.context.get("count")).toEqual(1)
+ })
+
+ test("context: controlled", async () => {
+ const machine = createMachine({
+ props() {
+ return { value: "foo", defaultValue: "" }
+ },
+ initialState() {
+ return "test"
+ },
+
+ context({ bindable, prop }) {
+ return {
+ value: bindable(() => ({
+ defaultValue: prop("defaultValue"),
+ value: prop("value"),
+ })),
+ }
+ },
+
+ states: {
+ test: {
+ on: {
+ "VALUE.SET": {
+ actions: ["setValue"],
+ },
+ },
+ },
+ },
+
+ implementations: {
+ actions: {
+ setValue: ({ context, event }) => context.set("value", event.value),
+ },
+ },
+ })
+
+ const { service, send } = renderMachine(machine, { value: "foo", defaultValue: "" })
+
+ await send({ type: "VALUE.SET", value: "next" })
+
+ // since value is controlled, it should not change
+ expect(service.context.get("value")).toEqual("foo")
+ })
+})
+
+describe("MachineController", () => {
+ test("triggers host.requestUpdate on state changes", async () => {
+ const machine = createMachine({
+ initialState() {
+ return "idle"
+ },
+ states: {
+ idle: {
+ on: {
+ START: { target: "active" },
+ },
+ },
+ active: {},
+ },
+ })
+
+ const { host, send } = renderMachine(machine)
+
+ // Initial update from machine start
+ expect(host.updateRequested).toBeGreaterThan(0)
+ const initialUpdates = host.updateRequested
+
+ await send({ type: "START" })
+
+ // Should have triggered additional update
+ expect(host.updateRequested).toBeGreaterThan(initialUpdates)
+ })
+
+ test("provides service API", () => {
+ const machine = createMachine({
+ initialState() {
+ return "test"
+ },
+ context({ bindable }) {
+ return { value: bindable(() => ({ defaultValue: "initial" })) }
+ },
+ states: {
+ test: {},
+ },
+ })
+
+ const { service } = renderMachine(machine)
+
+ // Check all service API properties are available
+ expect(service.state).toBeDefined()
+ expect(service.send).toBeDefined()
+ expect(service.context).toBeDefined()
+ expect(service.prop).toBeDefined()
+ expect(service.scope).toBeDefined()
+ expect(service.refs).toBeDefined()
+ expect(service.computed).toBeDefined()
+ expect(service.event).toBeDefined()
+ })
+
+ test("cleanup on hostDisconnected", () => {
+ const machine = createMachine({
+ initialState() {
+ return "test"
+ },
+ states: {
+ test: {},
+ },
+ })
+
+ const host = new MockLitElement()
+ const controller = new MachineController(host as any, machine, () => ({}))
+
+ controller.hostConnected()
+ expect(controller.service).toBeDefined()
+
+ controller.hostDisconnected()
+ // Machine should be stopped and cleaned up
+ expect(controller.service.getStatus()).toBe("Stopped")
+ })
+})
diff --git a/packages/frameworks/lit/tests/merge-props.test.ts b/packages/frameworks/lit/tests/merge-props.test.ts
new file mode 100644
index 0000000000..53b8818cd9
--- /dev/null
+++ b/packages/frameworks/lit/tests/merge-props.test.ts
@@ -0,0 +1,94 @@
+import { mergeProps } from "../src"
+
+describe("mergeProps", () => {
+ it("handles one argument", () => {
+ const onClick = () => {}
+ const className = "primary"
+ const id = "test_id"
+
+ const props = mergeProps({ onClick, className, id })
+
+ expect(props.onClick).toBe(onClick)
+ expect(props.className).toBe(className)
+ expect(props.id).toBe(id)
+ })
+
+ it("combines handlers with @ prefix", () => {
+ let count = 0
+
+ const mockFn = vi.fn(() => {
+ count++
+ })
+
+ const props = mergeProps({ "@click": mockFn }, { "@click": mockFn }, { "@click": mockFn })
+
+ props["@click"]()
+ expect(mockFn).toBeCalledTimes(3)
+ expect(count).toBe(3)
+ })
+
+ it("combines css classes", () => {
+ const className1 = "primary"
+ const className2 = "hover"
+ const className3 = "focus"
+
+ const props = mergeProps({ class: className1 }, { class: className2 }, { class: className3 })
+ expect(props.class).toBe("primary hover focus")
+
+ const props2 = mergeProps({ className: className1 }, { className: className2 }, { className: className3 })
+ expect(props2.className).toBe("primary hover focus")
+ })
+
+ it("combines styles", () => {
+ const stringStyles = `
+ margin: 24px;
+ padding: 2;
+ background-image: url("http://example.com/image.png");
+ border: 1px solid #123456;
+ --x: 123;
+ `
+
+ const objStyles = {
+ margin: "10px",
+ "font-size": "2rem",
+ }
+
+ const props = mergeProps({ style: stringStyles }, { style: objStyles })
+
+ expect(props.style).toMatchInlineSnapshot(`
+ {
+ "--x": "123",
+ "background-image": "url("http://example.com/image.png")",
+ "border": "1px solid #123456",
+ "font-size": "2rem",
+ "margin": "10px",
+ "padding": "2",
+ }
+ `)
+ })
+
+ it("handles Lit @ event prefix", () => {
+ const mockFn1 = vi.fn()
+ const mockFn2 = vi.fn()
+
+ const props = mergeProps({ "@click": mockFn1 }, { "@click": mockFn2 })
+
+ props["@click"]("test")
+ expect(mockFn1).toBeCalledWith("test")
+ expect(mockFn2).toBeCalledWith("test")
+ })
+
+ it("last value overwrites the event-listeners", () => {
+ const mockFn = vi.fn()
+ const message1 = "click1"
+ const message2 = "click2"
+
+ const props = mergeProps(
+ { "@event": () => mockFn(message1) },
+ { "@event": () => mockFn(message2) },
+ { "@event": "overwrites" },
+ )
+
+ expect(props["@event"]).toBe("overwrites")
+ })
+})
diff --git a/packages/frameworks/lit/tsconfig.json b/packages/frameworks/lit/tsconfig.json
new file mode 100644
index 0000000000..a56c4a3eee
--- /dev/null
+++ b/packages/frameworks/lit/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../tsconfig.json",
+ "include": ["src", "tests"],
+ "compilerOptions": {
+ "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo"
+ }
+}
diff --git a/packages/frameworks/lit/vite.config.ts b/packages/frameworks/lit/vite.config.ts
new file mode 100644
index 0000000000..f9a6eb55f8
--- /dev/null
+++ b/packages/frameworks/lit/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from "vitest/config"
+
+export default defineConfig({
+ test: {
+ retry: 2,
+ globals: true,
+ environment: "jsdom",
+ css: false,
+ setupFiles: "./vitest.setup.ts",
+ },
+})
diff --git a/packages/frameworks/lit/vitest.setup.ts b/packages/frameworks/lit/vitest.setup.ts
new file mode 100644
index 0000000000..d96f01d6f5
--- /dev/null
+++ b/packages/frameworks/lit/vitest.setup.ts
@@ -0,0 +1,9 @@
+import "@testing-library/jest-dom/vitest"
+import { JSDOM } from "jsdom"
+
+const { window } = new JSDOM()
+
+// @ts-ignore
+window.requestAnimationFrame = (cb: VoidFunction) => setTimeout(cb, 1000 / 60)
+
+Object.assign(global, { window, document: window.document })
diff --git a/playwright.config.ts b/playwright.config.ts
index e68b5c87ef..c53e6d4bae 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -34,6 +34,12 @@ export function getWebServer(): WebServer {
url: "http://localhost:3003",
reuseExistingServer: !CI,
},
+ lit: {
+ cwd: "./examples/lit-ts",
+ command: "pnpm vite --port 3004",
+ url: "http://localhost:3004",
+ reuseExistingServer: !CI,
+ },
}
return frameworks[framework]
diff --git a/playwright.lit.config.ts b/playwright.lit.config.ts
new file mode 100644
index 0000000000..4d29eedc19
--- /dev/null
+++ b/playwright.lit.config.ts
@@ -0,0 +1,53 @@
+import { defineConfig } from "@playwright/test"
+import baseConfig, { getWebServer } from "./playwright.config"
+
+// Set default Lit framework
+process.env.FRAMEWORK = process.env.FRAMEWORK || "lit"
+const webServer = getWebServer()
+
+export default defineConfig({
+ ...baseConfig,
+
+ webServer,
+ use: {
+ baseURL: webServer.url,
+ },
+
+ // Only test implemented Lit components
+ testMatch: [
+ "**/accordion.e2e.ts",
+ "**/checkbox.e2e.ts",
+ "**/collapsible.e2e.ts",
+ "**/dialog.e2e.ts",
+ "**/menu.e2e.ts",
+ "**/menu-nested.e2e.ts",
+ "**/menu-option.e2e.ts",
+ "**/popover.e2e.ts",
+ "**/radio-group.e2e.ts",
+ "**/slider.e2e.ts",
+ "**/switch.e2e.ts",
+ "**/tabs.e2e.ts",
+ "**/toggle-group.e2e.ts",
+ "**/toggle.e2e.ts",
+ ],
+
+ // // Custom projects for Lit testing with different DOM modes
+ // projects: [
+ // {
+ // name: "lit-light-dom",
+ // use: {
+ // ...baseConfig.use,
+ // },
+ // // Environment variables are set via process.env before test execution
+ // // VITE_DOM_MODE=light-dom will be set when running this project
+ // },
+ // {
+ // name: "lit-shadow-dom",
+ // use: {
+ // ...baseConfig.use,
+ // },
+ // // Environment variables are set via process.env before test execution
+ // // VITE_DOM_MODE=shadow-dom will be set when running this project
+ // },
+ // ],
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 72dfcf8cbb..af8a489d8c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -70,7 +70,7 @@ importers:
version: 10.1.8(eslint@9.34.0(jiti@2.5.1))
eslint-plugin-import:
specifier: 2.32.0
- version: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))
+ version: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1))
eslint-plugin-prettier:
specifier: 5.5.4
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1))(prettier@3.6.2)
@@ -129,6 +129,259 @@ importers:
specifier: 3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
+ examples/lit-ts:
+ dependencies:
+ '@internationalized/date':
+ specifier: 3.9.0
+ version: 3.9.0
+ '@open-wc/lit-helpers':
+ specifier: 0.7.0
+ version: 0.7.0(lit@3.3.1)
+ '@zag-js/accordion':
+ specifier: workspace:*
+ version: link:../../packages/machines/accordion
+ '@zag-js/anatomy':
+ specifier: workspace:*
+ version: link:../../packages/anatomy
+ '@zag-js/anatomy-icons':
+ specifier: workspace:*
+ version: link:../../packages/anatomy-icons
+ '@zag-js/angle-slider':
+ specifier: workspace:*
+ version: link:../../packages/machines/angle-slider
+ '@zag-js/aria-hidden':
+ specifier: workspace:*
+ version: link:../../packages/utilities/aria-hidden
+ '@zag-js/async-list':
+ specifier: workspace:*
+ version: link:../../packages/machines/async-list
+ '@zag-js/auto-resize':
+ specifier: workspace:*
+ version: link:../../packages/utilities/auto-resize
+ '@zag-js/avatar':
+ specifier: workspace:*
+ version: link:../../packages/machines/avatar
+ '@zag-js/carousel':
+ specifier: workspace:*
+ version: link:../../packages/machines/carousel
+ '@zag-js/checkbox':
+ specifier: workspace:*
+ version: link:../../packages/machines/checkbox
+ '@zag-js/clipboard':
+ specifier: workspace:*
+ version: link:../../packages/machines/clipboard
+ '@zag-js/collapsible':
+ specifier: workspace:*
+ version: link:../../packages/machines/collapsible
+ '@zag-js/collection':
+ specifier: workspace:*
+ version: link:../../packages/utilities/collection
+ '@zag-js/color-picker':
+ specifier: workspace:*
+ version: link:../../packages/machines/color-picker
+ '@zag-js/color-utils':
+ specifier: workspace:*
+ version: link:../../packages/utilities/color-utils
+ '@zag-js/combobox':
+ specifier: workspace:*
+ version: link:../../packages/machines/combobox
+ '@zag-js/core':
+ specifier: workspace:*
+ version: link:../../packages/core
+ '@zag-js/date-picker':
+ specifier: workspace:*
+ version: link:../../packages/machines/date-picker
+ '@zag-js/date-utils':
+ specifier: workspace:*
+ version: link:../../packages/utilities/date-utils
+ '@zag-js/dialog':
+ specifier: workspace:*
+ version: link:../../packages/machines/dialog
+ '@zag-js/dismissable':
+ specifier: workspace:*
+ version: link:../../packages/utilities/dismissable
+ '@zag-js/docs':
+ specifier: workspace:*
+ version: link:../../packages/docs
+ '@zag-js/dom-query':
+ specifier: workspace:*
+ version: link:../../packages/utilities/dom-query
+ '@zag-js/editable':
+ specifier: workspace:*
+ version: link:../../packages/machines/editable
+ '@zag-js/file-upload':
+ specifier: workspace:*
+ version: link:../../packages/machines/file-upload
+ '@zag-js/file-utils':
+ specifier: workspace:*
+ version: link:../../packages/utilities/file-utils
+ '@zag-js/floating-panel':
+ specifier: workspace:*
+ version: link:../../packages/machines/floating-panel
+ '@zag-js/focus-trap':
+ specifier: workspace:*
+ version: link:../../packages/utilities/focus-trap
+ '@zag-js/focus-visible':
+ specifier: workspace:*
+ version: link:../../packages/utilities/focus-visible
+ '@zag-js/highlight-word':
+ specifier: workspace:*
+ version: link:../../packages/utilities/highlight-word
+ '@zag-js/hover-card':
+ specifier: workspace:*
+ version: link:../../packages/machines/hover-card
+ '@zag-js/i18n-utils':
+ specifier: workspace:*
+ version: link:../../packages/utilities/i18n-utils
+ '@zag-js/interact-outside':
+ specifier: workspace:*
+ version: link:../../packages/utilities/interact-outside
+ '@zag-js/json-tree-utils':
+ specifier: workspace:*
+ version: link:../../packages/utilities/json-tree-utils
+ '@zag-js/listbox':
+ specifier: workspace:*
+ version: link:../../packages/machines/listbox
+ '@zag-js/lit':
+ specifier: workspace:*
+ version: link:../../packages/frameworks/lit
+ '@zag-js/live-region':
+ specifier: workspace:*
+ version: link:../../packages/utilities/live-region
+ '@zag-js/menu':
+ specifier: workspace:*
+ version: link:../../packages/machines/menu
+ '@zag-js/navigation-menu':
+ specifier: workspace:*
+ version: link:../../packages/machines/navigation-menu
+ '@zag-js/number-input':
+ specifier: workspace:*
+ version: link:../../packages/machines/number-input
+ '@zag-js/pagination':
+ specifier: workspace:*
+ version: link:../../packages/machines/pagination
+ '@zag-js/password-input':
+ specifier: workspace:*
+ version: link:../../packages/machines/password-input
+ '@zag-js/pin-input':
+ specifier: workspace:*
+ version: link:../../packages/machines/pin-input
+ '@zag-js/popover':
+ specifier: workspace:*
+ version: link:../../packages/machines/popover
+ '@zag-js/popper':
+ specifier: workspace:*
+ version: link:../../packages/utilities/popper
+ '@zag-js/presence':
+ specifier: workspace:*
+ version: link:../../packages/machines/presence
+ '@zag-js/progress':
+ specifier: workspace:*
+ version: link:../../packages/machines/progress
+ '@zag-js/qr-code':
+ specifier: workspace:*
+ version: link:../../packages/machines/qr-code
+ '@zag-js/radio-group':
+ specifier: workspace:*
+ version: link:../../packages/machines/radio-group
+ '@zag-js/rating-group':
+ specifier: workspace:*
+ version: link:../../packages/machines/rating-group
+ '@zag-js/rect-utils':
+ specifier: workspace:*
+ version: link:../../packages/utilities/rect
+ '@zag-js/remove-scroll':
+ specifier: workspace:*
+ version: link:../../packages/utilities/remove-scroll
+ '@zag-js/scroll-snap':
+ specifier: workspace:*
+ version: link:../../packages/utilities/scroll-snap
+ '@zag-js/select':
+ specifier: workspace:*
+ version: link:../../packages/machines/select
+ '@zag-js/shared':
+ specifier: workspace:*
+ version: link:../../shared
+ '@zag-js/signature-pad':
+ specifier: workspace:*
+ version: link:../../packages/machines/signature-pad
+ '@zag-js/slider':
+ specifier: workspace:*
+ version: link:../../packages/machines/slider
+ '@zag-js/splitter':
+ specifier: workspace:*
+ version: link:../../packages/machines/splitter
+ '@zag-js/steps':
+ specifier: workspace:*
+ version: link:../../packages/machines/steps
+ '@zag-js/store':
+ specifier: workspace:*
+ version: link:../../packages/store
+ '@zag-js/stringify-state':
+ specifier: workspace:*
+ version: link:../../packages/utilities/stringify-state
+ '@zag-js/switch':
+ specifier: workspace:*
+ version: link:../../packages/machines/switch
+ '@zag-js/tabs':
+ specifier: workspace:*
+ version: link:../../packages/machines/tabs
+ '@zag-js/tags-input':
+ specifier: workspace:*
+ version: link:../../packages/machines/tags-input
+ '@zag-js/timer':
+ specifier: workspace:*
+ version: link:../../packages/machines/timer
+ '@zag-js/toast':
+ specifier: workspace:*
+ version: link:../../packages/machines/toast
+ '@zag-js/toggle':
+ specifier: workspace:*
+ version: link:../../packages/machines/toggle
+ '@zag-js/toggle-group':
+ specifier: workspace:*
+ version: link:../../packages/machines/toggle-group
+ '@zag-js/tooltip':
+ specifier: workspace:*
+ version: link:../../packages/machines/tooltip
+ '@zag-js/tour':
+ specifier: workspace:*
+ version: link:../../packages/machines/tour
+ '@zag-js/tree-view':
+ specifier: workspace:*
+ version: link:../../packages/machines/tree-view
+ '@zag-js/types':
+ specifier: workspace:*
+ version: link:../../packages/types
+ '@zag-js/utils':
+ specifier: workspace:*
+ version: link:../../packages/utilities/core
+ form-serialize:
+ specifier: 0.7.2
+ version: 0.7.2
+ lit:
+ specifier: 3.3.1
+ version: 3.3.1
+ lucide:
+ specifier: 0.542.0
+ version: 0.542.0
+ match-sorter:
+ specifier: 8.1.0
+ version: 8.1.0
+ nanoid:
+ specifier: ^5.1.5
+ version: 5.1.5
+ devDependencies:
+ '@types/form-serialize':
+ specifier: 0.7.4
+ version: 0.7.4
+ typescript:
+ specifier: 5.9.2
+ version: 5.9.2
+ vite:
+ specifier: 7.1.3
+ version: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
+
examples/next-ts:
dependencies:
'@internationalized/date':
@@ -1750,6 +2003,46 @@ importers:
specifier: 2.2.0
version: 2.2.0
+ packages/frameworks/lit:
+ dependencies:
+ '@solid-primitives/keyed':
+ specifier: ^1.5.2
+ version: 1.5.2(solid-js@1.9.9)
+ '@zag-js/core':
+ specifier: workspace:*
+ version: link:../../core
+ '@zag-js/store':
+ specifier: workspace:*
+ version: link:../../store
+ '@zag-js/types':
+ specifier: workspace:*
+ version: link:../../types
+ '@zag-js/utils':
+ specifier: workspace:*
+ version: link:../../utilities/core
+ devDependencies:
+ '@solidjs/testing-library':
+ specifier: ^0.8.10
+ version: 0.8.10(@solidjs/router@0.15.3(solid-js@1.9.9))(solid-js@1.9.9)
+ '@testing-library/jest-dom':
+ specifier: ^6.8.0
+ version: 6.8.0
+ '@types/jsdom':
+ specifier: ^21.1.7
+ version: 21.1.7
+ clean-package:
+ specifier: 2.2.0
+ version: 2.2.0
+ jsdom:
+ specifier: ^26.1.0
+ version: 26.1.0
+ lit:
+ specifier: ^3.0.0
+ version: 3.3.1
+ solid-js:
+ specifier: 1.9.9
+ version: 1.9.9
+
packages/frameworks/preact:
dependencies:
'@zag-js/core':
@@ -4379,7 +4672,7 @@ packages:
'@emotion/use-insertion-effect-with-fallbacks@1.2.0':
resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
peerDependencies:
- react: '>=16.8.0'
+ react: ^18.3.1
'@emotion/utils@1.4.2':
resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
@@ -5115,6 +5408,12 @@ packages:
'@kwsites/promise-deferred@1.1.1':
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
+ '@lit-labs/ssr-dom-shim@1.4.0':
+ resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==}
+
+ '@lit/reactive-element@2.1.1':
+ resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==}
+
'@manypkg/find-root@1.1.0':
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
@@ -5363,6 +5662,11 @@ packages:
'@octokit/types@14.1.0':
resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==}
+ '@open-wc/lit-helpers@0.7.0':
+ resolution: {integrity: sha512-4NBlx5ve0EvZplCRJbESm0MdMbRCw16alP2y76KAAAwzmFFXXrUj5hFwhw55+sSg5qaRRx6sY+s7usKgnNo3TQ==}
+ peerDependencies:
+ lit: ^2.0.0 || ^3.0.0
+
'@opentelemetry/api-logs@0.39.1':
resolution: {integrity: sha512-9BJ8lMcOzEN0lu+Qji801y707oFO4xT3db6cosPvl+k7ItUHKN5ofWqtSbM9gbt1H4JJ/4/2TVrqI9Rq7hNv6Q==}
engines: {node: '>=14'}
@@ -6622,6 +6926,9 @@ packages:
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
'@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@@ -9867,6 +10174,15 @@ packages:
resolution: {integrity: sha512-VVd7cS6W+vLJu2wmq4QmfVj14Iep7cz4r/OWNk36Aq5ZOY7G8/BfCrQFexcwB1OIxB3yERiePfE/REBjEFulag==}
engines: {node: '>=20.0.0'}
+ lit-element@4.2.1:
+ resolution: {integrity: sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==}
+
+ lit-html@3.3.1:
+ resolution: {integrity: sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==}
+
+ lit@3.3.1:
+ resolution: {integrity: sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==}
+
load-json-file@4.0.0:
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
engines: {node: '>=4'}
@@ -10031,6 +10347,9 @@ packages:
peerDependencies:
vue: '>=3.0.1'
+ lucide@0.542.0:
+ resolution: {integrity: sha512-+EtDSHjqg/nONgCfnjHCNd84OzbDjxR8ShnOf+oImlU+A8gqlptZ6pGrMCnhEDw8pVNQv3zu/L0eDvMzcc7nWA==}
+
luxon@3.7.1:
resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==}
engines: {node: '>=12'}
@@ -14736,6 +15055,12 @@ snapshots:
'@kwsites/promise-deferred@1.1.1': {}
+ '@lit-labs/ssr-dom-shim@1.4.0': {}
+
+ '@lit/reactive-element@2.1.1':
+ dependencies:
+ '@lit-labs/ssr-dom-shim': 1.4.0
+
'@manypkg/find-root@1.1.0':
dependencies:
'@babel/runtime': 7.28.3
@@ -15274,6 +15599,10 @@ snapshots:
dependencies:
'@octokit/openapi-types': 25.1.0
+ '@open-wc/lit-helpers@0.7.0(lit@3.3.1)':
+ dependencies:
+ lit: 3.3.1
+
'@opentelemetry/api-logs@0.39.1':
dependencies:
'@opentelemetry/api': 1.9.0
@@ -16417,6 +16746,8 @@ snapshots:
'@types/triple-beam@1.3.5': {}
+ '@types/trusted-types@2.0.7': {}
+
'@types/unist@2.0.11': {}
'@types/unist@3.0.3': {}
@@ -16428,10 +16759,10 @@ snapshots:
'@types/node': 24.3.0
optional: true
- '@typescript-eslint/eslint-plugin@8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
+ '@typescript-eslint/eslint-plugin@8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/parser': 8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.41.0
'@typescript-eslint/type-utils': 8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
'@typescript-eslint/utils': 8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
@@ -18563,12 +18894,12 @@ snapshots:
dependencies:
'@next/eslint-plugin-next': 15.5.2
'@rushstack/eslint-patch': 1.12.0
- '@typescript-eslint/eslint-plugin': 8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/eslint-plugin': 8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
'@typescript-eslint/parser': 8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
eslint: 9.33.0(jiti@2.5.1)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.5.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1))
eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1))
eslint-plugin-react-hooks: 5.2.0(eslint@9.33.0(jiti@2.5.1))
@@ -18626,44 +18957,44 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1)):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.1
- eslint: 9.33.0(jiti@2.5.1)
+ eslint: 9.34.0(jiti@2.5.1)
get-tsconfig: 4.10.1
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.14
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1))
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1)):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.5.1)):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.1
- eslint: 9.34.0(jiti@2.5.1)
+ eslint: 9.33.0(jiti@2.5.1)
get-tsconfig: 4.10.1
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.14
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
- '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/parser': 8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
eslint: 9.33.0(jiti@2.5.1)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.5.1))
transitivePeerDependencies:
- supports-color
@@ -18678,16 +19009,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.34.0(jiti@2.5.1)):
- dependencies:
- debug: 3.2.7
- optionalDependencies:
- '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
- eslint: 9.34.0(jiti@2.5.1)
- eslint-import-resolver-node: 0.3.9
- transitivePeerDependencies:
- - supports-color
-
eslint-plugin-compat@6.0.2(eslint@9.34.0(jiti@2.5.1)):
dependencies:
'@mdn/browser-compat-data': 5.7.6
@@ -18700,7 +19021,7 @@ snapshots:
lodash.memoize: 4.1.2
semver: 7.7.2
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -18711,7 +19032,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.33.0(jiti@2.5.1)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -18723,7 +19044,7 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
+ '@typescript-eslint/parser': 8.41.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -18758,35 +19079,6 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)):
- dependencies:
- '@rtsao/scc': 1.1.0
- array-includes: 3.1.9
- array.prototype.findlastindex: 1.2.6
- array.prototype.flat: 1.3.3
- array.prototype.flatmap: 1.3.3
- debug: 3.2.7
- doctrine: 2.1.0
- eslint: 9.34.0(jiti@2.5.1)
- eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.34.0(jiti@2.5.1))
- hasown: 2.0.2
- is-core-module: 2.16.1
- is-glob: 4.0.3
- minimatch: 3.1.2
- object.fromentries: 2.0.8
- object.groupby: 1.0.3
- object.values: 1.2.1
- semver: 6.3.1
- string.prototype.trimend: 1.0.9
- tsconfig-paths: 3.15.0
- optionalDependencies:
- '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)
- transitivePeerDependencies:
- - eslint-import-resolver-typescript
- - eslint-import-resolver-webpack
- - supports-color
-
eslint-plugin-jsx-a11y@6.10.2(eslint@9.33.0(jiti@2.5.1)):
dependencies:
aria-query: 5.3.2
@@ -20486,6 +20778,22 @@ snapshots:
rfdc: 1.4.1
wrap-ansi: 9.0.0
+ lit-element@4.2.1:
+ dependencies:
+ '@lit-labs/ssr-dom-shim': 1.4.0
+ '@lit/reactive-element': 2.1.1
+ lit-html: 3.3.1
+
+ lit-html@3.3.1:
+ dependencies:
+ '@types/trusted-types': 2.0.7
+
+ lit@3.3.1:
+ dependencies:
+ '@lit/reactive-element': 2.1.1
+ lit-element: 4.2.1
+ lit-html: 3.3.1
+
load-json-file@4.0.0:
dependencies:
graceful-fs: 4.2.11
@@ -20640,6 +20948,8 @@ snapshots:
dependencies:
vue: 3.5.20(typescript@5.9.2)
+ lucide@0.542.0: {}
+
luxon@3.7.1: {}
lz-string@1.5.0: {}
diff --git a/shared/src/css/layout.css b/shared/src/css/layout.css
index df9e5dee71..5c2d521f30 100644
--- a/shared/src/css/layout.css
+++ b/shared/src/css/layout.css
@@ -86,7 +86,8 @@ body {
overflow: hidden;
}
-.page main {
+.page main,
+:host main {
flex: auto;
display: flex;
gap: 10px;
@@ -192,7 +193,7 @@ a[aria-current="page"] {
max-height: 100%;
}
-.toolbar .viz {
+.viz {
font-size: 13px;
--viz-font: SF Mono, Menlo, monospace;
padding-left: 14px;
@@ -201,13 +202,13 @@ a[aria-current="page"] {
border-bottom: solid 1px #d7e0da;
}
-.toolbar .viz summary {
+.viz summary {
margin-bottom: 24;
font-family: var(--viz-font);
font-weight: bold;
}
-.toolbar .viz summary + div > * {
+.viz summary + div > * {
font-family: var(--viz-font);
}