Code Monkey home page Code Monkey logo

zag's Introduction

Zag.js hero image

Zag

Finite state machines for accessible JavaScript components

  • Write once, use everywhere πŸ¦„: The component interactions are modelled in a framework agnostic way. We provide adapters for JS frameworks like React, Solid, or Vue.
  • Focus on accessibility ♿️: Zag is built with accessibility in mind. We handle many details related to keyboard interactions, focus management, aria roles and attributes.
  • Headless ✨: The machine APIs are completely unstyled and gives you the control to use any styling solution you prefer.
  • Powered by state machines 🌳: Zag is built on top of the latest ideas in Statecharts. We don't follow the SCXML specifications, but we've created an API that we think will help us build more complex components fast.

Documentation

To see the documentation, visit zagjs.com/

Releases

For changelog, Check CHANGELOG.md


Problem

With the rise of design systems and component-driven development, there's an endless re-implementation of common component patterns (Tabs, Menu, Modal, etc.) in multiple frameworks.

Most of these implementations seem to be fairly similar in spirit, the differences being around the reactivity and effects systems for the framework (e.g. useState, useEffect in React.js). Framework specific solutions tend to grow in complexity over time and often become hard to understand, debug, improve or test.

Solution

Zag is a JavaScript API that implements common component patterns using the state machine methodology.

Installation

npm i --save @zag-js/{component}

# or

yarn add @zag-js/{component}

{component} represents any component machine like dialog (@zag-js/dialog), tooltip (@zag-js/tooltip) , etc.

For framework specific solutions, we provide simple wrappers to help you consume the component state machines.

  • βš›οΈ @zag-js/react - React hooks for consuming machines in React applications
  • πŸ’š @zag-js/vue - Vue composition for consuming machines in Vue applications
  • 🎷 @zag-js/solid - Solid.js utilities for consuming machines in Solid.js applications

Usage

import { normalizeProps, useMachine } from "@zag-js/react"
import * as toggle from "@zag-js/toggle-group"
import { useId } from "react"

export function ToggleGroup() {
  const [state, send] = useMachine(toggle.machine({ id: useId() }))
  const api = toggle.connect(state, send, normalizeProps)

  return (
    <div {...api.rootProps}>
      <button {...api.getItemProps({ value: "bold" })}>B</button>
      <button {...api.getItemProps({ value: "italic" })}>I</button>
      <button {...api.getItemProps({ value: "underline" })}>U</button>
    </div>
  )
}

Guiding Principles

  • All component machines and tests are modelled according to the WAI-ARIA authoring practices
  • Write end-to-end tests for every component based on the WAI-ARIA spec. Regardless of the framework, users expect component patterns to work the same way!
  • All machines should be light-weight, simple, and easy to understand. Avoid using complex machine concepts like spawn, nested states, etc.

Fun Facts

Zag means to take a sharp change in direction. This clearly describes our approach of using state machines to power the logic behind UI components.

Teasers

  • When you see someone using classic react, vue or solid to build an interactive UI component that exists in Zag, tell them to "zag it!" ⚑️

  • Anyone using Zag will be called a "zagger" πŸ’₯

  • The feeling you get when you use Zag will be called "zagadat!" πŸš€

  • The Zag community will be called "zag nation" πŸ”₯


Commands

Build commands

Our build is managed with esbuild and turborepo to provide fast, concurrent builds across the packages.

  • build : Build the CJS, ESM and DTS files. This is the actual production build that we run in the CI.

Examples

Since zag is framework agnostic, we need a way to test it within a framework. The examples/ directory includes starter projects for the frameworks we support.

  • start-react : Starts the Next.js TypeScript project
  • start-vue : Starts the Vue 3 TypeScript project
  • start-solid : Starts the Solid TypeScript project

E2E Tests

We've setup end-to-end tests for every machine we built. We use Playwright for testing and we ensure that the component works the same way regardless of the framework.

  • e2e-react : Starts the E2E tests for the React project
  • e2e-vue : Starts the E2E tests for the Vue project
  • e2e-solid : Starts the E2E tests for the Solid project

Contributing new machines/features

  • generate-machine : Generates a new machine package in the packages/ directory. It sets up the required files and structure for new machine.
  • generate-util : Generates a new utility package in the packages/utilities directory.

Other commands

  • test : Run the tests for all packages
  • lint : Lint all packages

Website

  • start-website: Starts the website

Inspirations


Contributions

Looking to contribute? Look for the Good First Issue label.

πŸ› Bugs

Please file an issue for bugs, missing documentation, or unexpected behavior.

πŸ’‘ Feature Requests

Please file an issue to suggest new features. Vote on feature requests by adding a πŸ‘. This helps maintainers prioritize what to work on.


License

MIT Β© Segun Adebayo

zag's People

Contributors

alexluong avatar anubra266 avatar codebender828 avatar cschroeter avatar csenio avatar dangvanthanh avatar elcharitas avatar erm1116 avatar github-actions[bot] avatar heavenosk avatar iamdin avatar inetjojo avatar izznatsir avatar juliomuhlbauer avatar junghyeonsu avatar koba04 avatar malangcat avatar martian2lee avatar omikorin avatar pagebakers avatar pawelblaszczyk5 avatar renovate-bot avatar renovate[bot] avatar segunadebayo avatar shyrro avatar srflp avatar timkolberger avatar tylerapfledderer avatar visualjerk avatar wramzo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zag's Issues

[number-input] Provide final manipulated value to user-supplied event handlers

πŸš€ Feature request

I'd like to be able to access the final value (clamped, rounded, formatted, etc.) from @zag-js/number-input in a blur event handler (in React, if it matters).

🧱 Problem Statement / Justification

Currently, if I provide an onBlur function through the mergeProps helper, it behaves just like if I were to skip mergeProps and attach it directly to the input, which means the value in the event is whatever raw value the input had when the blur event occurred. That is, Zag's processing has not occurred yet. Zag responds to the same blur event and manipulates the value accordingly as it transitions from the focused state to the idle state, and then a final change event occurs (after the blur event) with the final value from Zag.

βœ… Proposed solution or API

Maybe Zag can perform its work prior to invoking handlers provided via mergeProps? This would break anyone currently depending on getting the raw/pre-processed value, though. Alternatively, perhaps Zag could expose a duplicate handler for every event that currently causes it to do its work (e.g., onZagBlur or onBlurFinal) which is fired after the work is done? Not sure of the best solution.

↩️ Alternatives

I've considered trying to track the overall state of things myself in the consuming code and fire what I need accordingly, but it's a bit tricky and feels like it should be doable with Zag natively.

πŸ“ Additional Information

Demo: https://codesandbox.io/s/upbeat-cdn-j8pyry?file=/src/App.tsx

Use-case: fire some event (e.g., a fetch) on blur (but not on change) that needs the final/formatted value

@zag-js/pin-input 2 characters in a box

πŸ› Bug report

Hi! I was trying out @zag-js/number-input and was able to input 2 characters at once inside a PinInput box.

zag-pin-input-fast-keys

πŸ’₯ Steps to reproduce

  1. Render a PinInput
  2. Focus on the first box.
  3. Smash two different keys at the same time
  4. See error

πŸ’» Link to reproduction

Tested locally with React and a couple keyboards ^^ but also in the docs example: https://zagjs.com/components/react/pin-input

🧐 Expected behavior

From the docs I understand that 1 digit or letter is allowed per box:

The input fields allow one character at a time.

🧭 Possible Solution

I don't know.

🌍 System information

Software Version(s)
@zag-js/pin-input 0.1.3
Browser Chrome
Operating System macOS & Windows

πŸ“ Additional information

I tried the same in chakra-ui docs but could not reproduce it.

Weird IOS webview bug with the Accordion Component

πŸ› Bug report

So this is difficult to reproduce and debug, sorry about that. I love what you are building here so felt it is important that I let you know what we discovered when our application went to production using your accordion component. We found that in some situations the accordion won't open.

πŸ’₯ Steps to reproduce

  1. Using an IOS device
  2. Open a link to your website from either Instagram, Gmail, FB or LinkeIn
  3. The link will open in a webview
  4. Try to click the button to open the accordion
  5. Nothing happens.

πŸ’» Link to reproduction

CodeSandbox reproduction: https://your-codesandbox-link-goes-here.io

🧐 Expected behavior

The accordion should open when the button is 'touched'.

🧭 Possible Solution

I don't know what could be causing this. FYI the in app webview for Slack and Twitter work fine πŸ™„

🌍 System information

Software Version(s)
Zag Version beta
Browser Instagram webview FB webview
Operating System IOS

πŸ“ Additional information

Dependency Dashboard

This issue provides visibility into Renovate updates and their statuses. Learn more

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): lock file maintenance

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/pr.yml
  • actions/checkout v3
  • actions/setup-node v3
.github/workflows/release.yml
  • actions/checkout v3
  • actions/setup-node v3
  • changesets/action v1
  • actions/github-script v6
.github/workflows/visualize.yml
  • actions/checkout v3
  • actions/setup-node v3
  • thollander/actions-comment-pull-request v1
npm
package.json
  • @axe-core/playwright 4.4.3
  • @changesets/changelog-github 0.4.5
  • @changesets/cli 2.23.0
  • @commitlint/cli 17.0.2
  • @commitlint/config-conventional 17.0.2
  • @emotion/css 11.9.0
  • @lerna/package-graph 5.1.4
  • @manypkg/cli 0.19.1
  • @playwright/test 1.22.2
  • @swc-node/jest 1.5.2
  • @testing-library/dom 8.13.0
  • @testing-library/jest-dom 5.16.4
  • @types/form-serialize 0.7.2
  • @types/jest 27.5.2
  • @typescript-eslint/eslint-plugin 5.28.0
  • @typescript-eslint/parser 5.28.0
  • axe-core 4.4.2
  • chalk 4.1.2
  • commitlint 17.0.2
  • esbuild 0.14.44
  • esbuild-runner 2.2.1
  • eslint 8.17.0
  • eslint-config-prettier 8.5.0
  • eslint-plugin-import 2.26.0
  • eslint-plugin-jest 26.5.3
  • eslint-plugin-jsx-a11y 6.5.1
  • eslint-plugin-prettier 4.0.0
  • eslint-plugin-testing-library 5.5.1
  • form-serialize 0.7.2
  • husky 8.0.1
  • jest 26.6.3
  • jest-watch-typeahead 0.6.5
  • json-format-highlight 1.0.4
  • lint-staged 11.2.6
  • patch-package 6.4.7
  • playwright 1.22.2
  • plop 3.1.1
  • prettier 2.7.1
  • start-server-and-test 1.14.0
  • turbo 1.2.16
  • typescript 4.7.3
packages/core/package.json
  • @zag-js/utils 0.1.2
  • klona ^2.0.5
  • valtio ^1.6.1
packages/frameworks/react/package.json
  • @zag-js/core 0.1.6
  • valtio ^1.6.1
  • @types/react 18.0.12
  • react 18.2.0
  • react >=16.8.6
packages/frameworks/solid/package.json
  • @zag-js/core 0.1.6
  • hyphenate-style-name ^1.0.4
  • valtio ^1.6.1
  • solid-js 1.4.4
  • solid-js >=1.1.3
packages/frameworks/svelte/package.json
  • @zag-js/core 0.1.6
  • svelte 3.48.0
  • svelte ^3.38.2
packages/frameworks/vue/package.json
  • @zag-js/core 0.1.6
  • valtio ^1.6.1
  • vue 3.2.22
  • vue >=3.0.0
packages/machines/accordion/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/checkbox/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/combobox/package.json
  • @zag-js/aria-hidden 0.1.0
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/popper 0.1.5
  • @zag-js/types 0.1.2
  • scroll-into-view-if-needed ^2.2.28
packages/machines/dialog/package.json
  • @zag-js/aria-hidden 0.1.0
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/remove-scroll 0.1.0
  • @zag-js/types 0.1.2
  • focus-trap ^6.7.1
  • scroll-into-view-if-needed ^2.2.28
packages/machines/editable/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
packages/machines/menu/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/popper 0.1.5
  • @zag-js/rect-utils 0.1.2
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/number-input/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/number-utils 0.1.2
  • @zag-js/rect-utils 0.1.2
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/pin-input/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/popover/package.json
  • @zag-js/aria-hidden 0.1.0
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/popper 0.1.5
  • @zag-js/remove-scroll 0.1.0
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
  • focus-trap ^6.7.1
packages/machines/range-slider/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/number-utils 0.1.2
  • @zag-js/rect-utils 0.1.2
  • @zag-js/slider 0.1.7
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/rating/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/rect-utils 0.1.2
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/slider/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/number-utils 0.1.2
  • @zag-js/rect-utils 0.1.2
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/splitter/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/number-utils 0.1.2
  • @zag-js/rect-utils 0.1.2
  • @zag-js/types 0.1.2
packages/machines/tabs/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/tags-input/package.json
  • @zag-js/auto-resize 0.0.0
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
packages/machines/toast/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
  • @zag-js/utils 0.1.2
packages/machines/toggle/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/types 0.1.2
packages/machines/tooltip/package.json
  • @zag-js/core 0.1.6
  • @zag-js/dom-utils 0.1.4
  • @zag-js/popper 0.1.5
  • @zag-js/types 0.1.2
packages/types/package.json
packages/utilities/aria-hidden/package.json
packages/utilities/auto-resize/package.json
  • @zag-js/dom-utils 0.1.4
packages/utilities/core/package.json
packages/utilities/dismissable/package.json
  • @zag-js/dom-utils 0.1.4
  • @zag-js/interact-outside 0.0.0
  • valtio ^1.6.1
packages/utilities/dom/package.json
  • @types/react ^18.0.0
  • @zag-js/utils 0.1.2
packages/utilities/focus-visible/package.json
packages/utilities/interact-outside/package.json
  • @zag-js/dom-utils 0.1.4
packages/utilities/number/package.json
packages/utilities/popper/package.json
  • @floating-ui/dom ^0.5.0
  • @zag-js/dom-utils 0.1.4
  • @zag-js/utils 0.1.2
packages/utilities/rect/package.json
  • @zag-js/utils 0.1.2
packages/utilities/remove-scroll/package.json
  • @zag-js/utils 0.1.2
scripts/package.json
  • @lerna/package-graph 5.1.4
  • @swc-node/register 1.5.1
  • @types/signale 1.4.4
  • commander 8.3.0
  • gzip-size 6.0.0
  • pretty-bytes 5.6.0
  • relative 3.0.2
  • signale 1.4.0
shared/package.json

  • Check this box to trigger a request for Renovate to run again on this repository

Popper arrow rotation

πŸš€ Feature request

Rotation of the arrow based on placement options

🧱 Problem Statement / Justification

When using the popper with the arrow, I often want borders on the the sides of the arrow which face the trigger. But then I have to set up data-attributes that take in the placement and add the borders appropriately (top and left borders when bottom positioned, bottom and right borders when top positioned).

βœ… Proposed solution or API

It would be much easier to change the rotation of the arrow to 45, 135, 225, 315 if when the arrow is positioned bottom, left, top and right positioned resp. Then I can just add borders to the top and left of my arrow and not need to worry about the placement in my code.

↩️ Alternatives

Adding my own data attributes

Support Xstate visualizer

In an ideal world, I'd really love for Zag to work seamlessly with XState. I'm more than happy to switch to @xstate/fsm when it has the key features we need.

For now, we'll focus on supporting XState Visualizer. Here are the tasks that come to mind:

  • Write a code mod or script to convert the machine's configuration to XState (we can skip the machine options)
  • Write a script to convert the machine configuration to a PNG/SVG format
  • Setup a CI action on PRs that tracks changes in the .machine files and shows the before/after logic changes

remove `.clone` and write machines using function

Within useMachine, we're doing this

machine.clone()

That clone method messes with the internal and caused items to be undefined or produces inconsistent instances. Let's get rid of clone all together and stick with functions

Combobox Tasks

  • support screen reader announcements (with and without section)
  • user can provide objects as values
  • user can decide what gets rendered in the input value
  • user can control when the popup opens
  • support for error message and help text
  • Move event handling to the combobox popup instead of option items (for performance)
  • Add openOnFocus prop (If it's not readonly)
  • Add closeOnSelect prop

Allow special characters on Pin Input

πŸš€ Feature request

When using Pin Input component in numeric, I would like to allow user to paste specials characters (like "-")

🧱 Problem Statement / Justification

I've received the following email :

Capture d’écran 2022-06-30 aΜ€ 14 10 22

When i copy paste 063-569 my code input component doesn't work because of the - character.

βœ… Proposed solution or API

// pin-input.types.ts

type PublicContext = DirectionProperty & {
   ...
   whiteList: string
}
// pin-input.machine.ts

export function machine(ctx: UserDefinedContext = {}) {
  return createMachine<MachineContext, MachineState>({
    ...,
    context: {
      whiteList: ''
     }
  })
})

function isValidType(value: string, type: MachineContext["type"], whiteList: MachineContext["whiteList"]) {
  if (!type) return true
  const filteredValue = [...value].filter(char => !whiteList.includes(char)).join()
  return !!REGEX[type]?.test(filteredValue)
}

and use it like this :

useMachine(pinInput.machine({ whiteList: '-' }))

↩️ Alternatives

There is no alternative, because alphanumeric doesn't allow special characters.

Run e2e tests in the CI

We use Cypress to test the components across the frameworks we support.

It'll be great to have cypress tests run in parallel in a Github Action for every PR.

Shadow root compatibilty

πŸš€ Feature request

Able to still position elements correctly when the component is inside a shadow dom.

🧱 Problem Statement / Justification

Document referenced in useSetup Is always the outer html document which bypasses the shadow root when the component is inside there. The base doc should be the shadow root for the positioning to work correctly.

βœ… Proposed solution or API

Change all occurrences of ownerDocument to getRootNode(). This will return the regular html doc when not inside a shadow dom and the shadow root when the component is there. This makes all the positioning for tooltips/popovers work.

πŸ“ Additional Information

https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode

[number-input] precision 0 meaning/behavior

πŸ› Bug report

Setting precision to 0 seems to disable rounding (that is, leave the value alone), as opposed to rounding the values to whole numbers (integers). Is this intentional behavior or a bug? If intentional, is there a way to round to whole numbers?

πŸ’₯ Steps to reproduce

  1. Go to https://zagjs.com/components/react/number-input
  2. Change precision to "0" (if not already)
  3. Type "12.34" in the "Enter number" input on the left
  4. Tab out
  5. Observe the value remains 12.34 as opposed to 12

πŸ’» Link to reproduction

N/A, see Zag's own Number Input page linked above.

🧐 Expected behavior

I would expect it to round to the nearest whole number when precision is 0 (perhaps some other value like undefined would disable rounding all together?).

🧭 Possible Solution

Not sure.

🌍 System information

Software Version(s)
Chakra UI Core N/A
Browser Chrome, Firefox
Operating System Windows 10, macOS

πŸ“ Additional information

Thank you.

[vue] Support for Vue 2

πŸš€ Feature request

Please describe your request in one or two sentences.

Despite Vue 3 now being the default version, there are still many users who have to stay on Vue 2. (Read more)

Currently, Zag only supports Vue 3. Since Zag has a goal of providing framework-agnostic logic, I wish be able to reuse the same component logic in Vue 2 using Zag.

βœ… Proposed solution or API

Please provide code snippets, gists, or links to the ideal design or API

  • Using vue-demi to write Universal Vue Libraries for Vue 2 & 3
  • Writing two separate wrapper for vue2 (adding @zag-js/vue2)
    • <=2.6 : exports from vue + @vue/composition-api with plugin auto installing.
    • 2.7: exports from vue (Composition API is built-in in Vue 2.7).

split each component into packages

  • Create the utils package with multiple entry points
  • Create each component package
  • Set up a bundle size measurement tool
  • Bundle each component machine using ESBuild

Missing attributes in Slider parts

  • Here the styles claim data-part="root" and data-part="range" have a data-focus attribute.

But they don’t here

We should add this :

"data-focus": dataAttr(isFocused)

to rootProps and rangeProps in that file. Make it follow alphabetical order with the properties in those objects

  • Here the styles also claim that data-part="root" has a data-invalid attribute.

But it doesn't in that same file

We should add this:

"data-invalid": dataAttr(isInvalid)

to rootProps in that file. Don't forget alphabetic order.

[menu] menu emits double click events when closeOnSelect: false

πŸ› Bug report

Menu emits double click events when closeOnSelect: false, because of a technical issue, due to the following code snippet:

        onPointerUp(event) {
          const evt = getNativeEvent(event)
          if (!evt.isPrimary || disabled) return
          event.currentTarget.click()
        },

I guess this is just an oversight. There are multiple problems in this approach:

  1. If the goal is to prevent non-left clicks, isPrimary doesn't do that according to MDN. Can use evt.button to decide if it's a left click or not.
  2. This event handler does nothing to prevent the other onClick handler from being triggered regardless of isPrimary. I'd have expected stopPropogation or preventDefault or return false or such.
  3. Neither stopPropogation nor preventDefault nor return false will prevent the other onClick handler from running. pointerup event apparently cannot cancel the upcoming click event.
  4. Due to event.currentTarget.click(), click events happen twice, so onClick handlers run twice. For checkboxes, that means they are toggled twice.
  5. This bug is only observable when closeOnSelect: false because when closeOnSelect: true, the menu is closed on the first click so it prevents the second click from happening.
  6. It seems click event's button prop doesn't actually reflect the button that is pressed. It's always 0 even if it's a different button.

My proposal(s)

Option 1. Drop onClick handlers and rely on onPointerUp only, if it's so important to only respect left clicks.

Option 2. Drop onPointerUp handler and rely only on onClick handlers, if it's not very important to handle middle/right clicks.

Option 3. Try hard to prevent non-left clicks. Integrate onPointerUp into the state machine, in onPointerUp event check whether if evt.button === 0 and emit "LEFT_CLICK_UP" and in onClick, emit CLICK and do the proper state transition only if LEFT_CLICK_UP is followed up by a CLICK, but this still sounds naive. There are myriad of edge cases like user holding left button pressed and release it on another element etc. Why bother? Let people click with whatever button they want.

I'd pick "Option 2" for now so that the bug that completely breaks checkboxes with closeOnSelect: false is fixed, and create a followup github issue to prevent non-left clicks (which I think will not be trivial to achieve) in future.

πŸ’₯ Steps to reproduce

  1. Add closeOnSelect: false to menu-options.ts in next-ts example project.
  2. Try clicking checkboxes.
  3. (BONUS) Add debugger in onPointerUp and in both onClick handlers in menu.connect.ts.

Expected behavior

Checkboxes toggle.

Actual behavior

Checkboxes don't toggle.

Codesandbox

I suggest you see with your own eyes, with a debugger.

But here you go: https://codesandbox.io/s/gallant-ellis-cq0wyr?file=/src/App2.js

🌍 System information

Software Version(s)
Zag Version 0.1.11
Browser All
Operating System All

[form-utils] Form reset listeners not implemented in some machines

  • Move trackFormReset and trackFieldsetDisabled to @zag-js/form-utils

Machines without form reset listeners

  • Rating

How;
In Activities

 trackFormReset(ctx) {
      return trackFormReset(dom.getInputEl(ctx), () => {
           ctx.value = ctx.initialValue
      })
},

// Add to activities
      activities: ["trackFormReset"],

chore: differentiate between combobox types

Most implementations of the ARIA spec assume a Listbox popup Combobox when there are 2-3 types of Combobox (with different interaction patterns)

We need to have:

  • Listbox Combobox @ui-machines/combobox
  • Grid Combobox @ui-machines/grid-combobox

[context types] separate user options types from internal options

type AccordionOptions = { ... }
type AccordionPrivateContext = { ... }

type AccordionContext = AccordionOptions & AccordionPrivateContext
  • We'll never export the AccordionContext to the user. Only the AccordionOptions
  • Computed context will be part of our internal/private context

Slider thumb position is broken inside relatively positioned elements

πŸ› Bug report

If the slider is within a containing block that is not the root dom node, clicking on the slider moves the thumb to the wrong place. It appears to be erroneously adding the slider root's X offset from the root dom node to whatever X position the thumb should be at.

For example, for the following set up in React:

<div style={{ padding: '100px' }}>
  <div style={{ position: 'relative' }}>
    <Slider /> /* as from the zag js slider docs */
  </div>
</div>

When you click on the slider, the thumb moves to 100px right of where you clicked (it appears that way at least).

This may be affecting other Zag components like RangeSlider but I haven't checked.

πŸ’₯ Steps to reproduce

  1. Create a containing block that is offset from the root dom node. (as seen above)
  2. Add slider within the containing block
  3. Click on the slider. The thumb is in the wrong place.

πŸ’» Link to reproduction

CodeSandbox reproduction: https://codesandbox.io/s/adoring-snowflake-ifyk13?file=/src/App.tsx

🧐 Expected behavior

Thumb is where you click.

🧭 Possible Solution

I haven't looked at the source, but it seems like the thumb position is being calculated based on the root dom node rather than the slider's root element. Any positioning logic for the slider should be contained within the slider root element. APIs like ResizeObserver can help with this: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver

🌍 System information

Software Version(s)
Zag Version react 1.9.0, slider 1.8.0
Browser Chrome 102
Operating System MacOS Monterey

[range-slider] Add support for dynamic thumbs

We currently assume that we know the slider thumbs at the initial render. We need a way to update the machine state when new slider thumbs are added to the DOM. This usually means that the values array will also be updated, so we can react to that to update slider thumb positions.

Update Solid-js examples

We need to update the pages on examples/solid-ts to match the pages in examples/next-ts

I added an example for the accordion component, we can use that as a guide to creating the other components. Toast and Tooltip might be tricky for now. I'll handle those myself.

Solidjs is a new UI framework we’re looking to support in the machines, it’s very similar to react but better.

Fix `normalize-prop` types

At the moment, the return type of normalizeProps is set to Record<string, any>

We need to update the normalizeProps return type for each framework and update all components connect function to use a generic.

React
incoming props (React.XElementAttributes) --> connect --> outgoing props (React.XElementAttributes)

Vue
incoming props (React.XElementAttributes) --> connect --> outgoing props (ElementAttrs<X>)

Solid
incoming props (React.XElementAttributes) --> connect --> outgoing props (JSX.HTMLAttributes[X])

Include example builds in PR workflow

It'll be great to have a deploy preview of the example projects in every PR we make. It'll help us catch and manually test regressions when the Playwright tests fail.

  • Create Vercel setup for examples/*
  • Setup matrix for playwright Github actions
  • Get all tests passing

[number-input] initial values of subsequent instances get replaced with the first

πŸ› Bug report

When you render multiple instances of the Number Input, the initial value of every instance following the first instance quickly gets replaced with the initial value of the first instance (after briefly being correctly set to their own initial values).

πŸ’₯ Steps to reproduce

  1. Render multiple number inputs with different initial values
  2. Observe they all start out correct but quickly change to the initial value of the first

πŸ’» Link to reproduction

CodeSandbox reproduction: https://codesandbox.io/s/stupefied-lake-effyhv?file=/src/App.js

🧐 Expected behavior

I would expect all instances to be completely independent (and in this case, respect their individual initial values).

🧭 Possible Solution

No clue.

🌍 System information

Software Version(s)
Chakra UI Core N/A
Browser Chrome, Firefox
Operating System Windows 10

πŸ“ Additional information

Thank you.

Add event types

For each machine, we have types for context and state. We need to add the types for the events as well.

In the process, we can consider updating the event type names to be more intuitive.

[New Component] Media player

πŸš€ Feature request

Media player logic.

🧱 Problem Statement / Justification

I wanted a headless video player for years. Thought about using XState, but too heavy like you noticed. Using Web Components as an abstraction is too heavy. Also want same API to control animation libs.

Maybe this + Mitosis for 'the one player to rule them all'?

βœ… Proposed solution or API

Start: mattpocock/xstate-catalogue#47
I can build something via XState visualizer, might take some time.

↩️ Alternatives

https://github.com/btwld/vxdk

πŸ“ Additional Information

https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
https://mifi.github.io/react-lottie-player/
https://www.remotion.dev/docs/player
https://github.com/vidstack/vidstack

Why not Svelte?

πŸš€ Feature request

Vue, React, Solid, why not Svelte?

[number-input] precision option ignored?

πŸ› Bug report

The precision option of Number Input doesn't always seem to be taken into account (or I've misunderstood it). For example, setting precision to 2 and typing "12.345678" results in "12.346" when I would expect "12.35". Setting precision to anything other than 3 still results in the value being rounded to the third decimal place on blur, so maybe 3 is the default and the option is being ignored entirely in that scenario? If you wipe out the value completely and use inc/dec buttons, that does seem to honor precision though.

NB: Happens whenever precision is lower than 3

πŸ’₯ Steps to reproduce

  1. Go to https://zagjs.com/components/react/number-input
  2. Change precision to "2"
  3. Type "12.345678" in the "Enter number" input on the left
  4. Tab out
  5. Observe the value has been rounded to 12.346 instead of 12.35

πŸ’» Link to reproduction

CodeSandbox reproduction: https://codesandbox.io/s/hungry-bush-1fzk87?file=/src/App.js

(Note: had to disable React 18 StrictMode to get it to work at all.)

🧐 Expected behavior

I would expect it to round to the decimal place specified in the precision option.

🧭 Possible Solution

Not sure.

🌍 System information

Software Version(s)
Chakra UI Core N/A
Browser Firefox, Chrome
Operating System Windows 10

πŸ“ Additional information

number-input-precision

Button

πŸš€ Feature request

I posted this as a discussion a few months ago but thought I would instead submit it as a proper feature request. Zag looks like a pretty sweet library and I'm thinking of switching out React-ARIA with Zag on a project. One thing for me that Zag.js is missing (understandably as the library was just released) is a button component that handles accessibility/keyboard navigation like React-ARIA.

🧱 Problem Statement / Justification

Buttons are a basic component for any component library. Even though they seem simple on the surface, there's a lot of work involved with smoothing out behavior across different kinds of devices (touch/mouse/keyboard) while supporting a pleasant developer experience. Pitching a headless framework to my team is a hard sell without a button

πŸ“ Additional Information

https://react-spectrum.adobe.com/react-aria/useButton.html
https://react-spectrum.adobe.com/blog/building-a-button-part-1.html
https://mui.com/base/react-button/#the-usebutton-hook

Slider cannot be dynamically disabled

πŸ› Bug report

Dynamically changing the disabled option of the slider machine does not affect its enabled/disabled state. It seems like the only way to change it is to remount/rekey the slider with disabled set to the desired value. There are situations where this is less than ideal and could cause other bugs.

πŸ’₯ Steps to reproduce

  1. Set up slider per the docs
  2. Set the disabled option of the slider machine to a boolean piece of state
  3. Change the state from false to true or vice versa after the slider has mounted. Notice that the slider's enabled status is not affected

πŸ’» Link to reproduction

CodeSandbox reproduction: https://codesandbox.io/s/billowing-forest-21bvc4?file=/src/App.tsx

🧐 Expected behavior

If disabled is dynamically changed to true, I expect the slider to be disabled and no longer respond to user events

🌍 System information

Software Version(s)
Zag Version 0.1.13 react / 0.1.11 slider
Browser latest Chrome
Operating System Mac OS Monterey

Mitosis support?

πŸš€ Feature request

Please describe your request in one or two sentences.
Hello,

Do you plan to support Mitosis? It share a lot similarity with Solid.js

Thanks for the good library.

[types] v0.2.1 missing jsx.d.ts

πŸ› Bug report

It looks like packages/types/src/jsx.d.ts was introduced in #178 but didn't make it into the published output on npm? We're seeing the following error:

node_modules/@zag-js/types/dist/index.d.ts:1:26 - error TS2307: Cannot find module './jsx' or its corresponding type declarations.

import type { JSX } from "./jsx";

and jsx.d.ts seems to be missing: https://unpkg.com/browse/@zag-js/[email protected]/dist/

πŸ’₯ Steps to reproduce

  1. Install and import Zag v0.1.11 in a TS project (e.g., @zag-js/react + @zag-js/number-input)
  2. Run tsc
  3. Observe type error

πŸ’» Link to reproduction

CSB probably not necessary here, but I will create one if that's helpful.

🧐 Expected behavior

No TS errors.

🧭 Possible Solution

Publish a new version that includes jsx.d.ts?

🌍 System information

Software Version(s)
Zag Version 0.1.11
Browser N/A
Operating System Windows 10

πŸ“ Additional information

N/A

`description` is not exposed

πŸ› Bug report

Attempting to use a description in a Toast component, I can set the description but am unable to access it from inside the toast.

πŸ’₯ Steps to reproduce

  1. Create a Toast component that uses api.description
function Toast({actor}: {actor: toast.Service}): JSX.Element {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
  const [state, send] = useActor(actor)
  const toastApi = toast.connect<PropTypes>(state, send, normalizeProps)

  function onToastDismiss(): void {
    toastApi.dismiss()
  }

  return (
    <div className="toast max-w-lg pointer-events-auto flex flex-col" {...toastApi.rootProps}>
      <div className="toast-header inline-flex items-center justify-between">
      <h3 className="m-0" {...toastApi.titleProps}>{toastApi.title}</h3>
      <p>{toastApi.description}</p>
      <button type="button" className="btn btn-link" onClick={onToastDismiss}>
        <Icon icon="mdi:close" />
        <span className="sr-only">Dismiss</span>
        </button>
        </div>
      <div {...toastApi.progressbarProps} />
    </div>
  )
}
  1. set up the toast api
  const [state, send] = useMachine(
    toast.group.machine({
      offsets: {
        top: '1rem',
        right: '1rem',
        bottom: '1rem',
        left: '1rem'
      },
      id: uuid()
    })
  )
  const apiToast = toast.group.connect(state, send, normalizeProps) 
  1. Create a toast with a description
  apiToast.create({
        type: 'info',
        title: 'Buy package',
        description: 'Please sign the transaction in your wallet',
        placement: 'bottom-end',
        duration: 7000
      })
  1. Trigger the Toast and observe the lack of description.

πŸ’» Link to reproduction

Error in ts
Property 'description' does not exist on type '{ type: Type; title: string | undefined; placement: Placement; isVisible: boolean; isPaused: boolean; pause(): void; resume(): void; dismiss(): void; rootProps: Dict<any>; progressbarProps: Dict<...>; titleProps: Dict<...>; closeButtonProps: Dict<...>; render(): any; }

🧐 Expected behavior

I expected the description to render in the Toast.

🌍 System information

Software Version(s)
Zag Version 0.1.13
Browser Chrome
Operating System Pop_OS

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.