diff --git a/README.md b/README.md index 2fc1f14..8c2bff2 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Add `corex` to your `mix.exs` dependencies: ```elixir def deps do [ - {:corex, "~> 0.1.0-alpha.21"} + {:corex, "~> 0.1.0-alpha.22"} ] end ``` diff --git a/assets/components/accordion.ts b/assets/components/accordion.ts index a6b07e8..7715945 100644 --- a/assets/components/accordion.ts +++ b/assets/components/accordion.ts @@ -1,4 +1,4 @@ -import {connect, machine, type Props, type Api} from "@zag-js/accordion"; +import { connect, machine, type Props, type Api } from "@zag-js/accordion"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; @@ -30,17 +30,23 @@ export class Accordion extends Component { const { value, disabled } = itemData; this.spreadProps(itemEl, this.api.getItemProps({ value, disabled })); - const triggerEl = itemEl.querySelector('[data-scope="accordion"][data-part="item-trigger"]'); + const triggerEl = itemEl.querySelector( + '[data-scope="accordion"][data-part="item-trigger"]' + ); if (triggerEl) { this.spreadProps(triggerEl, this.api.getItemTriggerProps({ value, disabled })); } - const indicatorEl = itemEl.querySelector('[data-scope="accordion"][data-part="item-indicator"]'); + const indicatorEl = itemEl.querySelector( + '[data-scope="accordion"][data-part="item-indicator"]' + ); if (indicatorEl) { this.spreadProps(indicatorEl, this.api.getItemIndicatorProps({ value, disabled })); } - const contentEl = itemEl.querySelector('[data-scope="accordion"][data-part="item-content"]'); + const contentEl = itemEl.querySelector( + '[data-scope="accordion"][data-part="item-content"]' + ); if (contentEl) { this.spreadProps(contentEl, this.api.getItemContentProps({ value, disabled })); } @@ -56,4 +62,4 @@ export class Accordion extends Component { return []; } } -} \ No newline at end of file +} diff --git a/assets/components/checkbox.ts b/assets/components/checkbox.ts index 0e074fb..9b479e1 100644 --- a/assets/components/checkbox.ts +++ b/assets/components/checkbox.ts @@ -1,4 +1,4 @@ -import {connect, machine, type Props, type Api} from "@zag-js/checkbox"; +import { connect, machine, type Props, type Api } from "@zag-js/checkbox"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; @@ -17,24 +17,32 @@ export class Checkbox extends Component { if (!rootEl) return; this.spreadProps(rootEl, this.api.getRootProps()); - const inputEl = rootEl.querySelector(':scope > [data-scope="checkbox"][data-part="hidden-input"]'); + const inputEl = rootEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="hidden-input"]' + ); if (inputEl) { this.spreadProps(inputEl, this.api.getHiddenInputProps()); } - const labelEl = rootEl.querySelector(':scope > [data-scope="checkbox"][data-part="label"]'); + const labelEl = rootEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="label"]' + ); if (labelEl) { this.spreadProps(labelEl, this.api.getLabelProps()); } - const controlEl = rootEl.querySelector(':scope > [data-scope="checkbox"][data-part="control"]'); + const controlEl = rootEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="control"]' + ); if (controlEl) { this.spreadProps(controlEl, this.api.getControlProps()); - - const indicatorEl = controlEl.querySelector(':scope > [data-scope="checkbox"][data-part="indicator"]'); + + const indicatorEl = controlEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="indicator"]' + ); if (indicatorEl) { this.spreadProps(indicatorEl, this.api.getIndicatorProps()); } } } -} \ No newline at end of file +} diff --git a/assets/components/clipboard.ts b/assets/components/clipboard.ts index 6d67d2b..e0e2784 100644 --- a/assets/components/clipboard.ts +++ b/assets/components/clipboard.ts @@ -1,4 +1,4 @@ -import {connect, machine, type Props, type Api} from "@zag-js/clipboard"; +import { connect, machine, type Props, type Api } from "@zag-js/clipboard"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; @@ -17,16 +17,22 @@ export class Clipboard extends Component { if (rootEl) { this.spreadProps(rootEl, this.api.getRootProps()); - const labelEl = rootEl.querySelector('[data-scope="clipboard"][data-part="label"]'); + const labelEl = rootEl.querySelector( + '[data-scope="clipboard"][data-part="label"]' + ); if (labelEl) { this.spreadProps(labelEl, this.api.getLabelProps()); } - const controlEl = rootEl.querySelector('[data-scope="clipboard"][data-part="control"]'); + const controlEl = rootEl.querySelector( + '[data-scope="clipboard"][data-part="control"]' + ); if (controlEl) { this.spreadProps(controlEl, this.api.getControlProps()); - const inputEl = controlEl.querySelector('[data-scope="clipboard"][data-part="input"]'); + const inputEl = controlEl.querySelector( + '[data-scope="clipboard"][data-part="input"]' + ); if (inputEl) { const inputProps = { ...this.api.getInputProps() }; const inputAriaLabel = this.el.dataset.inputAriaLabel; @@ -36,7 +42,9 @@ export class Clipboard extends Component { this.spreadProps(inputEl, inputProps); } - const triggerEl = controlEl.querySelector('[data-scope="clipboard"][data-part="trigger"]'); + const triggerEl = controlEl.querySelector( + '[data-scope="clipboard"][data-part="trigger"]' + ); if (triggerEl) { const triggerProps = { ...this.api.getTriggerProps() }; const ariaLabel = this.el.dataset.triggerAriaLabel; @@ -47,6 +55,5 @@ export class Clipboard extends Component { } } } - } -} \ No newline at end of file +} diff --git a/assets/components/collapsible.ts b/assets/components/collapsible.ts index 0974df7..74fdbf8 100644 --- a/assets/components/collapsible.ts +++ b/assets/components/collapsible.ts @@ -1,4 +1,4 @@ -import {connect, machine, type Props, type Api} from "@zag-js/collapsible"; +import { connect, machine, type Props, type Api } from "@zag-js/collapsible"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; @@ -13,20 +13,25 @@ export class Collapsible extends Component { } render(): void { - const rootEl = this.el.querySelector('[data-scope="collapsible"][data-part="root"]'); + const rootEl = this.el.querySelector( + '[data-scope="collapsible"][data-part="root"]' + ); if (rootEl) { this.spreadProps(rootEl, this.api.getRootProps()); - const triggerEl = rootEl.querySelector('[data-scope="collapsible"][data-part="trigger"]'); + const triggerEl = rootEl.querySelector( + '[data-scope="collapsible"][data-part="trigger"]' + ); if (triggerEl) { this.spreadProps(triggerEl, this.api.getTriggerProps()); } - const contentEl = rootEl.querySelector('[data-scope="collapsible"][data-part="content"]'); + const contentEl = rootEl.querySelector( + '[data-scope="collapsible"][data-part="content"]' + ); if (contentEl) { this.spreadProps(contentEl, this.api.getContentProps()); } } - } -} \ No newline at end of file +} diff --git a/assets/components/combobox.ts b/assets/components/combobox.ts index c6efe57..b872aed 100644 --- a/assets/components/combobox.ts +++ b/assets/components/combobox.ts @@ -1,62 +1,71 @@ -import * as combobox from "@zag-js/combobox"; -import { collection } from "@zag-js/combobox"; +import { + collection, + connect, + machine, + type Props, + type Api, + type OpenChangeDetails, + type InputValueChangeDetails, +} from "@zag-js/combobox"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; -import { OpenChangeDetails, InputValueChangeDetails } from "@zag-js/combobox"; -export class Combobox extends Component { - options: any[] = []; - allOptions: any[] = []; +export type ComboboxItem = { id?: string; label: string; disabled?: boolean; group?: string }; + +export class Combobox extends Component { + options: ComboboxItem[] = []; + allOptions: ComboboxItem[] = []; hasGroups: boolean = false; - setAllOptions(options: any[]) { + setAllOptions(options: ComboboxItem[]) { this.allOptions = options; this.options = options; } private getCollection() { const items = this.options || this.allOptions || []; - + if (this.hasGroups) { return collection({ items: items, - itemToValue: (item: any) => item.id, - itemToString: (item: any) => item.label, - isItemDisabled: (item: any) => item.disabled, - groupBy: (item: any) => item.group, + itemToValue: (item: ComboboxItem) => item.id ?? "", + itemToString: (item: ComboboxItem) => item.label, + isItemDisabled: (item: ComboboxItem) => item.disabled ?? false, + groupBy: (item: ComboboxItem) => item.group, }); } return collection({ items: items, - itemToValue: (item: any) => item.id, - itemToString: (item: any) => item.label, - isItemDisabled: (item: any) => item.disabled, + itemToValue: (item: ComboboxItem) => item.id ?? "", + itemToString: (item: ComboboxItem) => item.label, + isItemDisabled: (item: ComboboxItem) => item.disabled ?? false, }); } - initMachine(props: combobox.Props): VanillaMachine { - const self = this; - - return new VanillaMachine(combobox.machine, { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initMachine(props: Props): VanillaMachine { + const getCollection = this.getCollection.bind(this); + + return new VanillaMachine(machine, { ...props, get collection() { - return self.getCollection(); + return getCollection(); }, onOpenChange: (details: OpenChangeDetails) => { if (details.open) { - self.options = self.allOptions; + this.options = this.allOptions; } if (props.onOpenChange) { props.onOpenChange(details); } }, onInputValueChange: (details: InputValueChangeDetails) => { - const filtered = self.allOptions.filter((item: any) => + const filtered = this.allOptions.filter((item: ComboboxItem) => item.label.toLowerCase().includes(details.inputValue.toLowerCase()) ); - self.options = filtered.length > 0 ? filtered : self.allOptions; - + this.options = filtered.length > 0 ? filtered : this.allOptions; + if (props.onInputValueChange) { props.onInputValueChange(details); } @@ -64,25 +73,26 @@ export class Combobox extends Component { }); } - initApi(): combobox.Api { - return combobox.connect(this.machine.service, normalizeProps); + initApi(): Api { + return connect(this.machine.service, normalizeProps); } renderItems(): void { - const contentEl = this.el.querySelector('[data-scope="combobox"][data-part="content"]'); + const contentEl = this.el.querySelector( + '[data-scope="combobox"][data-part="content"]' + ); if (!contentEl) return; - const templatesContainer = - this.el.querySelector('[data-templates="combobox"]'); + const templatesContainer = this.el.querySelector('[data-templates="combobox"]'); if (!templatesContainer) return; contentEl .querySelectorAll('[data-scope="combobox"][data-part="item"]:not([data-template])') - .forEach(el => el.remove()); + .forEach((el) => el.remove()); contentEl .querySelectorAll('[data-scope="combobox"][data-part="item-group"]:not([data-template])') - .forEach(el => el.remove()); + .forEach((el) => el.remove()); const items = this.api.collection.items; @@ -99,31 +109,28 @@ export class Combobox extends Component { renderGroupedItems( contentEl: HTMLElement, templatesContainer: HTMLElement, - groups: [string | null, any[]][] + groups: [string | null, ComboboxItem[]][] ): void { for (const [groupId, groupItems] of groups) { if (groupId == null) continue; - + const groupTemplate = templatesContainer.querySelector( `[data-scope="combobox"][data-part="item-group"][data-id="${groupId}"][data-template]` ); if (!groupTemplate) continue; - + const groupEl = groupTemplate.cloneNode(true) as HTMLElement; groupEl.removeAttribute("data-template"); - + this.spreadProps(groupEl, this.api.getItemGroupProps({ id: groupId })); - + const labelEl = groupEl.querySelector( '[data-scope="combobox"][data-part="item-group-label"]' ); if (labelEl) { - this.spreadProps( - labelEl, - this.api.getItemGroupLabelProps({ htmlFor: groupId }) - ); + this.spreadProps(labelEl, this.api.getItemGroupLabelProps({ htmlFor: groupId })); } - + const groupContentEl = groupEl.querySelector( '[data-scope="combobox"][data-part="item-group-content"]' ); @@ -135,16 +142,15 @@ export class Combobox extends Component { const itemEl = this.cloneItem(templatesContainer, item); if (itemEl) groupContentEl.appendChild(itemEl); } - + contentEl.appendChild(groupEl); } } - renderFlatItems( contentEl: HTMLElement, templatesContainer: HTMLElement, - items: any[] + items: ComboboxItem[] ): void { for (const item of items) { const itemEl = this.cloneItem(templatesContainer, item); @@ -152,10 +158,7 @@ export class Combobox extends Component { } } - cloneItem( - templatesContainer: HTMLElement, - item: any - ): HTMLElement | null { + cloneItem(templatesContainer: HTMLElement, item: ComboboxItem): HTMLElement | null { const value = this.api.collection.getItemValue(item); // Find template by data-value (templates are rendered per item in Elixir when custom slots are used) @@ -178,32 +181,22 @@ export class Combobox extends Component { } } - const indicatorEl = - el.querySelector('[data-scope="combobox"][data-part="item-indicator"]'); + const indicatorEl = el.querySelector( + '[data-scope="combobox"][data-part="item-indicator"]' + ); if (indicatorEl) { - this.spreadProps( - indicatorEl, - this.api.getItemIndicatorProps({ item }) - ); + this.spreadProps(indicatorEl, this.api.getItemIndicatorProps({ item })); } return el; } render(): void { - const root = - this.el.querySelector('[data-scope="combobox"][data-part="root"]'); + const root = this.el.querySelector('[data-scope="combobox"][data-part="root"]'); if (!root) return; this.spreadProps(root, this.api.getRootProps()); - [ - "label", - "control", - "input", - "trigger", - "clear-trigger", - "positioner", - ].forEach(part => { + ["label", "control", "input", "trigger", "clear-trigger", "positioner"].forEach((part) => { const el = this.el.querySelector(`[data-scope="combobox"][data-part="${part}"]`); if (!el) return; @@ -211,7 +204,7 @@ export class Combobox extends Component { "get" + part .split("-") - .map(s => s[0].toUpperCase() + s.slice(1)) + .map((s) => s[0].toUpperCase() + s.slice(1)) .join("") + "Props"; @@ -219,11 +212,12 @@ export class Combobox extends Component { this.spreadProps(el, this.api[apiMethod]()); }); - const contentEl = - this.el.querySelector('[data-scope="combobox"][data-part="content"]'); + const contentEl = this.el.querySelector( + '[data-scope="combobox"][data-part="content"]' + ); if (contentEl) { this.spreadProps(contentEl, this.api.getContentProps()); this.renderItems(); } } -} \ No newline at end of file +} diff --git a/assets/components/date-picker.ts b/assets/components/date-picker.ts index f8f736f..e69830f 100644 --- a/assets/components/date-picker.ts +++ b/assets/components/date-picker.ts @@ -1,25 +1,20 @@ -import * as datePicker from "@zag-js/date-picker"; +import { connect, machine, type Props, type Api } from "@zag-js/date-picker"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; -export class DatePicker extends Component { - - initMachine(props: datePicker.Props): VanillaMachine { - return new VanillaMachine(datePicker.machine, { - ...props, - }); +export class DatePicker extends Component { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initMachine(props: Props): VanillaMachine { + return new VanillaMachine(machine, props); } - initApi(): datePicker.Api { - return datePicker.connect(this.machine.service, normalizeProps); + initApi(): Api { + return connect(this.machine.service, normalizeProps); } - private getDayView = () => - this.el.querySelector('[data-part="day-view"]'); - private getMonthView = () => - this.el.querySelector('[data-part="month-view"]'); - private getYearView = () => - this.el.querySelector('[data-part="year-view"]'); + private getDayView = () => this.el.querySelector('[data-part="day-view"]'); + private getMonthView = () => this.el.querySelector('[data-part="month-view"]'); + private getYearView = () => this.el.querySelector('[data-part="year-view"]'); private renderDayTableHeader = () => { const dayView = this.getDayView(); @@ -52,10 +47,7 @@ export class DatePicker extends Component { const td = this.doc.createElement("td"); this.spreadProps(td, this.api.getDayTableCellProps({ value })); const trigger = this.doc.createElement("div"); - this.spreadProps( - trigger, - this.api.getDayTableCellTriggerProps({ value }), - ); + this.spreadProps(trigger, this.api.getDayTableCellTriggerProps({ value })); trigger.textContent = String(value.day); td.appendChild(trigger); tr.appendChild(td); @@ -76,15 +68,9 @@ export class DatePicker extends Component { this.spreadProps(tr, this.api.getTableRowProps()); months.forEach((month) => { const td = this.doc.createElement("td"); - this.spreadProps( - td, - this.api.getMonthTableCellProps({ ...month, columns: 4 }), - ); + this.spreadProps(td, this.api.getMonthTableCellProps({ ...month, columns: 4 })); const trigger = this.doc.createElement("div"); - this.spreadProps( - trigger, - this.api.getMonthTableCellTriggerProps({ ...month, columns: 4 }), - ); + this.spreadProps(trigger, this.api.getMonthTableCellTriggerProps({ ...month, columns: 4 })); trigger.textContent = month.label; td.appendChild(trigger); tr.appendChild(td); @@ -105,15 +91,9 @@ export class DatePicker extends Component { this.spreadProps(tr, this.api.getTableRowProps({ view: "year" })); years.forEach((year) => { const td = this.doc.createElement("td"); - this.spreadProps( - td, - this.api.getYearTableCellProps({ ...year, columns: 4 }), - ); + this.spreadProps(td, this.api.getYearTableCellProps({ ...year, columns: 4 })); const trigger = this.doc.createElement("div"); - this.spreadProps( - trigger, - this.api.getYearTableCellTriggerProps({ ...year, columns: 4 }), - ); + this.spreadProps(trigger, this.api.getYearTableCellTriggerProps({ ...year, columns: 4 })); trigger.textContent = year.label; td.appendChild(trigger); tr.appendChild(td); @@ -123,42 +103,40 @@ export class DatePicker extends Component { }; render(): void { - const root = this.el.querySelector( - '[data-scope="date-picker"][data-part="root"]', - ); + const root = this.el.querySelector('[data-scope="date-picker"][data-part="root"]'); if (root) this.spreadProps(root, this.api.getRootProps()); const label = this.el.querySelector( - '[data-scope="date-picker"][data-part="label"]', + '[data-scope="date-picker"][data-part="label"]' ); if (label) this.spreadProps(label, this.api.getLabelProps()); const control = this.el.querySelector( - '[data-scope="date-picker"][data-part="control"]', + '[data-scope="date-picker"][data-part="control"]' ); if (control) this.spreadProps(control, this.api.getControlProps()); const input = this.el.querySelector( - '[data-scope="date-picker"][data-part="input"]', + '[data-scope="date-picker"][data-part="input"]' ); if (input) { this.spreadProps(input, this.api.getInputProps()); } const trigger = this.el.querySelector( - '[data-scope="date-picker"][data-part="trigger"]', + '[data-scope="date-picker"][data-part="trigger"]' ); if (trigger) { this.spreadProps(trigger, this.api.getTriggerProps()); } const positioner = this.el.querySelector( - '[data-scope="date-picker"][data-part="positioner"]', + '[data-scope="date-picker"][data-part="positioner"]' ); if (positioner) this.spreadProps(positioner, this.api.getPositionerProps()); const content = this.el.querySelector( - '[data-scope="date-picker"][data-part="content"]', + '[data-scope="date-picker"][data-part="content"]' ); if (content) this.spreadProps(content, this.api.getContentProps()); @@ -171,122 +149,59 @@ export class DatePicker extends Component { if (yearView) yearView.hidden = this.api.view !== "year"; if (this.api.view === "day" && dayView) { - const viewControl = dayView.querySelector( - '[data-part="view-control"]', - ); + const viewControl = dayView.querySelector('[data-part="view-control"]'); if (viewControl) - this.spreadProps( - viewControl, - this.api.getViewControlProps({ view: "year" }), - ); - const prevTrigger = dayView.querySelector( - '[data-part="prev-trigger"]', - ); - if (prevTrigger) - this.spreadProps(prevTrigger, this.api.getPrevTriggerProps()); - const viewTrigger = dayView.querySelector( - '[data-part="view-trigger"]', - ); + this.spreadProps(viewControl, this.api.getViewControlProps({ view: "year" })); + const prevTrigger = dayView.querySelector('[data-part="prev-trigger"]'); + if (prevTrigger) this.spreadProps(prevTrigger, this.api.getPrevTriggerProps()); + const viewTrigger = dayView.querySelector('[data-part="view-trigger"]'); if (viewTrigger) { this.spreadProps(viewTrigger, this.api.getViewTriggerProps()); viewTrigger.textContent = this.api.visibleRangeText.start; } - const nextTrigger = dayView.querySelector( - '[data-part="next-trigger"]', - ); - if (nextTrigger) - this.spreadProps(nextTrigger, this.api.getNextTriggerProps()); + const nextTrigger = dayView.querySelector('[data-part="next-trigger"]'); + if (nextTrigger) this.spreadProps(nextTrigger, this.api.getNextTriggerProps()); const table = dayView.querySelector("table"); - if (table) - this.spreadProps(table, this.api.getTableProps({ view: "day" })); + if (table) this.spreadProps(table, this.api.getTableProps({ view: "day" })); const thead = dayView.querySelector("thead"); - if (thead) - this.spreadProps( - thead, - this.api.getTableHeaderProps({ view: "day" }), - ); + if (thead) this.spreadProps(thead, this.api.getTableHeaderProps({ view: "day" })); this.renderDayTableHeader(); this.renderDayTableBody(); } else if (this.api.view === "month" && monthView) { - const viewControl = monthView.querySelector( - '[data-part="view-control"]', - ); + const viewControl = monthView.querySelector('[data-part="view-control"]'); if (viewControl) - this.spreadProps( - viewControl, - this.api.getViewControlProps({ view: "month" }), - ); - const prevTrigger = monthView.querySelector( - '[data-part="prev-trigger"]', - ); + this.spreadProps(viewControl, this.api.getViewControlProps({ view: "month" })); + const prevTrigger = monthView.querySelector('[data-part="prev-trigger"]'); if (prevTrigger) - this.spreadProps( - prevTrigger, - this.api.getPrevTriggerProps({ view: "month" }), - ); - const viewTrigger = monthView.querySelector( - '[data-part="view-trigger"]', - ); + this.spreadProps(prevTrigger, this.api.getPrevTriggerProps({ view: "month" })); + const viewTrigger = monthView.querySelector('[data-part="view-trigger"]'); if (viewTrigger) { - this.spreadProps( - viewTrigger, - this.api.getViewTriggerProps({ view: "month" }), - ); + this.spreadProps(viewTrigger, this.api.getViewTriggerProps({ view: "month" })); viewTrigger.textContent = String(this.api.visibleRange.start.year); } - const nextTrigger = monthView.querySelector( - '[data-part="next-trigger"]', - ); + const nextTrigger = monthView.querySelector('[data-part="next-trigger"]'); if (nextTrigger) - this.spreadProps( - nextTrigger, - this.api.getNextTriggerProps({ view: "month" }), - ); + this.spreadProps(nextTrigger, this.api.getNextTriggerProps({ view: "month" })); const table = monthView.querySelector("table"); - if (table) - this.spreadProps( - table, - this.api.getTableProps({ view: "month", columns: 4 }), - ); + if (table) this.spreadProps(table, this.api.getTableProps({ view: "month", columns: 4 })); this.renderMonthTableBody(); } else if (this.api.view === "year" && yearView) { - const viewControl = yearView.querySelector( - '[data-part="view-control"]', - ); + const viewControl = yearView.querySelector('[data-part="view-control"]'); if (viewControl) - this.spreadProps( - viewControl, - this.api.getViewControlProps({ view: "year" }), - ); - const prevTrigger = yearView.querySelector( - '[data-part="prev-trigger"]', - ); + this.spreadProps(viewControl, this.api.getViewControlProps({ view: "year" })); + const prevTrigger = yearView.querySelector('[data-part="prev-trigger"]'); if (prevTrigger) - this.spreadProps( - prevTrigger, - this.api.getPrevTriggerProps({ view: "year" }), - ); - const decadeText = yearView.querySelector( - '[data-part="decade"]', - ); + this.spreadProps(prevTrigger, this.api.getPrevTriggerProps({ view: "year" })); + const decadeText = yearView.querySelector('[data-part="decade"]'); if (decadeText) { const decade = this.api.getDecade(); decadeText.textContent = `${decade.start} - ${decade.end}`; } - const nextTrigger = yearView.querySelector( - '[data-part="next-trigger"]', - ); + const nextTrigger = yearView.querySelector('[data-part="next-trigger"]'); if (nextTrigger) - this.spreadProps( - nextTrigger, - this.api.getNextTriggerProps({ view: "year" }), - ); + this.spreadProps(nextTrigger, this.api.getNextTriggerProps({ view: "year" })); const table = yearView.querySelector("table"); - if (table) - this.spreadProps( - table, - this.api.getTableProps({ view: "year", columns: 4 }), - ); + if (table) this.spreadProps(table, this.api.getTableProps({ view: "year", columns: 4 })); this.renderYearTableBody(); } } diff --git a/assets/components/dialog.ts b/assets/components/dialog.ts index ae999a3..99cfcee 100644 --- a/assets/components/dialog.ts +++ b/assets/components/dialog.ts @@ -1,4 +1,4 @@ -import {connect, machine, type Props, type Api} from "@zag-js/dialog"; +import { connect, machine, type Props, type Api } from "@zag-js/dialog"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; @@ -15,25 +15,37 @@ export class Dialog extends Component { render(): void { const rootEl = this.el; - const triggerEl = rootEl.querySelector('[data-scope="dialog"][data-part="trigger"]'); + const triggerEl = rootEl.querySelector( + '[data-scope="dialog"][data-part="trigger"]' + ); if (triggerEl) this.spreadProps(triggerEl, this.api.getTriggerProps()); - const backdropEl = rootEl.querySelector('[data-scope="dialog"][data-part="backdrop"]'); + const backdropEl = rootEl.querySelector( + '[data-scope="dialog"][data-part="backdrop"]' + ); if (backdropEl) this.spreadProps(backdropEl, this.api.getBackdropProps()); - const positionerEl = rootEl.querySelector('[data-scope="dialog"][data-part="positioner"]'); + const positionerEl = rootEl.querySelector( + '[data-scope="dialog"][data-part="positioner"]' + ); if (positionerEl) this.spreadProps(positionerEl, this.api.getPositionerProps()); - const contentEl = rootEl.querySelector('[data-scope="dialog"][data-part="content"]'); + const contentEl = rootEl.querySelector( + '[data-scope="dialog"][data-part="content"]' + ); if (contentEl) this.spreadProps(contentEl, this.api.getContentProps()); const titleEl = rootEl.querySelector('[data-scope="dialog"][data-part="title"]'); if (titleEl) this.spreadProps(titleEl, this.api.getTitleProps()); - const descriptionEl = rootEl.querySelector('[data-scope="dialog"][data-part="description"]'); + const descriptionEl = rootEl.querySelector( + '[data-scope="dialog"][data-part="description"]' + ); if (descriptionEl) this.spreadProps(descriptionEl, this.api.getDescriptionProps()); - const closeTriggerEl = rootEl.querySelector('[data-scope="dialog"][data-part="close-trigger"]'); + const closeTriggerEl = rootEl.querySelector( + '[data-scope="dialog"][data-part="close-trigger"]' + ); if (closeTriggerEl) this.spreadProps(closeTriggerEl, this.api.getCloseTriggerProps()); } -} \ No newline at end of file +} diff --git a/assets/components/menu.ts b/assets/components/menu.ts index c627d29..7eef4e0 100644 --- a/assets/components/menu.ts +++ b/assets/components/menu.ts @@ -50,9 +50,7 @@ export class Menu extends Component { const nestedMenuId = triggerEl.dataset.nestedMenu; if (!nestedMenuId) continue; - const childMenu = this.children.find( - (child) => child.el.id === `menu:${nestedMenuId}` - ); + const childMenu = this.children.find((child) => child.el.id === `menu:${nestedMenuId}`); if (!childMenu) continue; const applyProps = () => { @@ -82,17 +80,17 @@ export class Menu extends Component { ); if (positionerEl && contentEl) { - // Always apply positioner and content props so Zag can manage visibility, - // keyboard handlers, aria-activedescendant, etc. across state transitions. - // getContentProps() includes hidden: !open which controls content visibility. this.spreadProps(positionerEl, this.api.getPositionerProps()); this.spreadProps(contentEl, this.api.getContentProps()); + contentEl.style.pointerEvents = "auto"; - // Also manage positioner hidden state for rendering optimization positionerEl.hidden = !this.api.open; - if (this.api.open) { - // Handle menu items - only process items belonging to THIS menu + const isNested = !this.el.querySelector( + '[data-scope="menu"][data-part="trigger"]' + ); + const shouldApplyItems = this.api.open || isNested; + if (shouldApplyItems) { const items = contentEl.querySelectorAll( '[data-scope="menu"][data-part="item"]' ); @@ -109,30 +107,6 @@ export class Menu extends Component { } }); - // Handle option items (checkbox/radio) - const optionItems = contentEl.querySelectorAll( - '[data-scope="menu"][data-part="option-item"]' - ); - optionItems.forEach((optionItemEl) => { - if (!this.isOwnElement(optionItemEl)) return; - - const value = optionItemEl.dataset.value; - const type = optionItemEl.dataset.type as "checkbox" | "radio" | undefined; - if (value && type) { - const checked = optionItemEl.hasAttribute("data-checked"); - const disabled = optionItemEl.hasAttribute("data-disabled"); - this.spreadProps( - optionItemEl, - this.api.getOptionItemProps({ - value, - type, - checked: checked, - disabled: disabled || undefined, - }) - ); - } - }); - // Handle item groups const itemGroups = contentEl.querySelectorAll( '[data-scope="menu"][data-part="item-group"]' diff --git a/assets/components/select.ts b/assets/components/select.ts index 2e87ecb..d971a1c 100644 --- a/assets/components/select.ts +++ b/assets/components/select.ts @@ -1,5 +1,5 @@ -import * as select from "@zag-js/select"; -import { collection } from "@zag-js/select"; +import { connect, machine, collection, type Props, type Api } from "@zag-js/select"; +import type { ListCollection } from "@zag-js/collection"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; import { getString } from "../lib/util"; @@ -9,15 +9,18 @@ type Item = { value?: string; label: string; disabled?: boolean; + group?: string; }; -export class Select extends Component { +export class Select extends Component { private _options: Item[] = []; hasGroups: boolean = false; private placeholder: string = ""; - constructor(el: HTMLElement | null, props: select.Props) { + constructor(el: HTMLElement | null, props: Props) { super(el, props); + const collectionFromProps = (props as Props & { collection?: { items: Item[] } }).collection; + this._options = collectionFromProps?.items ?? []; this.placeholder = getString(this.el, "placeholder") || ""; } @@ -29,7 +32,7 @@ export class Select extends Component { this._options = Array.isArray(options) ? options : []; } - getCollection(): any { + getCollection(): ListCollection { const items = this.options; if (this.hasGroups) { @@ -38,7 +41,7 @@ export class Select extends Component { itemToValue: (item) => item.id ?? item.value ?? "", itemToString: (item) => item.label, isItemDisabled: (item) => !!item.disabled, - groupBy: (item: any) => item.group, + groupBy: (item: Item) => item.group ?? "", }); } @@ -50,177 +53,102 @@ export class Select extends Component { }); } - initMachine(props: select.Props): VanillaMachine { - const self = this; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initMachine(props: Props): VanillaMachine { + const getCollection = this.getCollection.bind(this); + const collectionFromProps = (props as Props & { collection?: ListCollection }).collection; - return new VanillaMachine(select.machine, { + return new VanillaMachine(machine, { ...props, get collection() { - return self.getCollection(); + return collectionFromProps ?? getCollection(); }, }); } - initApi(): select.Api { - return select.connect(this.machine.service, normalizeProps); + initApi(): Api { + return connect(this.machine.service, normalizeProps); } - renderItems(): void { + init = (): void => { + this.machine.start(); + this.render(); + this.machine.subscribe(() => { + this.api = this.initApi(); + this.render(); + }); + }; + + applyItemProps(): void { const contentEl = this.el.querySelector( '[data-scope="select"][data-part="content"]' ); if (!contentEl) return; - const templatesContainer = - this.el.querySelector('[data-templates="select"]'); - if (!templatesContainer) return; - contentEl - .querySelectorAll('[data-scope="select"][data-part="item"]:not([data-template])') - .forEach((el) => el.remove()); + .querySelectorAll('[data-scope="select"][data-part="item-group"]') + .forEach((groupEl) => { + const groupId = groupEl.dataset.id ?? ""; + this.spreadProps(groupEl, this.api.getItemGroupProps({ id: groupId })); + const labelEl = groupEl.querySelector( + '[data-scope="select"][data-part="item-group-label"]' + ); + if (labelEl) { + this.spreadProps(labelEl, this.api.getItemGroupLabelProps({ htmlFor: groupId })); + } + }); contentEl - .querySelectorAll('[data-scope="select"][data-part="item-group"]:not([data-template])') - .forEach((el) => el.remove()); - - const items = this.api.collection.items; - - const groups = this.api.collection.group?.() ?? []; - const hasGroupsInCollection = groups.some(([group]) => group != null); - - if (hasGroupsInCollection) { - this.renderGroupedItems(contentEl, templatesContainer, groups); - } else { - this.renderFlatItems(contentEl, templatesContainer, items); - } - } - - renderGroupedItems( - contentEl: HTMLElement, - templatesContainer: HTMLElement, - groups: [string | null, any[]][] - ): void { - for (const [groupId, groupItems] of groups) { - if (groupId == null) continue; - - const groupTemplate = templatesContainer.querySelector( - `[data-scope="select"][data-part="item-group"][data-id="${groupId}"][data-template]` - ); - if (!groupTemplate) continue; - - const groupEl = groupTemplate.cloneNode(true) as HTMLElement; - groupEl.removeAttribute("data-template"); - - this.spreadProps(groupEl, this.api.getItemGroupProps({ id: groupId })); - - const labelEl = groupEl.querySelector( - '[data-scope="select"][data-part="item-group-label"]' - ); - if (labelEl) { - this.spreadProps( - labelEl, - this.api.getItemGroupLabelProps({ htmlFor: groupId }) + .querySelectorAll('[data-scope="select"][data-part="item"]') + .forEach((itemEl) => { + const value = itemEl.dataset.value ?? ""; + const item = this.options.find((i) => String(i.id ?? i.value ?? "") === String(value)); + if (!item) return; + this.spreadProps(itemEl, this.api.getItemProps({ item })); + const textEl = itemEl.querySelector( + '[data-scope="select"][data-part="item-text"]' ); - } - - const templateItems = groupEl.querySelectorAll( - '[data-scope="select"][data-part="item"][data-template]' - ); - templateItems.forEach((item) => item.remove()); - - for (const item of groupItems) { - const itemEl = this.cloneItem(templatesContainer, item); - if (itemEl) groupEl.appendChild(itemEl); - } - - contentEl.appendChild(groupEl); - } - } - - renderFlatItems( - contentEl: HTMLElement, - templatesContainer: HTMLElement, - items: any[] - ): void { - for (const item of items) { - const itemEl = this.cloneItem(templatesContainer, item); - if (itemEl) contentEl.appendChild(itemEl); - } + if (textEl) { + this.spreadProps(textEl, this.api.getItemTextProps({ item })); + } + const indicatorEl = itemEl.querySelector( + '[data-scope="select"][data-part="item-indicator"]' + ); + if (indicatorEl) { + this.spreadProps(indicatorEl, this.api.getItemIndicatorProps({ item })); + } + }); } - cloneItem( - templatesContainer: HTMLElement, - item: Item - ): HTMLElement | null { - const value = this.api.collection.getItemValue(item); - const template = templatesContainer.querySelector( - `[data-scope="select"][data-part="item"][data-value="${value}"][data-template]` - ); - if (!template) return null; - - const el = template.cloneNode(true) as HTMLElement; - el.removeAttribute("data-template"); + render(): void { + const root = + this.el.querySelector('[data-scope="select"][data-part="root"]') ?? this.el; - this.spreadProps(el, this.api.getItemProps({ item })); + this.spreadProps(root, this.api.getRootProps()); - const textEl = el.querySelector( - '[data-scope="select"][data-part="item-text"]' + const hiddenSelect = this.el.querySelector( + '[data-scope="select"][data-part="hidden-select"]' ); - if (textEl) { - this.spreadProps(textEl, this.api.getItemTextProps({ item })); - } - const indicatorEl = el.querySelector( - '[data-scope="select"][data-part="item-indicator"]' + const valueInput = this.el.querySelector( + '[data-scope="select"][data-part="value-input"]' ); - if (indicatorEl) { - this.spreadProps( - indicatorEl, - this.api.getItemIndicatorProps({ item }) - ); + if (valueInput) { + if (!this.api.value || this.api.value.length === 0) { + valueInput.value = ""; + } else if (this.api.value.length === 1) { + valueInput.value = String(this.api.value[0]); + } else { + valueInput.value = this.api.value.map(String).join(","); + } } - return el; - } - - render(): void { - const root = - this.el.querySelector('[data-scope="select"][data-part="root"]'); - if (!root) return; - this.spreadProps(root, this.api.getRootProps()); - - const hiddenSelect = this.el.querySelector( - '[data-scope="select"][data-part="hidden-select"]' - ); - - const valueInput = this.el.querySelector( - '[data-scope="select"][data-part="value-input"]' - ); - if (valueInput) { - if (!this.api.value || this.api.value.length === 0) { - valueInput.value = ""; - } else if (this.api.value.length === 1) { - valueInput.value = String(this.api.value[0]); - } else { - valueInput.value = this.api.value.map(String).join(","); + if (hiddenSelect) { + this.spreadProps(hiddenSelect, this.api.getHiddenSelectProps()); } - } - if (hiddenSelect) { - this.spreadProps(hiddenSelect, this.api.getHiddenSelectProps()); - } - - [ - "label", - "control", - "trigger", - "indicator", - "clear-trigger", - "positioner", - ].forEach((part) => { - const el = this.el.querySelector( - `[data-scope="select"][data-part="${part}"]` - ); + ["label", "control", "trigger", "indicator", "clear-trigger", "positioner"].forEach((part) => { + const el = this.el.querySelector(`[data-scope="select"][data-part="${part}"]`); if (!el) return; const method = @@ -242,7 +170,7 @@ export class Select extends Component { const valueAsString = this.api.valueAsString; if (this.api.value && this.api.value.length > 0 && !valueAsString) { const selectedValue = this.api.value[0]; - const selectedItem = this.options.find((item: any) => { + const selectedItem = this.options.find((item: Item) => { const itemValue = item.id ?? item.value ?? ""; return String(itemValue) === String(selectedValue); }); @@ -261,8 +189,7 @@ export class Select extends Component { ); if (contentEl) { this.spreadProps(contentEl, this.api.getContentProps()); - this.renderItems(); + this.applyItemProps(); } } } - diff --git a/assets/components/signature-pad.ts b/assets/components/signature-pad.ts index 7ffb7f1..069b794 100644 --- a/assets/components/signature-pad.ts +++ b/assets/components/signature-pad.ts @@ -1,17 +1,15 @@ -import { connect, machine } from "@zag-js/signature-pad"; - -import type { Props, Api } from "@zag-js/signature-pad"; - +import { connect, machine, type Props, type Api } from "@zag-js/signature-pad"; +import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; -import { normalizeProps, VanillaMachine } from "@zag-js/vanilla"; export class SignaturePad extends Component { imageURL: string = ""; - paths: any[] = []; + paths: string[] = []; name?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any initMachine(props: Props): VanillaMachine { - this.name = (props as any).name; + this.name = (props as Props & { name?: string }).name; return new VanillaMachine(machine, props); } @@ -19,7 +17,7 @@ export class SignaturePad extends Component { this.name = name; } - setPaths(paths: any[]) { + setPaths(paths: string[]) { this.paths = paths; } @@ -28,7 +26,9 @@ export class SignaturePad extends Component { } syncPaths = () => { - const segment = this.el.querySelector('[data-scope="signature-pad"][data-part="segment"]'); + const segment = this.el.querySelector( + '[data-scope="signature-pad"][data-part="segment"]' + ); if (!segment) return; const totalPaths = this.api.paths.length + (this.api.currentPath ? 1 : 0); @@ -37,7 +37,9 @@ export class SignaturePad extends Component { segment.innerHTML = ""; this.imageURL = ""; this.paths = []; - const hiddenInput = this.el.querySelector('[data-scope="signature-pad"][data-part="hidden-input"]'); + const hiddenInput = this.el.querySelector( + '[data-scope="signature-pad"][data-part="hidden-input"]' + ); if (hiddenInput) hiddenInput.value = ""; return; } @@ -62,30 +64,44 @@ export class SignaturePad extends Component { }; render() { - const rootEl = this.el.querySelector('[data-scope="signature-pad"][data-part="root"]'); + const rootEl = this.el.querySelector( + '[data-scope="signature-pad"][data-part="root"]' + ); if (!rootEl) return; this.spreadProps(rootEl, this.api.getRootProps()); - const label = rootEl.querySelector('[data-scope="signature-pad"][data-part="label"]'); + const label = rootEl.querySelector( + '[data-scope="signature-pad"][data-part="label"]' + ); if (label) this.spreadProps(label, this.api.getLabelProps()); - const control = rootEl.querySelector('[data-scope="signature-pad"][data-part="control"]'); + const control = rootEl.querySelector( + '[data-scope="signature-pad"][data-part="control"]' + ); if (control) this.spreadProps(control, this.api.getControlProps()); - const segment = rootEl.querySelector('[data-scope="signature-pad"][data-part="segment"]'); + const segment = rootEl.querySelector( + '[data-scope="signature-pad"][data-part="segment"]' + ); if (segment) this.spreadProps(segment, this.api.getSegmentProps()); - const guide = rootEl.querySelector('[data-scope="signature-pad"][data-part="guide"]'); + const guide = rootEl.querySelector( + '[data-scope="signature-pad"][data-part="guide"]' + ); if (guide) this.spreadProps(guide, this.api.getGuideProps()); - const clearBtn = rootEl.querySelector('[data-scope="signature-pad"][data-part="clear-trigger"]'); + const clearBtn = rootEl.querySelector( + '[data-scope="signature-pad"][data-part="clear-trigger"]' + ); if (clearBtn) { this.spreadProps(clearBtn, this.api.getClearTriggerProps()); const hasPaths = this.api.paths.length > 0 || !!this.api.currentPath; clearBtn.hidden = !hasPaths; } - const hiddenInput = rootEl.querySelector('[data-scope="signature-pad"][data-part="hidden-input"]'); + const hiddenInput = rootEl.querySelector( + '[data-scope="signature-pad"][data-part="hidden-input"]' + ); if (hiddenInput) { const pathsForValue = this.paths.length > 0 ? this.paths : this.api.paths; if (this.paths.length === 0 && this.api.paths.length > 0) { diff --git a/assets/components/switch.ts b/assets/components/switch.ts index bd9d571..efbe1ce 100644 --- a/assets/components/switch.ts +++ b/assets/components/switch.ts @@ -1,15 +1,15 @@ -import * as zagSwitch from "@zag-js/switch"; +import { connect, machine, type Props, type Api } from "@zag-js/switch"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; -export class Switch extends Component { +export class Switch extends Component { // eslint-disable-next-line @typescript-eslint/no-explicit-any - initMachine(props: zagSwitch.Props): VanillaMachine { - return new VanillaMachine(zagSwitch.machine, props); + initMachine(props: Props): VanillaMachine { + return new VanillaMachine(machine, props); } - initApi(): zagSwitch.Api { - return zagSwitch.connect(this.machine.service, normalizeProps); + initApi(): Api { + return connect(this.machine.service, normalizeProps); } render(): void { @@ -17,7 +17,9 @@ export class Switch extends Component { if (!rootEl) return; this.spreadProps(rootEl, this.api.getRootProps()); - const inputEl = this.el.querySelector('[data-scope="switch"][data-part="hidden-input"]'); + const inputEl = this.el.querySelector( + '[data-scope="switch"][data-part="hidden-input"]' + ); if (inputEl) { this.spreadProps(inputEl, this.api.getHiddenInputProps()); } @@ -27,7 +29,9 @@ export class Switch extends Component { this.spreadProps(labelEl, this.api.getLabelProps()); } - const controlEl = this.el.querySelector('[data-scope="switch"][data-part="control"]'); + const controlEl = this.el.querySelector( + '[data-scope="switch"][data-part="control"]' + ); if (controlEl) { this.spreadProps(controlEl, this.api.getControlProps()); } diff --git a/assets/components/tabs.ts b/assets/components/tabs.ts index 12c01d1..80f90ad 100644 --- a/assets/components/tabs.ts +++ b/assets/components/tabs.ts @@ -1,4 +1,4 @@ -import {connect, machine, type Props, type Api} from "@zag-js/tabs"; +import { connect, machine, type Props, type Api } from "@zag-js/tabs"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; @@ -21,19 +21,22 @@ export class Tabs extends Component { if (!listEl) return; this.spreadProps(listEl, this.api.getListProps()); - const itemsData = this.el.getAttribute('data-items'); - const items: Array<{value: string, disabled: boolean}> = itemsData + const itemsData = this.el.getAttribute("data-items"); + const items: Array<{ value: string; disabled: boolean }> = itemsData ? JSON.parse(itemsData) : []; const triggers = listEl.querySelectorAll( '[data-scope="tabs"][data-part="trigger"]' ); - + for (let i = 0; i < triggers.length && i < items.length; i++) { const triggerEl = triggers[i]; const item = items[i]; - this.spreadProps(triggerEl, this.api.getTriggerProps({ value: item.value, disabled: item.disabled })); + this.spreadProps( + triggerEl, + this.api.getTriggerProps({ value: item.value, disabled: item.disabled }) + ); } const contents = rootEl.querySelectorAll( @@ -46,4 +49,4 @@ export class Tabs extends Component { this.spreadProps(contentEl, this.api.getContentProps({ value: item.value })); } } -} \ No newline at end of file +} diff --git a/assets/components/toast.ts b/assets/components/toast.ts index fb1d2ad..59247a1 100644 --- a/assets/components/toast.ts +++ b/assets/components/toast.ts @@ -1,5 +1,16 @@ -import * as toast from "@zag-js/toast"; -import type { Store } from "@zag-js/toast"; +import { + connect, + machine, + group, + createStore, + type Props, + type Api, + type GroupProps, + type GroupApi, + type Store, + type StoreProps, + type Options, +} from "@zag-js/toast"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; import { generateId } from "../lib/util"; @@ -8,14 +19,14 @@ export const toastGroups = new Map(); export const toastStores = new Map(); // eslint-disable-next-line @typescript-eslint/no-explicit-any -type ToastItemProps = toast.Props & { +type ToastItemProps = Props & { // eslint-disable-next-line @typescript-eslint/no-explicit-any parent: any; index: number; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export class ToastItem extends Component, toast.Api> { +export class ToastItem extends Component, Api> { private parts!: { title: HTMLElement; description: HTMLElement; @@ -66,12 +77,12 @@ export class ToastItem extends Component, toast.Api> } // eslint-disable-next-line @typescript-eslint/no-explicit-any - initMachine(props: toast.Props): VanillaMachine { - return new VanillaMachine(toast.machine, props); + initMachine(props: Props): VanillaMachine { + return new VanillaMachine(machine, props); } - initApi(): toast.Api { - return toast.connect(this.machine.service, normalizeProps); + initApi(): Api { + return connect(this.machine.service, normalizeProps); } render() { @@ -93,9 +104,12 @@ export class ToastItem extends Component, toast.Api> this.spreadProps(this.parts.description, this.api.getDescriptionProps()); const duration = this.duration; - const isInfinity = duration === "Infinity" || duration === Infinity || duration === Number.POSITIVE_INFINITY; + const isInfinity = + duration === "Infinity" || duration === Infinity || duration === Number.POSITIVE_INFINITY; const toastGroup = this.el.closest('[phx-hook="Toast"]') as HTMLElement; - const loadingIconTemplate = toastGroup?.querySelector('[data-loading-icon-template]') as HTMLElement; + const loadingIconTemplate = toastGroup?.querySelector( + "[data-loading-icon-template]" + ) as HTMLElement; const loadingIcon = loadingIconTemplate?.innerHTML; if (isInfinity) { @@ -118,12 +132,12 @@ export class ToastItem extends Component, toast.Api> }; } -export class ToastGroup extends Component { +export class ToastGroup extends Component { private toastComponents = new Map(); private groupEl: HTMLElement; public store: Store; - constructor(el: HTMLElement, props: toast.GroupProps) { + constructor(el: HTMLElement, props: GroupProps) { super(el, props); this.store = props.store; @@ -140,12 +154,12 @@ export class ToastGroup extends Component { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - initMachine(props: toast.GroupProps): VanillaMachine { - return new VanillaMachine(toast.group.machine, props); + initMachine(props: GroupProps): VanillaMachine { + return new VanillaMachine(group.machine, props); } - initApi(): toast.GroupApi { - return toast.group.connect(this.machine.service, normalizeProps); + initApi(): GroupApi { + return group.connect(this.machine.service, normalizeProps); } render() { @@ -153,7 +167,7 @@ export class ToastGroup extends Component { const toasts = this.api .getToasts() - .filter((t): t is toast.Props & { id: string } => typeof t.id === "string"); + .filter((t): t is Props & { id: string } => typeof t.id === "string"); const nextIds = new Set(toasts.map((t) => t.id)); @@ -201,10 +215,9 @@ export class ToastGroup extends Component { }; } - export function createToastGroup( container: HTMLElement, - options?: Partial & { + options?: Partial & { id?: string; store?: Store; } @@ -213,7 +226,7 @@ export function createToastGroup( const store = options?.store ?? - toast.createStore({ + createStore({ placement: options?.placement ?? "bottom", overlap: options?.overlap, max: options?.max, @@ -244,7 +257,7 @@ export function getToastStore(groupId?: string): Store | undefined { return id ? toastStores.get(id) : undefined; } -export function createToast(options: toast.Options & { groupId?: string }) { +export function createToast(options: Options & { groupId?: string }) { const store = getToastStore(options.groupId); if (!store) throw new Error("No toast store found"); @@ -254,7 +267,7 @@ export function createToast(options: toast.Options & { groupId?: string }) { }); } -export function updateToast(id: string, options: Partial, groupId?: string) { +export function updateToast(id: string, options: Partial, groupId?: string) { getToastStore(groupId)?.update(id, options); } diff --git a/assets/components/toggle-group.ts b/assets/components/toggle-group.ts index 75876ea..8fac7d0 100644 --- a/assets/components/toggle-group.ts +++ b/assets/components/toggle-group.ts @@ -1,24 +1,28 @@ -import * as toggleGroup from "@zag-js/toggle-group"; +import { connect, machine, type Props, type Api } from "@zag-js/toggle-group"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; import { getString, getBoolean } from "../lib/util"; -export class ToggleGroup extends Component { +export class ToggleGroup extends Component { // eslint-disable-next-line @typescript-eslint/no-explicit-any - initMachine(props: toggleGroup.Props): VanillaMachine { - return new VanillaMachine(toggleGroup.machine, props); + initMachine(props: Props): VanillaMachine { + return new VanillaMachine(machine, props); } - initApi(): toggleGroup.Api { - return toggleGroup.connect(this.machine.service, normalizeProps); + initApi(): Api { + return connect(this.machine.service, normalizeProps); } render(): void { - const rootEl = this.el.querySelector('[data-scope="toggle-group"][data-part="root"]'); + const rootEl = this.el.querySelector( + '[data-scope="toggle-group"][data-part="root"]' + ); if (!rootEl) return; this.spreadProps(rootEl, this.api.getRootProps()); - const items = this.el.querySelectorAll('[data-scope="toggle-group"][data-part="item"]'); + const items = this.el.querySelectorAll( + '[data-scope="toggle-group"][data-part="item"]' + ); for (let i = 0; i < items.length; i++) { const itemEl = items[i]; @@ -27,7 +31,6 @@ export class ToggleGroup extends Component { const disabled = getBoolean(itemEl, "disabled"); this.spreadProps(itemEl, this.api.getItemProps({ value, disabled })); - } } } diff --git a/assets/components/tree-view.ts b/assets/components/tree-view.ts index 193feea..f2eef70 100644 --- a/assets/components/tree-view.ts +++ b/assets/components/tree-view.ts @@ -1,4 +1,4 @@ -import * as tree from "@zag-js/tree-view"; +import { collection, connect, machine, type Props, type Api } from "@zag-js/tree-view"; import { VanillaMachine, normalizeProps } from "@zag-js/vanilla"; import { Component } from "../lib/core"; @@ -47,41 +47,37 @@ function buildTreeFromDOM(rootEl: HTMLElement): TreeNode { } const lastIdx = pathArr[pathArr.length - 1]!; if (!parent.children) parent.children = []; - parent.children[lastIdx] = isBranch - ? { id, name, children: [] } - : { id, name }; + parent.children[lastIdx] = isBranch ? { id, name, children: [] } : { id, name }; } return root; } -export class TreeView extends Component { - private collection: ReturnType>; +export class TreeView extends Component { + private treeCollection: ReturnType>; - constructor( - el: HTMLElement | null, - props: Omit & { treeData?: TreeNode } - ) { + constructor(el: HTMLElement | null, props: Omit & { treeData?: TreeNode }) { const treeData = props.treeData ?? buildTreeFromDOM(el as HTMLElement); - const collection = tree.collection({ + const treeCollection = collection({ nodeToValue: (node) => node.id, nodeToString: (node) => node.name, rootNode: treeData, }); - super(el, { ...props, collection } as tree.Props); - this.collection = collection; + super(el, { ...props, collection: treeCollection } as Props); + this.treeCollection = treeCollection; } - initMachine(props: tree.Props): VanillaMachine { - return new VanillaMachine(tree.machine, { ...props }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initMachine(props: Props): VanillaMachine { + return new VanillaMachine(machine, { ...props }); } - initApi(): tree.Api { - return tree.connect(this.machine.service, normalizeProps); + initApi(): Api { + return connect(this.machine.service, normalizeProps); } private getNodeAt(indexPath: number[]): TreeNode | undefined { if (indexPath.length === 0) return undefined; - let current: TreeNode | undefined = this.collection.rootNode as TreeNode; + let current: TreeNode | undefined = this.treeCollection.rootNode as TreeNode; for (const i of indexPath) { current = current?.children?.[i]; if (!current) return undefined; @@ -118,8 +114,7 @@ export class TreeView extends Component { const indicatorEl = branchEl.querySelector( '[data-scope="tree-view"][data-part="branch-indicator"]' ); - if (indicatorEl) - this.spreadProps(indicatorEl, this.api.getBranchIndicatorProps(nodeProps)); + if (indicatorEl) this.spreadProps(indicatorEl, this.api.getBranchIndicatorProps(nodeProps)); const contentEl = branchEl.querySelector( '[data-scope="tree-view"][data-part="branch-content"]' @@ -148,9 +143,7 @@ export class TreeView extends Component { } private syncTree = () => { - const treeEl = this.el.querySelector( - '[data-scope="tree-view"][data-part="tree"]' - ); + const treeEl = this.el.querySelector('[data-scope="tree-view"][data-part="tree"]'); if (!treeEl) return; this.spreadProps(treeEl, this.api.getTreeProps()); @@ -159,14 +152,10 @@ export class TreeView extends Component { render(): void { const rootEl = - this.el.querySelector( - '[data-scope="tree-view"][data-part="root"]' - ) ?? this.el; + this.el.querySelector('[data-scope="tree-view"][data-part="root"]') ?? this.el; this.spreadProps(rootEl, this.api.getRootProps()); - const label = this.el.querySelector( - '[data-scope="tree-view"][data-part="label"]' - ); + const label = this.el.querySelector('[data-scope="tree-view"][data-part="label"]'); if (label) this.spreadProps(label, this.api.getLabelProps()); this.syncTree(); diff --git a/assets/hooks/accordion.ts b/assets/hooks/accordion.ts index fccd3f7..23e09d0 100644 --- a/assets/hooks/accordion.ts +++ b/assets/hooks/accordion.ts @@ -14,67 +14,64 @@ type AccordionHookState = { const AccordionHook: Hook = { mounted(this: object & HookInterface & AccordionHookState) { - const el = this.el; const pushEvent = this.pushEvent.bind(this); - const accordion = new Accordion(el, - { - id: el.id, - ...(getBoolean(el, "controlled") - ? { value: getStringList(el, "value") } - : { defaultValue: getStringList(el, "defaultValue") }), - collapsible: getBoolean(el, "collapsible"), - multiple: getBoolean(el, "multiple"), - orientation: getString(el, "orientation", ["horizontal", "vertical"]), - dir: getDir(el), - onValueChange: (details: ValueChangeDetails) => { - const eventName = getString(el, "onValueChange"); - if (eventName && this.liveSocket.main.isConnected()) { - pushEvent(eventName, { - id: el.id, - value: details.value ?? null, - }); - } - - const eventNameClient = getString(el, "onValueChangeClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - value: details.value ?? null, - }, - }) - ); - } - }, - - onFocusChange: (details: FocusChangeDetails) => { - const eventName = getString(el, "onFocusChange"); - if (eventName && this.liveSocket.main.isConnected()) { - pushEvent(eventName, { - id: el.id, - value: details.value ?? null, - }); - } - - const eventNameClient = getString(el, "onFocusChangeClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - value: details.value ?? null, - }, - }) - ); - } - }, - } - ); + const accordion = new Accordion(el, { + id: el.id, + ...(getBoolean(el, "controlled") + ? { value: getStringList(el, "value") } + : { defaultValue: getStringList(el, "defaultValue") }), + collapsible: getBoolean(el, "collapsible"), + multiple: getBoolean(el, "multiple"), + orientation: getString(el, "orientation", ["horizontal", "vertical"]), + dir: getDir(el), + onValueChange: (details: ValueChangeDetails) => { + const eventName = getString(el, "onValueChange"); + if (eventName && this.liveSocket.main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.value ?? null, + }); + } + + const eventNameClient = getString(el, "onValueChangeClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.value ?? null, + }, + }) + ); + } + }, + + onFocusChange: (details: FocusChangeDetails) => { + const eventName = getString(el, "onFocusChange"); + if (eventName && this.liveSocket.main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.value ?? null, + }); + } + + const eventNameClient = getString(el, "onFocusChangeClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.value ?? null, + }, + }) + ); + } + }, + }); accordion.init(); this.accordion = accordion; @@ -124,18 +121,15 @@ const AccordionHook: Hook = { ...(controlled ? { value: getStringList(this.el, "value") } : { - defaultValue: - this.accordion?.api?.value ?? getStringList(this.el, "defaultValue"), + defaultValue: this.accordion?.api?.value ?? getStringList(this.el, "defaultValue"), }), collapsible: getBoolean(this.el, "collapsible"), multiple: getBoolean(this.el, "multiple"), orientation: getString(this.el, "orientation", ["horizontal", "vertical"]), - dir: getDir(this.el) + dir: getDir(this.el), } as Props); }, - - destroyed(this: object & HookInterface & AccordionHookState) { if (this.onSetValue) { this.el.removeEventListener("phx:accordion:set-value", this.onSetValue); diff --git a/assets/hooks/checkbox.ts b/assets/hooks/checkbox.ts index 84bf729..fd608ba 100644 --- a/assets/hooks/checkbox.ts +++ b/assets/hooks/checkbox.ts @@ -16,12 +16,12 @@ const CheckboxHook: Hook = { mounted(this: object & HookInterface & CheckboxHookState) { const el = this.el; const pushEvent = this.pushEvent.bind(this); - + const zagCheckbox = new Checkbox(el, { id: el.id, ...(getBoolean(el, "controlled") - ? { checked: getBoolean(el, "checked") } - : { defaultChecked: getBoolean(el, "defaultChecked") }), + ? { checked: getBoolean(el, "checked") } + : { defaultChecked: getBoolean(el, "defaultChecked") }), disabled: getBoolean(el, "disabled"), name: getString(el, "name"), form: getString(el, "form"), @@ -113,7 +113,6 @@ const CheckboxHook: Hook = { }, updated(this: object & HookInterface & CheckboxHookState) { - this.checkbox?.updateProps({ id: this.el.id, ...(getBoolean(this.el, "controlled") diff --git a/assets/hooks/clipboard.ts b/assets/hooks/clipboard.ts index 4403218..b0360f0 100644 --- a/assets/hooks/clipboard.ts +++ b/assets/hooks/clipboard.ts @@ -23,8 +23,8 @@ const ClipboardHook: Hook = { id: el.id, timeout: getNumber(el, "timeout"), ...(getBoolean(el, "controlled") - ? { value: getString(el, "value") } - : { defaultValue: getString(el, "defaultValue") }), + ? { value: getString(el, "value") } + : { defaultValue: getString(el, "defaultValue") }), onValueChange: (details: ValueChangeDetails) => { const eventName = getString(el, "onValueChange"); @@ -79,11 +79,14 @@ const ClipboardHook: Hook = { ); this.handlers.push( - this.handleEvent("clipboard_set_value", (payload: { clipboard_id?: string; value: string }) => { - const targetId = payload.clipboard_id; - if (targetId && targetId !== el.id) return; - clipboard.api.setValue(payload.value); - }) + this.handleEvent( + "clipboard_set_value", + (payload: { clipboard_id?: string; value: string }) => { + const targetId = payload.clipboard_id; + if (targetId && targetId !== el.id) return; + clipboard.api.setValue(payload.value); + } + ) ); this.handlers.push( diff --git a/assets/hooks/collapsible.ts b/assets/hooks/collapsible.ts index a34b325..c977406 100644 --- a/assets/hooks/collapsible.ts +++ b/assets/hooks/collapsible.ts @@ -60,11 +60,14 @@ const CollapsibleHook: Hook = { this.handlers = []; this.handlers.push( - this.handleEvent("collapsible_set_open", (payload: { collapsible_id?: string; open: boolean }) => { - const targetId = payload.collapsible_id; - if (targetId && targetId !== el.id) return; - collapsible.api.setOpen(payload.open); - }) + this.handleEvent( + "collapsible_set_open", + (payload: { collapsible_id?: string; open: boolean }) => { + const targetId = payload.collapsible_id; + if (targetId && targetId !== el.id) return; + collapsible.api.setOpen(payload.open); + } + ) ); this.handlers.push( diff --git a/assets/hooks/combobox.ts b/assets/hooks/combobox.ts index 6583537..8501cc1 100644 --- a/assets/hooks/combobox.ts +++ b/assets/hooks/combobox.ts @@ -1,7 +1,13 @@ import type { Hook } from "phoenix_live_view"; import type { HookInterface, CallbackRef } from "phoenix_live_view/assets/js/types/view_hook"; import { Combobox } from "../components/combobox"; -import type { Props, InputValueChangeDetails, OpenChangeDetails, ValueChangeDetails, PositioningOptions } from "@zag-js/combobox"; +import type { + Props, + InputValueChangeDetails, + OpenChangeDetails, + ValueChangeDetails, + PositioningOptions, +} from "@zag-js/combobox"; import type { Direction } from "@zag-js/types"; import { getString, getBoolean, getStringList } from "../lib/util"; @@ -15,8 +21,8 @@ function snakeToCamel(str: string): string { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } -function transformPositioningOptions(obj: any): PositioningOptions { - const result: any = {}; +function transformPositioningOptions(obj: Record): PositioningOptions { + const result: Record = {}; for (const [key, value] of Object.entries(obj)) { const camelKey = snakeToCamel(key); result[camelKey] = value; @@ -30,7 +36,7 @@ const ComboboxHook: Hook = { const pushEvent = this.pushEvent.bind(this); const allItems = JSON.parse(el.dataset.collection || "[]"); - const hasGroups = allItems.some((item: any) => item.group !== undefined); + const hasGroups = allItems.some((item: { group?: unknown }) => item.group !== undefined); const props: Props = { id: el.id, @@ -92,7 +98,6 @@ const ComboboxHook: Hook = { } }, onInputValueChange: (details: InputValueChangeDetails) => { - const eventName = getString(el, "onInputValueChange"); if (eventName && !this.liveSocket.main.isDead && this.liveSocket.main.isConnected()) { pushEvent(eventName, { @@ -117,58 +122,57 @@ const ComboboxHook: Hook = { } }, onValueChange: (details: ValueChangeDetails) => { - const valueInput = el.querySelector( '[data-scope="combobox"][data-part="value-input"]' ); if (valueInput) { - const idValue = details.value.length === 0 - ? "" - : details.value.length === 1 - ? String(details.value[0]) - : details.value.map(String).join(","); + const idValue = + details.value.length === 0 + ? "" + : details.value.length === 1 + ? String(details.value[0]) + : details.value.map(String).join(","); valueInput.value = idValue; - + const formId = valueInput.getAttribute("form"); let form: HTMLFormElement | null = null; - + if (formId) { form = document.getElementById(formId) as HTMLFormElement; } else { form = valueInput.closest("form"); } - - const changeEvent = new Event("change", { - bubbles: true, - cancelable: true + + const changeEvent = new Event("change", { + bubbles: true, + cancelable: true, }); valueInput.dispatchEvent(changeEvent); - - const inputEvent = new Event("input", { - bubbles: true, - cancelable: true + + const inputEvent = new Event("input", { + bubbles: true, + cancelable: true, }); valueInput.dispatchEvent(inputEvent); - - if (form && form.hasAttribute("phx-change")) { + + if (form && form.hasAttribute("phx-change")) { requestAnimationFrame(() => { - const formElement = form.querySelector('input, select, textarea') as HTMLElement; + const formElement = form.querySelector("input, select, textarea") as HTMLElement; if (formElement) { - const formChangeEvent = new Event("change", { - bubbles: true, - cancelable: true + const formChangeEvent = new Event("change", { + bubbles: true, + cancelable: true, }); formElement.dispatchEvent(formChangeEvent); } else { - const formChangeEvent = new Event("change", { - bubbles: true, - cancelable: true + const formChangeEvent = new Event("change", { + bubbles: true, + cancelable: true, }); form.dispatchEvent(formChangeEvent); } }); } - } const eventName = getString(el, "onValueChange"); @@ -204,13 +208,15 @@ const ComboboxHook: Hook = { const initialValue = getBoolean(el, "controlled") ? getStringList(el, "value") : getStringList(el, "defaultValue"); - + if (initialValue && initialValue.length > 0) { - const selectedItems = allItems.filter((item: any) => - initialValue.includes(item.id) + const selectedItems = allItems.filter((item: { id?: string }) => + initialValue.includes(item.id ?? "") ); if (selectedItems.length > 0) { - const inputValue = selectedItems.map((item: any) => item.label).join(", "); + const inputValue = selectedItems + .map((item: { label?: string }) => item.label ?? "") + .join(", "); if (combobox.api && typeof combobox.api.setInputValue === "function") { combobox.api.setInputValue(inputValue); } else { @@ -230,8 +236,8 @@ const ComboboxHook: Hook = { updated(this: object & HookInterface & ComboboxHookState) { const newCollection = JSON.parse(this.el.dataset.collection || "[]"); - const hasGroups = newCollection.some((item: any) => item.group !== undefined); - + const hasGroups = newCollection.some((item: { group?: unknown }) => item.group !== undefined); + if (this.combobox) { this.combobox.hasGroups = hasGroups; this.combobox.setAllOptions(newCollection); @@ -248,7 +254,7 @@ const ComboboxHook: Hook = { required: getBoolean(this.el, "required"), readOnly: getBoolean(this.el, "readOnly"), }); - + const inputEl = this.el.querySelector( '[data-scope="combobox"][data-part="input"]' ); @@ -271,4 +277,4 @@ const ComboboxHook: Hook = { }, }; -export { ComboboxHook as Combobox }; \ No newline at end of file +export { ComboboxHook as Combobox }; diff --git a/assets/hooks/corex.ts b/assets/hooks/corex.ts index a2dc197..4a37a3f 100644 --- a/assets/hooks/corex.ts +++ b/assets/hooks/corex.ts @@ -1,11 +1,8 @@ import type { Hook } from "phoenix_live_view"; -type HookModule = Record | undefined>; +type HookModule = Record | undefined>; -function hooks( - importFn: () => Promise, - exportName: string -): Hook { +function hooks(importFn: () => Promise, exportName: string): Hook { return { async mounted() { const mod = await importFn(); diff --git a/assets/hooks/date-picker.ts b/assets/hooks/date-picker.ts index 23af83d..0ca2911 100644 --- a/assets/hooks/date-picker.ts +++ b/assets/hooks/date-picker.ts @@ -28,15 +28,15 @@ const DatePickerHook: Hook = { const min = getString(el, "min"); const max = getString(el, "max"); const positioningJson = getString(el, "positioning"); - const parseList = (v: string[] | undefined) => (v ? v.map((x) => datePicker.parse(x)) : undefined); + const parseList = (v: string[] | undefined) => + v ? v.map((x) => datePicker.parse(x)) : undefined; const parseOne = (v: string | undefined) => (v ? datePicker.parse(v) : undefined); - const datePickerInstance = new DatePicker(el, { id: el.id, ...(getBoolean(el, "controlled") - ? { value: parseList(getStringList(el, "value") ) } - : { defaultValue: parseList(getStringList(el, "defaultValue") )}), + ? { value: parseList(getStringList(el, "value")) } + : { defaultValue: parseList(getStringList(el, "defaultValue")) }), defaultFocusedValue: parseOne(getString(el, "focusedValue")), defaultView: getString<"day" | "month" | "year">(el, "defaultView", ["day", "month", "year"]), dir: getString(el, "dir", ["ltr", "rtl"]), @@ -53,7 +53,11 @@ const DatePickerHook: Hook = { numOfMonths: getNumber(el, "numOfMonths"), startOfWeek: getNumber(el, "startOfWeek"), fixedWeeks: getBoolean(el, "fixedWeeks"), - selectionMode: getString<"single" | "multiple" | "range">(el, "selectionMode", ["single", "multiple", "range"]), + selectionMode: getString<"single" | "multiple" | "range">(el, "selectionMode", [ + "single", + "multiple", + "range", + ]), placeholder: getString(el, "placeholder"), minView: getString<"day" | "month" | "year">(el, "minView", ["day", "month", "year"]), maxView: getString<"day" | "month" | "year">(el, "maxView", ["day", "month", "year"]), @@ -67,9 +71,7 @@ const DatePickerHook: Hook = { .join(",") : ""; - const hiddenInput = el.querySelector( - `#${el.id}-value` - ); + const hiddenInput = el.querySelector(`#${el.id}-value`); if (hiddenInput && hiddenInput.value !== isoStr) { hiddenInput.value = isoStr; hiddenInput.dispatchEvent(new Event("input", { bubbles: true })); @@ -84,7 +86,7 @@ const DatePickerHook: Hook = { }); } }, - onFocusChange: (details: any) => { + onFocusChange: (details: { focused?: boolean }) => { const eventName = getString(el, "onFocusChange"); if (eventName && liveSocket.main.isConnected()) { pushEvent(eventName, { @@ -102,7 +104,7 @@ const DatePickerHook: Hook = { }); } }, - onVisibleRangeChange: (details: any) => { + onVisibleRangeChange: (details: { start?: unknown; end?: unknown }) => { const eventName = getString(el, "onVisibleRangeChange"); if (eventName && liveSocket.main.isConnected()) { pushEvent(eventName, { @@ -112,7 +114,7 @@ const DatePickerHook: Hook = { }); } }, - onOpenChange: (details: any) => { + onOpenChange: (details: { open?: boolean }) => { const eventName = getString(el, "onOpenChange"); if (eventName && liveSocket.main.isConnected()) { pushEvent(eventName, { @@ -127,18 +129,21 @@ const DatePickerHook: Hook = { this.datePicker = datePickerInstance; const inputWrapper = el.querySelector( - '[data-scope="date-picker"][data-part="input-wrapper"]', + '[data-scope="date-picker"][data-part="input-wrapper"]' ); if (inputWrapper) inputWrapper.removeAttribute("data-loading"); this.handlers = []; this.handlers.push( - this.handleEvent("date_picker_set_value", (payload: { date_picker_id?: string; value: string }) => { - const targetId = payload.date_picker_id; - if (targetId && targetId !== el.id) return; - datePickerInstance.api.setValue([datePicker.parse(payload.value)]); - }) + this.handleEvent( + "date_picker_set_value", + (payload: { date_picker_id?: string; value: string }) => { + const targetId = payload.date_picker_id; + if (targetId && targetId !== el.id) return; + datePickerInstance.api.setValue([datePicker.parse(payload.value)]); + } + ) ); this.onSetValue = (event: Event) => { @@ -153,11 +158,12 @@ const DatePickerHook: Hook = { updated(this: object & HookInterface & DatePickerHookState) { const el = this.el; const inputWrapper = el.querySelector( - '[data-scope="date-picker"][data-part="input-wrapper"]', + '[data-scope="date-picker"][data-part="input-wrapper"]' ); if (inputWrapper) inputWrapper.removeAttribute("data-loading"); - const parseList = (v: string[] | undefined) => (v ? v.map((x) => datePicker.parse(x)) : undefined); + const parseList = (v: string[] | undefined) => + v ? v.map((x) => datePicker.parse(x)) : undefined; const min = getString(el, "min"); const max = getString(el, "max"); const positioningJson = getString(el, "positioning"); @@ -166,8 +172,8 @@ const DatePickerHook: Hook = { this.datePicker?.updateProps({ ...(getBoolean(el, "controlled") - ? { value: parseList(getStringList(el, "value") ) } - : { defaultValue: parseList(getStringList(el, "defaultValue") )}), + ? { value: parseList(getStringList(el, "value")) } + : { defaultValue: parseList(getStringList(el, "defaultValue")) }), defaultFocusedValue: focusedStr ? datePicker.parse(focusedStr) : undefined, defaultView: getString<"day" | "month" | "year">(el, "defaultView", ["day", "month", "year"]), dir: getString(this.el, "dir", ["ltr", "rtl"]), @@ -184,7 +190,11 @@ const DatePickerHook: Hook = { numOfMonths: getNumber(this.el, "numOfMonths"), startOfWeek: getNumber(this.el, "startOfWeek"), fixedWeeks: getBoolean(this.el, "fixedWeeks"), - selectionMode: getString<"single" | "multiple" | "range">(this.el, "selectionMode", ["single", "multiple", "range"]), + selectionMode: getString<"single" | "multiple" | "range">(this.el, "selectionMode", [ + "single", + "multiple", + "range", + ]), placeholder: getString(this.el, "placeholder"), minView: getString<"day" | "month" | "year">(this.el, "minView", ["day", "month", "year"]), maxView: getString<"day" | "month" | "year">(this.el, "maxView", ["day", "month", "year"]), @@ -197,12 +207,12 @@ const DatePickerHook: Hook = { const serverIso = serverValues?.join(",") ?? ""; const zagValue = this.datePicker.api.value; const zagIso = zagValue?.length - ? zagValue.map((d: { year: number; month: number; day: number }) => toISOString(d)).join(",") + ? zagValue + .map((d: { year: number; month: number; day: number }) => toISOString(d)) + .join(",") : ""; if (serverIso !== zagIso) { - const parsed = serverValues?.length - ? serverValues.map((x) => datePicker.parse(x)) - : []; + const parsed = serverValues?.length ? serverValues.map((x) => datePicker.parse(x)) : []; this.datePicker.api.setValue(parsed); } } diff --git a/assets/hooks/dialog.ts b/assets/hooks/dialog.ts index 9647796..a348a4a 100644 --- a/assets/hooks/dialog.ts +++ b/assets/hooks/dialog.ts @@ -28,7 +28,7 @@ const DialogHook: Hook = { preventScroll: getBoolean(el, "preventScroll"), restoreFocus: getBoolean(el, "restoreFocus"), dir: getString(el, "dir", ["ltr", "rtl"]), - + onOpenChange: (details: OpenChangeDetails) => { const eventName = getString(el, "onOpenChange"); if (eventName && this.liveSocket.main.isConnected()) { diff --git a/assets/hooks/menu.ts b/assets/hooks/menu.ts index 3104192..06b6322 100644 --- a/assets/hooks/menu.ts +++ b/assets/hooks/menu.ts @@ -10,6 +10,7 @@ type MenuHookState = { menu?: Menu; handlers?: Array; onSetOpen?: (event: Event) => void; + onSubmenuItemClick?: (event: Event) => void; nestedMenus?: Map; }; @@ -21,82 +22,81 @@ const MenuHook: Hook = { return; } - const menu = new Menu( - el, - { - id: el.id.replace("menu:", ""), - ...(getBoolean(el, "controlled") - ? { open: getBoolean(el, "open") } - : { defaultOpen: getBoolean(el, "defaultOpen") }), - closeOnSelect: getBoolean(el, "closeOnSelect"), - loopFocus: getBoolean(el, "loopFocus"), - typeahead: getBoolean(el, "typeahead"), - composite: getBoolean(el, "composite"), - dir: getString(el, "dir", ["ltr", "rtl"]), - onSelect: (details: SelectionDetails) => { - const redirect = getBoolean(el, "redirect"); - const itemEl = [...el.querySelectorAll('[data-scope="menu"][data-part="item"]')].find( - (node) => node.getAttribute("data-value") === details.value - ); - const itemRedirect = itemEl?.getAttribute("data-redirect"); - const itemNewTab = itemEl?.hasAttribute("data-new-tab"); - const doRedirect = - redirect && - details.value && - !this.liveSocket.main.isConnected() && - itemRedirect !== "false"; - if (doRedirect) { - if (itemNewTab) { - window.open(details.value, "_blank", "noopener,noreferrer"); - } else { - window.location.href = details.value; - } - } - const eventName = getString(el, "onSelect"); - if (eventName && this.liveSocket.main.isConnected()) { - this.pushEvent(eventName, { - id: el.id, - value: details.value ?? null, - }); + const pushEvent = this.pushEvent.bind(this); + const getMain = () => this.liveSocket?.main; + + const menu = new Menu(el, { + id: el.id.replace("menu:", ""), + ...(getBoolean(el, "controlled") + ? { open: getBoolean(el, "open") } + : { defaultOpen: getBoolean(el, "defaultOpen") }), + closeOnSelect: getBoolean(el, "closeOnSelect"), + loopFocus: getBoolean(el, "loopFocus"), + typeahead: getBoolean(el, "typeahead"), + composite: getBoolean(el, "composite"), + dir: getString(el, "dir", ["ltr", "rtl"]), + onSelect: (details: SelectionDetails) => { + const redirect = getBoolean(el, "redirect"); + const itemEl = [ + ...el.querySelectorAll('[data-scope="menu"][data-part="item"]'), + ].find((node) => node.getAttribute("data-value") === details.value); + const itemRedirect = itemEl?.getAttribute("data-redirect"); + const itemNewTab = itemEl?.hasAttribute("data-new-tab"); + const main = getMain(); + const doRedirect = + redirect && details.value && (main?.isDead ?? true) && itemRedirect !== "false"; + if (doRedirect) { + if (itemNewTab) { + window.open(details.value, "_blank", "noopener,noreferrer"); + } else { + window.location.href = details.value; } + } + const eventName = getString(el, "onSelect"); + if (eventName && main && !main.isDead && main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.value ?? null, + }); + } - const eventNameClient = getString(el, "onSelectClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - value: details.value ?? null, - }, - }) - ); - } - }, - onOpenChange: (details: OpenChangeDetails) => { - const eventName = getString(el, "onOpenChange"); - if (eventName && this.liveSocket.main.isConnected()) { - this.pushEvent(eventName, { - id: el.id, - open: details.open ?? false, - }); - } + const eventNameClient = getString(el, "onSelectClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.value ?? null, + }, + }) + ); + } + }, + onOpenChange: (details: OpenChangeDetails) => { + const main = getMain(); + const eventName = getString(el, "onOpenChange"); + if (eventName && main && !main.isDead && main.isConnected()) { + pushEvent(eventName, { + id: el.id, + open: details.open ?? false, + }); + } - const eventNameClient = getString(el, "onOpenChangeClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - open: details.open ?? false, - }, - }) - ); - } - }, - } - ); + const eventNameClient = getString(el, "onOpenChangeClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + open: details.open ?? false, + }, + }) + ); + } + }, + }); menu.init(); this.menu = menu; @@ -104,12 +104,12 @@ const MenuHook: Hook = { const nestedMenuElements = el.querySelectorAll( '[data-scope="menu"][data-nested="menu"]' ); - + const nestedMenuInstances: Menu[] = []; - nestedMenuElements.forEach((nestedEl) => { + nestedMenuElements.forEach((nestedEl, index) => { const nestedId = nestedEl.id; if (nestedId) { - const nestedMenuId = nestedId.replace("menu:", ""); + const nestedMenuId = `${nestedId}-${index}`; const nestedMenu = new Menu(nestedEl, { id: nestedMenuId, dir: getString(nestedEl, "dir", ["ltr", "rtl"]), @@ -117,14 +117,52 @@ const MenuHook: Hook = { loopFocus: getBoolean(nestedEl, "loopFocus"), typeahead: getBoolean(nestedEl, "typeahead"), composite: getBoolean(nestedEl, "composite"), + onSelect: (details: SelectionDetails) => { + const redirect = getBoolean(el, "redirect"); + const itemEl = [ + ...el.querySelectorAll('[data-scope="menu"][data-part="item"]'), + ].find((node) => node.getAttribute("data-value") === details.value); + const itemRedirect = itemEl?.getAttribute("data-redirect"); + const itemNewTab = itemEl?.hasAttribute("data-new-tab"); + const main = getMain(); + const doRedirect = + redirect && details.value && (main?.isDead ?? true) && itemRedirect !== "false"; + if (doRedirect) { + if (itemNewTab) { + window.open(details.value, "_blank", "noopener,noreferrer"); + } else { + window.location.href = details.value; + } + } + const eventName = getString(el, "onSelect"); + if (eventName && main && !main.isDead && main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.value ?? null, + }); + } + + const eventNameClient = getString(el, "onSelectClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.value ?? null, + }, + }) + ); + } + }, }); - + nestedMenu.init(); this.nestedMenus?.set(nestedId, nestedMenu); nestedMenuInstances.push(nestedMenu); } }); - + setTimeout(() => { nestedMenuInstances.forEach((nestedMenu) => { if (this.menu) { @@ -133,15 +171,6 @@ const MenuHook: Hook = { } }); - if (this.menu) { - this.menu.api = this.menu.initApi(); - this.menu.render(); - } - nestedMenuInstances.forEach((nestedMenu) => { - nestedMenu.api = nestedMenu.initApi(); - nestedMenu.render(); - }); - if (this.menu && this.menu.children.length > 0) { this.menu.renderSubmenuTriggers(); } @@ -149,7 +178,7 @@ const MenuHook: Hook = { this.onSetOpen = (event: Event) => { const { open } = (event as CustomEvent<{ open: boolean }>).detail; - menu.api.setOpen(open); + if (menu.api.open !== open) menu.api.setOpen(open); }; el.addEventListener("phx:menu:set-open", this.onSetOpen); @@ -158,7 +187,8 @@ const MenuHook: Hook = { this.handlers.push( this.handleEvent("menu_set_open", (payload: { menu_id?: string; open: boolean }) => { const targetId = payload.menu_id; - if (targetId && targetId !== el.id) return; + const matches = !targetId || el.id === targetId || el.id === `menu:${targetId}`; + if (!matches) return; menu.api.setOpen(payload.open); }) ); diff --git a/assets/hooks/select.ts b/assets/hooks/select.ts index 22715b0..b85ecd9 100644 --- a/assets/hooks/select.ts +++ b/assets/hooks/select.ts @@ -1,5 +1,6 @@ import type { Hook } from "phoenix_live_view"; import type { HookInterface, CallbackRef } from "phoenix_live_view/assets/js/types/view_hook"; +import { collection } from "@zag-js/select"; import { Select } from "../components/select"; import type { Props, ValueChangeDetails } from "@zag-js/select"; import type { Direction } from "@zag-js/types"; @@ -7,6 +8,32 @@ import type { PositioningOptions } from "@zag-js/popper"; import { getString, getBoolean, getStringList } from "../lib/util"; +type SelectItem = { + id?: string; + value?: string; + label: string; + disabled?: boolean; + group?: string; +}; + +function buildCollection(items: SelectItem[], hasGroups: boolean) { + if (hasGroups) { + return collection({ + items, + itemToValue: (item: SelectItem) => item.id ?? item.value ?? "", + itemToString: (item: SelectItem) => item.label, + isItemDisabled: (item: SelectItem) => !!item.disabled, + groupBy: (item: SelectItem) => item.group ?? "", + }); + } + return collection({ + items, + itemToValue: (item: SelectItem) => item.id ?? item.value ?? "", + itemToString: (item: SelectItem) => item.label, + isItemDisabled: (item: SelectItem) => !!item.disabled, + }); +} + type SelectHookState = { select?: Select; handlers?: Array; @@ -17,8 +44,8 @@ function snakeToCamel(str: string): string { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } -function transformPositioningOptions(obj: any): PositioningOptions { - const result: any = {}; +function transformPositioningOptions(obj: Record): PositioningOptions { + const result: Record = {}; for (const [key, value] of Object.entries(obj)) { const camelKey = snakeToCamel(key); result[camelKey] = value; @@ -29,113 +56,93 @@ function transformPositioningOptions(obj: any): PositioningOptions { const SelectHook: Hook = { mounted(this: object & HookInterface & SelectHookState) { const el = this.el; - const allItems = JSON.parse(el.dataset.collection || "[]"); - const hasGroups = allItems.some((item: any) => item.group !== undefined); - let selectComponent: Select | undefined; - const hook = this; - this.wasFocused = false; - - - - selectComponent = new Select(el, - { - id: el.id, - ...(getBoolean(el, "controlled") - ? { value: getStringList(el, "value") } - : { defaultValue: getStringList(el, "defaultValue") }), - disabled: getBoolean(el, "disabled"), - closeOnSelect: getBoolean(el, "closeOnSelect"), - dir: getString(el, "dir", ["ltr", "rtl"]), - loopFocus: getBoolean(el, "loopFocus"), - multiple: getBoolean(el, "multiple"), - invalid: getBoolean(el, "invalid"), - name: getString(el, "name"), - form: getString(el, "form"), - readOnly: getBoolean(el, "readOnly"), - required: getBoolean(el, "required"), - positioning: (() => { - const positioningJson = el.dataset.positioning; - if (positioningJson) { - try { - const parsed = JSON.parse(positioningJson); - return transformPositioningOptions(parsed); - } catch { - return undefined; - } - } - return undefined; - })(), - onValueChange: (details: ValueChangeDetails) => { - const redirect = getBoolean(el, "redirect"); - const firstValue = - details.value.length > 0 ? String(details.value[0]) : null; - const firstItem = details.items?.length ? details.items[0] : null; - const itemRedirect = firstItem && typeof firstItem === "object" && firstItem !== null && "redirect" in firstItem ? (firstItem as { redirect?: boolean }).redirect : undefined; - const itemNewTab = firstItem && typeof firstItem === "object" && firstItem !== null && "new_tab" in firstItem ? (firstItem as { new_tab?: boolean }).new_tab : undefined; - const doRedirect = redirect && firstValue && hook.liveSocket.main.isDead && itemRedirect !== false; - const openInNewTab = itemNewTab === true; - if (doRedirect) { - if (openInNewTab) { - window.open(firstValue, "_blank", "noopener,noreferrer"); - } else { - window.location.href = firstValue; - } + const allItems = JSON.parse(el.dataset.collection || "[]") as SelectItem[]; + const hasGroups = allItems.some((item: SelectItem) => item.group !== undefined); + const initialCollection = buildCollection(allItems, hasGroups); + const selectComponent = new Select(el, { + id: el.id, + collection: initialCollection, + ...(getBoolean(el, "controlled") + ? { value: getStringList(el, "value") } + : { defaultValue: getStringList(el, "defaultValue") }), + disabled: getBoolean(el, "disabled"), + closeOnSelect: getBoolean(el, "closeOnSelect"), + dir: getString(el, "dir", ["ltr", "rtl"]), + loopFocus: getBoolean(el, "loopFocus"), + multiple: getBoolean(el, "multiple"), + invalid: getBoolean(el, "invalid"), + name: getString(el, "name"), + form: getString(el, "form"), + readOnly: getBoolean(el, "readOnly"), + required: getBoolean(el, "required"), + positioning: (() => { + const positioningJson = el.dataset.positioning; + if (positioningJson) { + try { + const parsed = JSON.parse(positioningJson); + return transformPositioningOptions(parsed); + } catch { + return undefined; } - const valueInput = el.querySelector( - '[data-scope="select"][data-part="value-input"]' - ); - if (valueInput) { - valueInput.value = - details.value.length === 0 - ? "" - : details.value.length === 1 - ? String(details.value[0]) - : details.value.map(String).join(","); - valueInput.dispatchEvent(new Event("input", { bubbles: true })); - valueInput.dispatchEvent(new Event("change", { bubbles: true })); + } + return undefined; + })(), + onValueChange: (details: ValueChangeDetails) => { + const redirect = getBoolean(el, "redirect"); + const firstValue = details.value.length > 0 ? String(details.value[0]) : null; + const firstItem = details.items?.length ? details.items[0] : null; + const itemRedirect = + firstItem && + typeof firstItem === "object" && + firstItem !== null && + "redirect" in firstItem + ? (firstItem as { redirect?: boolean }).redirect + : undefined; + const itemNewTab = + firstItem && typeof firstItem === "object" && firstItem !== null && "new_tab" in firstItem + ? (firstItem as { new_tab?: boolean }).new_tab + : undefined; + const doRedirect = + redirect && firstValue && this.liveSocket.main.isDead && itemRedirect !== false; + const openInNewTab = itemNewTab === true; + if (doRedirect) { + if (openInNewTab) { + window.open(firstValue, "_blank", "noopener,noreferrer"); + } else { + window.location.href = firstValue; } + } + const valueInput = el.querySelector( + '[data-scope="select"][data-part="value-input"]' + ); + if (valueInput) { + valueInput.value = + details.value.length === 0 + ? "" + : details.value.length === 1 + ? String(details.value[0]) + : details.value.map(String).join(","); + valueInput.dispatchEvent(new Event("input", { bubbles: true })); + valueInput.dispatchEvent(new Event("change", { bubbles: true })); + } - - - const payload: Record = { - value: details.value, - items: details.items, - id: el.id, - }; - - const encodedJS = el.getAttribute("data-on-value-change-js"); - if (encodedJS) { - let js = encodedJS; - const indexMatches = [...js.matchAll(/__VALUE_(\d+)__/g)].map((m) => parseInt(m[1], 10)); - const uniqueIndices = [...new Set(indexMatches)].sort((a, b) => b - a); - for (const i of uniqueIndices) { - const val = details.value[i]; - const str = val !== undefined && val !== null ? String(val) : ""; - const escaped = JSON.stringify(str).slice(1, -1); - js = js.split(`__VALUE_${i}__`).join(escaped); - } - js = js.split("__VALUE__").join(JSON.stringify(details.value)); - hook.liveSocket.execJS(el, js); - } + const payload: Record = { + value: details.value, + items: details.items, + id: el.id, + }; - const clientEventName = getString(el, "onValueChangeClient"); - if (clientEventName) { - el.dispatchEvent( - new CustomEvent(clientEventName, { bubbles: true, detail: payload }) - ); - } + const clientEventName = getString(el, "onValueChangeClient"); + if (clientEventName) { + el.dispatchEvent(new CustomEvent(clientEventName, { bubbles: true, detail: payload })); + } - const serverEventName = getString(el, "onValueChange"); - if ( - serverEventName && - !hook.liveSocket.main.isDead && - hook.liveSocket.main.isConnected() - ) { - this.pushEvent(serverEventName, payload); - } - }, - } as Props - ); + const serverEventName = getString(el, "onValueChange"); + if (serverEventName && !this.liveSocket.main.isDead && this.liveSocket.main.isConnected()) { + this.pushEvent(serverEventName, payload); + } + }, + } as Props); selectComponent.hasGroups = hasGroups; selectComponent.setOptions(allItems); selectComponent.init(); @@ -144,19 +151,15 @@ const SelectHook: Hook = { this.handlers = []; }, - beforeUpdate(this: object & HookInterface & SelectHookState) { - this.wasFocused = this.select?.api?.focused ?? false; - }, - updated(this: object & HookInterface & SelectHookState) { - const newCollection = JSON.parse(this.el.dataset.collection || "[]"); - const hasGroups = newCollection.some((item: any) => item.group !== undefined); + const newItems = JSON.parse(this.el.dataset.collection || "[]") as SelectItem[]; + const hasGroups = newItems.some((item: SelectItem) => item.group !== undefined); if (this.select) { this.select.hasGroups = hasGroups; - this.select.setOptions(newCollection); - + this.select.setOptions(newItems); this.select.updateProps({ + collection: buildCollection(newItems, hasGroups), id: this.el.id, ...(getBoolean(this.el, "controlled") ? { value: getStringList(this.el, "value") } @@ -170,20 +173,9 @@ const SelectHook: Hook = { required: getBoolean(this.el, "required"), readOnly: getBoolean(this.el, "readOnly"), } as Props); - - if (getBoolean(this.el, "controlled")) { - if (this.wasFocused) { - const trigger = this.el.querySelector('[data-scope="select"][data-part="trigger"]') as HTMLElement; - if (trigger && document.activeElement !== trigger) { - trigger.focus(); - } - } - } } }, - - destroyed(this: object & HookInterface & SelectHookState) { if (this.handlers) { for (const handler of this.handlers) { @@ -195,4 +187,4 @@ const SelectHook: Hook = { }, }; -export { SelectHook as Select }; \ No newline at end of file +export { SelectHook as Select }; diff --git a/assets/hooks/signature-pad.ts b/assets/hooks/signature-pad.ts index 9a83231..c200b8f 100644 --- a/assets/hooks/signature-pad.ts +++ b/assets/hooks/signature-pad.ts @@ -5,7 +5,7 @@ import type { Props } from "@zag-js/signature-pad"; import { getString, getBoolean, getNumber } from "../lib/util"; -function getPaths(el: HTMLElement, attr: string): any[] { +function getPaths(el: HTMLElement, attr: string): unknown[] { const value = el.dataset[attr]; if (!value) return []; try { @@ -49,7 +49,9 @@ const SignaturePadHook: Hook = { drawing: buildDrawingOptions(el), onDrawEnd: (details) => { signaturePad.setPaths(details.paths); - const hiddenInput = el.querySelector('[data-scope="signature-pad"][data-part="hidden-input"]'); + const hiddenInput = el.querySelector( + '[data-scope="signature-pad"][data-part="hidden-input"]' + ); if (hiddenInput) { hiddenInput.value = JSON.stringify(details.paths); } @@ -87,7 +89,9 @@ const SignaturePadHook: Hook = { const initialPaths = controlled ? paths : defaultPaths; if (initialPaths.length > 0) { - const hiddenInput = el.querySelector('[data-scope="signature-pad"][data-part="hidden-input"]'); + const hiddenInput = el.querySelector( + '[data-scope="signature-pad"][data-part="hidden-input"]' + ); if (hiddenInput) { hiddenInput.dispatchEvent(new Event("input", { bubbles: true })); hiddenInput.dispatchEvent(new Event("change", { bubbles: true })); @@ -100,7 +104,9 @@ const SignaturePadHook: Hook = { signaturePad.api.clear(); signaturePad.imageURL = ""; signaturePad.setPaths([]); - const hiddenInput = el.querySelector('[data-scope="signature-pad"][data-part="hidden-input"]'); + const hiddenInput = el.querySelector( + '[data-scope="signature-pad"][data-part="hidden-input"]' + ); if (hiddenInput) { hiddenInput.value = ""; } @@ -116,13 +122,14 @@ const SignaturePadHook: Hook = { signaturePad.api.clear(); signaturePad.imageURL = ""; signaturePad.setPaths([]); - const hiddenInput = el.querySelector('[data-scope="signature-pad"][data-part="hidden-input"]'); + const hiddenInput = el.querySelector( + '[data-scope="signature-pad"][data-part="hidden-input"]' + ); if (hiddenInput) { hiddenInput.value = ""; } }) ); - }, updated(this: object & HookInterface & SignaturePadHookState) { @@ -130,11 +137,11 @@ const SignaturePadHook: Hook = { const paths = getPaths(this.el, "paths"); const defaultPaths = getPaths(this.el, "defaultPaths"); const name = getString(this.el, "name"); - + if (name) { this.signaturePad?.setName(name); } - + this.signaturePad?.updateProps({ id: this.el.id, name: name, diff --git a/assets/hooks/tabs.ts b/assets/hooks/tabs.ts index a752f2c..fa580da 100644 --- a/assets/hooks/tabs.ts +++ b/assets/hooks/tabs.ts @@ -14,65 +14,62 @@ type TabsHookState = { const TabsHook: Hook = { mounted(this: object & HookInterface & TabsHookState) { - const el = this.el; const pushEvent = this.pushEvent.bind(this); - const tabs = new Tabs(el, - { - id: el.id, - ...(getBoolean(el, "controlled") - ? { value: getString(el, "value") } - : { defaultValue: getString(el, "defaultValue") }), - orientation: getString(el, "orientation", ["horizontal", "vertical"]), - dir: getString(el, "dir", ["ltr", "rtl"]), - onValueChange: (details: ValueChangeDetails) => { - const eventName = getString(el, "onValueChange"); - if (eventName && this.liveSocket.main.isConnected()) { - pushEvent(eventName, { - id: el.id, - value: details.value ?? null, - }); - } - - const eventNameClient = getString(el, "onValueChangeClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - value: details.value ?? null, - }, - }) - ); - } - }, - - onFocusChange: (details: FocusChangeDetails) => { - const eventName = getString(el, "onFocusChange"); - if (eventName && this.liveSocket.main.isConnected()) { - pushEvent(eventName, { - id: el.id, - value: details.focusedValue ?? null, - }); - } - - const eventNameClient = getString(el, "onFocusChangeClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - value: details.focusedValue ?? null, - }, - }) - ); - } - }, - } - ); + const tabs = new Tabs(el, { + id: el.id, + ...(getBoolean(el, "controlled") + ? { value: getString(el, "value") } + : { defaultValue: getString(el, "defaultValue") }), + orientation: getString(el, "orientation", ["horizontal", "vertical"]), + dir: getString(el, "dir", ["ltr", "rtl"]), + onValueChange: (details: ValueChangeDetails) => { + const eventName = getString(el, "onValueChange"); + if (eventName && this.liveSocket.main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.value ?? null, + }); + } + + const eventNameClient = getString(el, "onValueChangeClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.value ?? null, + }, + }) + ); + } + }, + + onFocusChange: (details: FocusChangeDetails) => { + const eventName = getString(el, "onFocusChange"); + if (eventName && this.liveSocket.main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.focusedValue ?? null, + }); + } + + const eventNameClient = getString(el, "onFocusChangeClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.focusedValue ?? null, + }, + }) + ); + } + }, + }); tabs.init(); this.tabs = tabs; @@ -85,14 +82,11 @@ const TabsHook: Hook = { this.handlers = []; this.handlers.push( - this.handleEvent( - "tabs_set_value", - (payload: { tabs_id?: string; value: string }) => { - const targetId = payload.tabs_id; - if (targetId && targetId !== el.id) return; - tabs.api.setValue(payload.value); - } - ) + this.handleEvent("tabs_set_value", (payload: { tabs_id?: string; value: string }) => { + const targetId = payload.tabs_id; + if (targetId && targetId !== el.id) return; + tabs.api.setValue(payload.value); + }) ); this.handlers.push( @@ -119,12 +113,10 @@ const TabsHook: Hook = { ? { value: getString(this.el, "value") } : { defaultValue: getString(this.el, "defaultValue") }), orientation: getString(this.el, "orientation", ["horizontal", "vertical"]), - dir: getString(this.el, "dir", ["ltr", "rtl"]) + dir: getString(this.el, "dir", ["ltr", "rtl"]), } as Props); }, - - destroyed(this: object & HookInterface & TabsHookState) { if (this.onSetValue) { this.el.removeEventListener("phx:tabs:set-value", this.onSetValue); diff --git a/assets/hooks/toast.ts b/assets/hooks/toast.ts index 1af7d43..7bddccd 100644 --- a/assets/hooks/toast.ts +++ b/assets/hooks/toast.ts @@ -48,10 +48,15 @@ const ToastHook: Hook = { return duration; }; - const placement = getString(el, "placement", [ - "top-start", "top", "top-end", - "bottom-start", "bottom", "bottom-end", - ]) ?? "bottom-end"; + const placement = + getString(el, "placement", [ + "top-start", + "top", + "top-end", + "bottom-start", + "bottom", + "bottom-end", + ]) ?? "bottom-end"; createToastGroup(el, { id: this.groupId, @@ -167,7 +172,6 @@ const ToastHook: Hook = { console.error("Failed to create toast:", error); } }) as EventListener); - }, destroyed(this: object & HookInterface & ToastHookState) { @@ -179,4 +183,4 @@ const ToastHook: Hook = { }, }; -export { ToastHook as Toast }; \ No newline at end of file +export { ToastHook as Toast }; diff --git a/assets/hooks/toggle-group.ts b/assets/hooks/toggle-group.ts index 5f11ea8..f3966dd 100644 --- a/assets/hooks/toggle-group.ts +++ b/assets/hooks/toggle-group.ts @@ -19,8 +19,8 @@ const ToggleGroupHook: Hook = { const props: Props = { id: el.id, ...(getBoolean(el, "controlled") - ? { value: getStringList(el, "value") } - : { defaultValue: getStringList(el, "defaultValue") }), + ? { value: getStringList(el, "value") } + : { defaultValue: getStringList(el, "defaultValue") }), defaultValue: getStringList(el, "defaultValue"), deselectable: getBoolean(el, "deselectable"), loopFocus: getBoolean(el, "loopFocus"), @@ -66,14 +66,11 @@ const ToggleGroupHook: Hook = { this.handlers = []; this.handlers.push( - this.handleEvent( - "toggle-group_set_value", - (payload: { id?: string; value: string[] }) => { - const targetId = payload.id; - if (targetId && targetId !== el.id) return; - toggleGroup.api.setValue(payload.value); - } - ) + this.handleEvent("toggle-group_set_value", (payload: { id?: string; value: string[] }) => { + const targetId = payload.id; + if (targetId && targetId !== el.id) return; + toggleGroup.api.setValue(payload.value); + }) ); this.handlers.push( @@ -88,8 +85,8 @@ const ToggleGroupHook: Hook = { updated(this: object & HookInterface & ToggleGroupHookState) { this.toggleGroup?.updateProps({ ...(getBoolean(this.el, "controlled") - ? { value: getStringList(this.el, "value") } - : { defaultValue: getStringList(this.el, "defaultValue") }), + ? { value: getStringList(this.el, "value") } + : { defaultValue: getStringList(this.el, "defaultValue") }), deselectable: getBoolean(this.el, "deselectable"), loopFocus: getBoolean(this.el, "loopFocus"), rovingFocus: getBoolean(this.el, "rovingFocus"), diff --git a/assets/hooks/tree-view.ts b/assets/hooks/tree-view.ts index fccf740..b77e011 100644 --- a/assets/hooks/tree-view.ts +++ b/assets/hooks/tree-view.ts @@ -26,15 +26,12 @@ const TreeViewHook: Hook = { defaultExpandedValue: getStringList(el, "defaultExpandedValue"), defaultSelectedValue: getStringList(el, "defaultSelectedValue"), }), - selectionMode: getString< - "single" | "multiple" - >(el, "selectionMode", ["single", "multiple"]) ?? "single", + selectionMode: + getString<"single" | "multiple">(el, "selectionMode", ["single", "multiple"]) ?? "single", dir: getDir(el), onSelectionChange: (details) => { const redirect = getBoolean(el, "redirect"); - const value = details.selectedValue?.length - ? details.selectedValue[0] - : undefined; + const value = details.selectedValue?.length ? details.selectedValue[0] : undefined; const itemEl = [ ...el.querySelectorAll( '[data-scope="tree-view"][data-part="item"], [data-scope="tree-view"][data-part="branch"]' @@ -44,11 +41,7 @@ const TreeViewHook: Hook = { const itemRedirect = itemEl?.getAttribute("data-redirect"); const itemNewTab = itemEl?.hasAttribute("data-new-tab"); const doRedirect = - redirect && - value && - isItem && - this.liveSocket.main.isDead && - itemRedirect !== "false"; + redirect && value && isItem && this.liveSocket.main.isDead && itemRedirect !== "false"; if (doRedirect) { if (itemNewTab) { window.open(value, "_blank", "noopener,noreferrer"); diff --git a/assets/lib/util.ts b/assets/lib/util.ts index 2f17f76..01f8d1e 100644 --- a/assets/lib/util.ts +++ b/assets/lib/util.ts @@ -95,4 +95,3 @@ export const generateId = (element?: HTMLElement, fallbackId: string = "element" if (element?.id) return element.id; return `${fallbackId}-${Math.random().toString(36).substring(2, 9)}`; }; - diff --git a/e2e/assets/corex/components/dialog.css b/e2e/assets/corex/components/dialog.css index ca66b1d..202e30c 100644 --- a/e2e/assets/corex/components/dialog.css +++ b/e2e/assets/corex/components/dialog.css @@ -41,6 +41,17 @@ } + .dialog [data-scope="dialog"][data-part="header"] { + display: flex; + align-items: center; + gap: var(--spacing-ui-gap); + } + + .dialog [data-scope="dialog"][data-part="header"] [data-scope="dialog"][data-part="title"] { + flex: 1 1 0%; + min-width: 0; + } + .dialog [data-part="row"] { display: inline-flex; flex-direction: row; diff --git a/e2e/assets/corex/components/toast.css b/e2e/assets/corex/components/toast.css index 8889310..c1d5bfa 100644 --- a/e2e/assets/corex/components/toast.css +++ b/e2e/assets/corex/components/toast.css @@ -5,11 +5,12 @@ display: flex; flex-direction: column; gap: var(--spacing-ui-gap); - width: var(--container-ui); + width: var(--container-mini); } .toast [data-scope="toast"][data-part="root"] { @apply ui-root; + max-width: var(--container-mini); translate: var(--x) var(--y); scale: var(--scale); diff --git a/e2e/lib/e2e_web/controllers/page_html.ex b/e2e/lib/e2e_web/controllers/page_html.ex index b004e7b..2651896 100644 --- a/e2e/lib/e2e_web/controllers/page_html.ex +++ b/e2e/lib/e2e_web/controllers/page_html.ex @@ -7,4 +7,52 @@ defmodule E2eWeb.PageHTML do use E2eWeb, :html embed_templates "page_html/*" + + def menu_items(locale) do + Corex.Tree.new([ + [ + label: "Accordion", + id: "accordion", + children: [ + [label: "Controller", id: "/#{locale}/accordion"], + [label: "Live", id: "/#{locale}/live/accordion"], + [label: "Playground", id: "/#{locale}/playground/accordion"], + [label: "Controlled", id: "/#{locale}/controlled/accordion"], + [label: "Async", id: "/#{locale}/async/accordion"] + ] + ], + [ + label: "Checkbox", + id: "checkbox", + children: [ + [label: "Controller", id: "/#{locale}/checkbox"], + [label: "Live", id: "/#{locale}/live/checkbox"] + ] + ], + [ + label: "Clipboard", + id: "clipboard", + children: [ + [label: "Controller", id: "/#{locale}/clipboard"], + [label: "Live", id: "/#{locale}/live/clipboard"] + ] + ], + [ + label: "Collapsible", + id: "collapsible", + children: [ + [label: "Controller", id: "/#{locale}/collapsible"], + [label: "Live", id: "/#{locale}/live/collapsible"] + ] + ], + [ + label: "Combobox", + id: "combobox", + children: [ + [label: "Controller", id: "/#{locale}/combobox"], + [label: "Live", id: "/#{locale}/live/combobox"] + ] + ] + ]) + end end diff --git a/e2e/lib/e2e_web/controllers/page_html/menu_page.html.heex b/e2e/lib/e2e_web/controllers/page_html/menu_page.html.heex index bc20911..82b049c 100644 --- a/e2e/lib/e2e_web/controllers/page_html/menu_page.html.heex +++ b/e2e/lib/e2e_web/controllers/page_html/menu_page.html.heex @@ -1,7 +1,6 @@

Menu

Controller View

-

Client Api

<.menu class="menu" @@ -99,28 +98,7 @@ <.menu class="menu" redirect - items={[ - %Corex.Tree.Item{ - id: "accordion", - label: "Accordion", - group: "Components" - }, - %Corex.Tree.Item{ - id: "checkbox", - label: "Checkbox", - group: "Components" - }, - %Corex.Tree.Item{ - id: "users", - label: "Controller", - group: "Phoenix Form" - }, - %Corex.Tree.Item{ - id: "admins", - label: "Live View", - group: "Phoenix Form" - } - ]} + items={menu_items(@locale)} > <:trigger>Corex <:indicator> diff --git a/e2e/lib/e2e_web/live/menu_live.ex b/e2e/lib/e2e_web/live/menu_live.ex index e411354..95445be 100644 --- a/e2e/lib/e2e_web/live/menu_live.ex +++ b/e2e/lib/e2e_web/live/menu_live.ex @@ -9,9 +9,10 @@ defmodule E2eWeb.MenuLive do {:noreply, Corex.Menu.set_open(socket, "my-menu", value == "true")} end - # def handle_event("handle_on_select", %{"id" => _id, "value" => value}, socket) do - # {:noreply, push_navigate(socket, to: "/#{value}")} - # end + def handle_event("handle_on_select", %{"id" => _id, "value" => value}, socket) do + IO.inspect(value, label: "value") + {:noreply, push_navigate(socket, to: "#{value}")} + end def render(assigns) do ~H""" @@ -48,7 +49,160 @@ defmodule E2eWeb.MenuLive do Close menu + + <.menu + class="menu" + id="my-menu" + items={[ + %Corex.Tree.Item{ + id: "edit", + label: "Edit" + }, + %Corex.Tree.Item{ + id: "duplicate", + label: "Duplicate" + }, + %Corex.Tree.Item{ + id: "delete", + label: "Delete" + } + ]} + > + <:trigger>File + <:indicator> + <.icon name="hero-chevron-down" /> + + + <.menu + class="menu" + items={[ + %Corex.Tree.Item{ + id: "new-tab", + label: "New tab" + }, + %Corex.Tree.Item{ + id: "share", + label: "Share", + children: [ + %Corex.Tree.Item{ + id: "messages", + label: "Messages" + }, + %Corex.Tree.Item{ + id: "airdrop", + label: "Airdrop" + }, + %Corex.Tree.Item{ + id: "whatsapp", + label: "WhatsApp" + } + ] + }, + %Corex.Tree.Item{ + id: "print", + label: "Print..." + } + ]} + > + <:trigger>Menu + <:indicator> + <.icon name="hero-chevron-down" /> + + <:nested_indicator> + <.icon name="hero-chevron-right" /> + + + + <.menu + class="menu" + items={[ + %Corex.Tree.Item{ + id: "edit", + label: "Edit", + group: "Actions" + }, + %Corex.Tree.Item{ + id: "duplicate", + label: "Duplicate", + group: "Actions" + }, + %Corex.Tree.Item{ + id: "account-1", + label: "Account 1", + group: "Accounts" + }, + %Corex.Tree.Item{ + id: "account-2", + label: "Account 2", + group: "Accounts" + } + ]} + > + <:trigger>Actions + <:indicator> + <.icon name="hero-chevron-down" /> + + + + <.menu + class="menu" + redirect + on_select="handle_on_select" + items={menu_items(@locale)} + > + <:trigger>Corex + <:indicator> + <.icon name="hero-chevron-down" /> + + <:nested_indicator> + <.icon name="hero-chevron-right" /> + + <:item :let={item}> + {item.label} + +
""" end + + defp menu_items(locale) do + Corex.Tree.new([ + [ + label: "Accordion", + id: "/#{locale}/accordion" + ], + [ + label: "Checkbox", + id: "checkbox", + children: [ + [label: "Controller", id: "/#{locale}/checkbox"], + [label: "Live", id: "/#{locale}/live/checkbox"] + ] + ], + [ + label: "Clipboard", + id: "clipboard", + children: [ + [label: "Controller", id: "/#{locale}/clipboard"], + [label: "Live", id: "/#{locale}/live/clipboard"] + ] + ], + [ + label: "Collapsible", + id: "collapsible", + children: [ + [label: "Controller", id: "/#{locale}/collapsible"], + [label: "Live", id: "/#{locale}/live/collapsible"] + ] + ], + [ + label: "Combobox", + id: "combobox", + children: [ + [label: "Controller", id: "/#{locale}/combobox"], + [label: "Live", id: "/#{locale}/live/combobox"] + ] + ] + ]) + end end diff --git a/e2e/mix.exs b/e2e/mix.exs index ede3dd9..a7e3d41 100644 --- a/e2e/mix.exs +++ b/e2e/mix.exs @@ -67,7 +67,7 @@ defmodule E2e.MixProject do {:jason, "~> 1.2"}, {:dns_cluster, "~> 0.2.0"}, {:bandit, "~> 1.5"}, - {:corex, "~> 0.1.0-alpha.21"}, + {:corex, "~> 0.1.0-alpha.22"}, {:wallaby, "~> 0.30", only: :test}, {:a11y_audit, "~> 0.3.1", only: :test}, {:flagpack, "~> 0.6.0"} diff --git a/e2e/mix.lock b/e2e/mix.lock index 2f30c7b..32d9b3a 100644 --- a/e2e/mix.lock +++ b/e2e/mix.lock @@ -3,7 +3,7 @@ "bandit": {:hex, :bandit, "1.10.2", "d15ea32eb853b5b42b965b24221eb045462b2ba9aff9a0bda71157c06338cbff", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "27b2a61b647914b1726c2ced3601473be5f7aa6bb468564a688646a689b3ee45"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, - "corex": {:hex, :corex, "0.1.0-alpha.21", "6a5fb25e5d4dd8ba7b9d970870980b02ea9a5819dd5f4d7fccf1ed94a05897f8", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, "~> 1.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.8.1", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "ed5471578d9e389256d798a9f8a941cd38a291b11c2cdc133586485b619d111a"}, + "corex": {:hex, :corex, "0.1.0-alpha.22", "230d48f258f9348b197d721e23d285b546c5fc4b547988d939b011128cffd00d", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, "~> 1.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.8.1", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "dab72286c92b1f7b9316fbb37f28f2cadd32f05ce95aad3c1909a63bc37971ee"}, "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, diff --git a/e2e/test/components/menu_test.exs b/e2e/test/components/menu_test.exs new file mode 100644 index 0000000..8870284 --- /dev/null +++ b/e2e/test/components/menu_test.exs @@ -0,0 +1,16 @@ +defmodule E2eWeb.MenuTest do + use ExUnit.Case, async: true + use Wallaby.Feature + + alias E2eWeb.MenuModel, as: Menu + + for mode <- [:static, :live] do + @mode mode + + feature "#{@mode} - Menu has no A11y violations", %{session: session} do + session + |> Menu.goto(@mode) + |> Menu.check_accessibility() + end + end +end diff --git a/e2e/test/components/tree_view_test.exs b/e2e/test/components/tree_view_test.exs new file mode 100644 index 0000000..a0ceace --- /dev/null +++ b/e2e/test/components/tree_view_test.exs @@ -0,0 +1,16 @@ +defmodule E2eWeb.TreeViewTest do + use ExUnit.Case, async: true + use Wallaby.Feature + + alias E2eWeb.TreeViewModel, as: TreeView + + for mode <- [:static, :live] do + @mode mode + + feature "#{@mode} - TreeView has no A11y violations", %{session: session} do + session + |> TreeView.goto(@mode) + |> TreeView.check_accessibility() + end + end +end diff --git a/e2e/test/support/models/menu_model.ex b/e2e/test/support/models/menu_model.ex new file mode 100644 index 0000000..11b8f83 --- /dev/null +++ b/e2e/test/support/models/menu_model.ex @@ -0,0 +1,3 @@ +defmodule E2eWeb.MenuModel do + use E2eWeb.Model, component: "menu" +end diff --git a/e2e/test/support/models/tree_view_model.ex b/e2e/test/support/models/tree_view_model.ex new file mode 100644 index 0000000..528bc30 --- /dev/null +++ b/e2e/test/support/models/tree_view_model.ex @@ -0,0 +1,3 @@ +defmodule E2eWeb.TreeViewModel do + use E2eWeb.Model, component: "tree-view" +end diff --git a/eslint.config.mjs b/eslint.config.mjs index e47d862..ee69562 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -19,6 +19,7 @@ export default [ globals: { window: "readonly", document: "readonly", + Document: "readonly", console: "readonly", navigator: "readonly", HTMLElement: "readonly", @@ -28,7 +29,12 @@ export default [ CustomEvent: "readonly", EventListener: "readonly", HTMLInputElement: "readonly", - + HTMLSelectElement: "readonly", + HTMLFormElement: "readonly", + SVGSVGElement: "readonly", + SVGRectElement: "readonly", + requestAnimationFrame: "readonly", + setTimeout: "readonly", }, }, plugins: { diff --git a/guides/installation.md b/guides/installation.md index c9942ac..244e618 100644 --- a/guides/installation.md +++ b/guides/installation.md @@ -37,7 +37,7 @@ Add `corex` to your `mix.exs` dependencies: ```elixir def deps do [ - {:corex, "~> 0.1.0-alpha.21"} + {:corex, "~> 0.1.0-alpha.22"} ] end ``` diff --git a/lib/components/dialog.ex b/lib/components/dialog.ex index f8d2c72..0d85539 100644 --- a/lib/components/dialog.ex +++ b/lib/components/dialog.ex @@ -255,16 +255,18 @@ defmodule Corex.Dialog do
-

+
+

{render_slot(@title)}

+ +

{render_slot(@description)}

{render_slot(@content)} -

diff --git a/lib/components/menu.ex b/lib/components/menu.ex index 19aebb2..77cfb47 100644 --- a/lib/components/menu.ex +++ b/lib/components/menu.ex @@ -383,7 +383,7 @@ defmodule Corex.Menu do on_open_change: @on_open_change, on_open_change_client: @on_open_change_client })}> -
+
@@ -565,29 +554,27 @@ defmodule Corex.Select do
{render_slot(@error, msg)}
-
+
    -
- -
-
+
  • (a || "") <= (b || "") end)} data-scope="select" data-part="item-group" data-id={group || "default"}>
    {group}
    -
  • - - {render_slot(@item, item)} - - - {item.label} - - - {render_slot(@item_indicator)} - -
  • -
    - -
  • +
      +
    • + + {render_slot(@item, item)} + + + {item.label} + + + {render_slot(@item_indicator)} + +
    • +
    +
  • +
  • {render_slot(@item, item)} @@ -598,7 +585,7 @@ defmodule Corex.Select do {render_slot(@item_indicator)}
  • -
    +
    diff --git a/lib/components/select/anatomy.ex b/lib/components/select/anatomy.ex index e5ae3ba..9725c97 100644 --- a/lib/components/select/anatomy.ex +++ b/lib/components/select/anatomy.ex @@ -24,7 +24,6 @@ defmodule Corex.Select.Anatomy do required: false, on_value_change: nil, on_value_change_client: nil, - on_value_change_js: nil, redirect: false, redirect_new_tab: false, positioning: nil @@ -48,7 +47,6 @@ defmodule Corex.Select.Anatomy do required: boolean(), on_value_change: String.t() | nil, on_value_change_client: String.t() | nil, - on_value_change_js: String.t() | nil, redirect: boolean(), positioning: Corex.Positioning.t() | nil } diff --git a/lib/components/select/connect.ex b/lib/components/select/connect.ex index 90f7199..e92ec78 100644 --- a/lib/components/select/connect.ex +++ b/lib/components/select/connect.ex @@ -44,7 +44,6 @@ defmodule Corex.Select.Connect do base |> maybe_put("data-on-value-change", assigns.on_value_change) |> maybe_put("data-on-value-change-client", assigns.on_value_change_client) - |> maybe_put("data-on-value-change-js", assigns.on_value_change_js) |> maybe_put("data-redirect", get_boolean(assigns.redirect)) end @@ -94,7 +93,8 @@ defmodule Corex.Select.Connect do "data-scope" => "select", "data-part" => "positioner", "dir" => assigns.dir, - "style" => "display: none;", + "style" => + "position:fixed;isolation:isolate;width:var(--reference-width);pointer-events:none;top:0px;left:0px;transform:translate3d(0, -100vh, 0);z-index:var(--z-index);", "id" => "select:#{assigns.id}:positioner" } end diff --git a/mix.exs b/mix.exs index f693699..cd083ff 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Corex.MixProject do use Mix.Project - @version "0.1.0-alpha.21" + @version "0.1.0-alpha.22" @elixir_requirement "~> 1.15" def project do diff --git a/package.json b/package.json index 48992f8..3c1c3dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "corex", - "version": "0.1.0-alpha.21", + "version": "0.1.0-alpha.22", "description": "The official JavaScript client for the Corex Phoenix Hooks.", "license": "MIT", "module": "./priv/static/corex.mjs", diff --git a/priv/design/components/dialog.css b/priv/design/components/dialog.css index ca66b1d..202e30c 100644 --- a/priv/design/components/dialog.css +++ b/priv/design/components/dialog.css @@ -41,6 +41,17 @@ } + .dialog [data-scope="dialog"][data-part="header"] { + display: flex; + align-items: center; + gap: var(--spacing-ui-gap); + } + + .dialog [data-scope="dialog"][data-part="header"] [data-scope="dialog"][data-part="title"] { + flex: 1 1 0%; + min-width: 0; + } + .dialog [data-part="row"] { display: inline-flex; flex-direction: row; diff --git a/priv/design/components/toast.css b/priv/design/components/toast.css index 8889310..c1d5bfa 100644 --- a/priv/design/components/toast.css +++ b/priv/design/components/toast.css @@ -5,11 +5,12 @@ display: flex; flex-direction: column; gap: var(--spacing-ui-gap); - width: var(--container-ui); + width: var(--container-mini); } .toast [data-scope="toast"][data-part="root"] { @apply ui-root; + max-width: var(--container-mini); translate: var(--x) var(--y); scale: var(--scale); diff --git a/priv/static/accordion.mjs b/priv/static/accordion.mjs index b575e60..c16d972 100644 --- a/priv/static/accordion.mjs +++ b/priv/static/accordion.mjs @@ -354,15 +354,21 @@ var Accordion = class extends Component { if (!itemData?.value) continue; const { value, disabled } = itemData; this.spreadProps(itemEl, this.api.getItemProps({ value, disabled })); - const triggerEl = itemEl.querySelector('[data-scope="accordion"][data-part="item-trigger"]'); + const triggerEl = itemEl.querySelector( + '[data-scope="accordion"][data-part="item-trigger"]' + ); if (triggerEl) { this.spreadProps(triggerEl, this.api.getItemTriggerProps({ value, disabled })); } - const indicatorEl = itemEl.querySelector('[data-scope="accordion"][data-part="item-indicator"]'); + const indicatorEl = itemEl.querySelector( + '[data-scope="accordion"][data-part="item-indicator"]' + ); if (indicatorEl) { this.spreadProps(indicatorEl, this.api.getItemIndicatorProps({ value, disabled })); } - const contentEl = itemEl.querySelector('[data-scope="accordion"][data-part="item-content"]'); + const contentEl = itemEl.querySelector( + '[data-scope="accordion"][data-part="item-content"]' + ); if (contentEl) { this.spreadProps(contentEl, this.api.getItemContentProps({ value, disabled })); } @@ -384,59 +390,56 @@ var AccordionHook = { mounted() { const el = this.el; const pushEvent = this.pushEvent.bind(this); - const accordion = new Accordion( - el, - { - id: el.id, - ...getBoolean(el, "controlled") ? { value: getStringList(el, "value") } : { defaultValue: getStringList(el, "defaultValue") }, - collapsible: getBoolean(el, "collapsible"), - multiple: getBoolean(el, "multiple"), - orientation: getString(el, "orientation", ["horizontal", "vertical"]), - dir: getDir(el), - onValueChange: (details) => { - const eventName = getString(el, "onValueChange"); - if (eventName && this.liveSocket.main.isConnected()) { - pushEvent(eventName, { - id: el.id, - value: details.value ?? null - }); - } - const eventNameClient = getString(el, "onValueChangeClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - value: details.value ?? null - } - }) - ); - } - }, - onFocusChange: (details) => { - const eventName = getString(el, "onFocusChange"); - if (eventName && this.liveSocket.main.isConnected()) { - pushEvent(eventName, { - id: el.id, - value: details.value ?? null - }); - } - const eventNameClient = getString(el, "onFocusChangeClient"); - if (eventNameClient) { - el.dispatchEvent( - new CustomEvent(eventNameClient, { - bubbles: true, - detail: { - id: el.id, - value: details.value ?? null - } - }) - ); - } + const accordion = new Accordion(el, { + id: el.id, + ...getBoolean(el, "controlled") ? { value: getStringList(el, "value") } : { defaultValue: getStringList(el, "defaultValue") }, + collapsible: getBoolean(el, "collapsible"), + multiple: getBoolean(el, "multiple"), + orientation: getString(el, "orientation", ["horizontal", "vertical"]), + dir: getDir(el), + onValueChange: (details) => { + const eventName = getString(el, "onValueChange"); + if (eventName && this.liveSocket.main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.value ?? null + }); + } + const eventNameClient = getString(el, "onValueChangeClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.value ?? null + } + }) + ); + } + }, + onFocusChange: (details) => { + const eventName = getString(el, "onFocusChange"); + if (eventName && this.liveSocket.main.isConnected()) { + pushEvent(eventName, { + id: el.id, + value: details.value ?? null + }); + } + const eventNameClient = getString(el, "onFocusChangeClient"); + if (eventNameClient) { + el.dispatchEvent( + new CustomEvent(eventNameClient, { + bubbles: true, + detail: { + id: el.id, + value: details.value ?? null + } + }) + ); } } - ); + }); accordion.init(); this.accordion = accordion; this.onSetValue = (event) => { diff --git a/priv/static/checkbox.mjs b/priv/static/checkbox.mjs index e3757bd..74942be 100644 --- a/priv/static/checkbox.mjs +++ b/priv/static/checkbox.mjs @@ -316,18 +316,26 @@ var Checkbox = class extends Component { const rootEl = this.el.querySelector('[data-scope="checkbox"][data-part="root"]'); if (!rootEl) return; this.spreadProps(rootEl, this.api.getRootProps()); - const inputEl = rootEl.querySelector(':scope > [data-scope="checkbox"][data-part="hidden-input"]'); + const inputEl = rootEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="hidden-input"]' + ); if (inputEl) { this.spreadProps(inputEl, this.api.getHiddenInputProps()); } - const labelEl = rootEl.querySelector(':scope > [data-scope="checkbox"][data-part="label"]'); + const labelEl = rootEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="label"]' + ); if (labelEl) { this.spreadProps(labelEl, this.api.getLabelProps()); } - const controlEl = rootEl.querySelector(':scope > [data-scope="checkbox"][data-part="control"]'); + const controlEl = rootEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="control"]' + ); if (controlEl) { this.spreadProps(controlEl, this.api.getControlProps()); - const indicatorEl = controlEl.querySelector(':scope > [data-scope="checkbox"][data-part="indicator"]'); + const indicatorEl = controlEl.querySelector( + ':scope > [data-scope="checkbox"][data-part="indicator"]' + ); if (indicatorEl) { this.spreadProps(indicatorEl, this.api.getIndicatorProps()); } diff --git a/priv/static/chunk-GYNMIQP2.mjs b/priv/static/chunk-GYNMIQP2.mjs new file mode 100644 index 0000000..1778365 --- /dev/null +++ b/priv/static/chunk-GYNMIQP2.mjs @@ -0,0 +1,517 @@ +import { + addDomEvent, + callAll, + contains, + getDocument, + getEventTarget, + getNearestOverflowAncestor, + getWindow, + isContextMenuEvent, + isControlledElement, + isFocusable, + isFunction, + isHTMLElement, + isShadowRoot, + isTouchDevice, + nextTick, + raf, + setStyle, + waitForElement, + warn +} from "./chunk-ZTKS6RKJ.mjs"; + +// ../node_modules/.pnpm/@zag-js+interact-outside@1.33.1/node_modules/@zag-js/interact-outside/dist/index.mjs +function getWindowFrames(win) { + const frames = { + each(cb) { + for (let i = 0; i < win.frames?.length; i += 1) { + const frame = win.frames[i]; + if (frame) cb(frame); + } + }, + addEventListener(event, listener, options) { + frames.each((frame) => { + try { + frame.document.addEventListener(event, listener, options); + } catch { + } + }); + return () => { + try { + frames.removeEventListener(event, listener, options); + } catch { + } + }; + }, + removeEventListener(event, listener, options) { + frames.each((frame) => { + try { + frame.document.removeEventListener(event, listener, options); + } catch { + } + }); + } + }; + return frames; +} +function getParentWindow(win) { + const parent = win.frameElement != null ? win.parent : null; + return { + addEventListener: (event, listener, options) => { + try { + parent?.addEventListener(event, listener, options); + } catch { + } + return () => { + try { + parent?.removeEventListener(event, listener, options); + } catch { + } + }; + }, + removeEventListener: (event, listener, options) => { + try { + parent?.removeEventListener(event, listener, options); + } catch { + } + } + }; +} +var POINTER_OUTSIDE_EVENT = "pointerdown.outside"; +var FOCUS_OUTSIDE_EVENT = "focus.outside"; +function isComposedPathFocusable(composedPath) { + for (const node of composedPath) { + if (isHTMLElement(node) && isFocusable(node)) return true; + } + return false; +} +var isPointerEvent = (event) => "clientY" in event; +function isEventPointWithin(node, event) { + if (!isPointerEvent(event) || !node) return false; + const rect = node.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return false; + return rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; +} +function isPointInRect(rect, point) { + return rect.y <= point.y && point.y <= rect.y + rect.height && rect.x <= point.x && point.x <= rect.x + rect.width; +} +function isEventWithinScrollbar(event, ancestor) { + if (!ancestor || !isPointerEvent(event)) return false; + const isScrollableY = ancestor.scrollHeight > ancestor.clientHeight; + const onScrollbarY = isScrollableY && event.clientX > ancestor.offsetLeft + ancestor.clientWidth; + const isScrollableX = ancestor.scrollWidth > ancestor.clientWidth; + const onScrollbarX = isScrollableX && event.clientY > ancestor.offsetTop + ancestor.clientHeight; + const rect = { + x: ancestor.offsetLeft, + y: ancestor.offsetTop, + width: ancestor.clientWidth + (isScrollableY ? 16 : 0), + height: ancestor.clientHeight + (isScrollableX ? 16 : 0) + }; + const point = { + x: event.clientX, + y: event.clientY + }; + if (!isPointInRect(rect, point)) return false; + return onScrollbarY || onScrollbarX; +} +function trackInteractOutsideImpl(node, options) { + const { + exclude, + onFocusOutside, + onPointerDownOutside, + onInteractOutside, + defer, + followControlledElements = true + } = options; + if (!node) return; + const doc = getDocument(node); + const win = getWindow(node); + const frames = getWindowFrames(win); + const parentWin = getParentWindow(win); + function isEventOutside(event, target) { + if (!isHTMLElement(target)) return false; + if (!target.isConnected) return false; + if (contains(node, target)) return false; + if (isEventPointWithin(node, event)) return false; + if (followControlledElements && isControlledElement(node, target)) return false; + const triggerEl = doc.querySelector(`[aria-controls="${node.id}"]`); + if (triggerEl) { + const triggerAncestor = getNearestOverflowAncestor(triggerEl); + if (isEventWithinScrollbar(event, triggerAncestor)) return false; + } + const nodeAncestor = getNearestOverflowAncestor(node); + if (isEventWithinScrollbar(event, nodeAncestor)) return false; + return !exclude?.(target); + } + const pointerdownCleanups = /* @__PURE__ */ new Set(); + const isInShadowRoot = isShadowRoot(node?.getRootNode()); + function onPointerDown(event) { + function handler(clickEvent) { + const func = defer && !isTouchDevice() ? raf : (v) => v(); + const evt = clickEvent ?? event; + const composedPath = evt?.composedPath?.() ?? [evt?.target]; + func(() => { + const target = isInShadowRoot ? composedPath[0] : getEventTarget(event); + if (!node || !isEventOutside(event, target)) return; + if (onPointerDownOutside || onInteractOutside) { + const handler2 = callAll(onPointerDownOutside, onInteractOutside); + node.addEventListener(POINTER_OUTSIDE_EVENT, handler2, { once: true }); + } + fireCustomEvent(node, POINTER_OUTSIDE_EVENT, { + bubbles: false, + cancelable: true, + detail: { + originalEvent: evt, + contextmenu: isContextMenuEvent(evt), + focusable: isComposedPathFocusable(composedPath), + target + } + }); + }); + } + if (event.pointerType === "touch") { + pointerdownCleanups.forEach((fn) => fn()); + pointerdownCleanups.add(addDomEvent(doc, "click", handler, { once: true })); + pointerdownCleanups.add(parentWin.addEventListener("click", handler, { once: true })); + pointerdownCleanups.add(frames.addEventListener("click", handler, { once: true })); + } else { + handler(); + } + } + const cleanups = /* @__PURE__ */ new Set(); + const timer = setTimeout(() => { + cleanups.add(addDomEvent(doc, "pointerdown", onPointerDown, true)); + cleanups.add(parentWin.addEventListener("pointerdown", onPointerDown, true)); + cleanups.add(frames.addEventListener("pointerdown", onPointerDown, true)); + }, 0); + function onFocusin(event) { + const func = defer ? raf : (v) => v(); + func(() => { + const composedPath = event?.composedPath?.() ?? [event?.target]; + const target = isInShadowRoot ? composedPath[0] : getEventTarget(event); + if (!node || !isEventOutside(event, target)) return; + if (onFocusOutside || onInteractOutside) { + const handler = callAll(onFocusOutside, onInteractOutside); + node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true }); + } + fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, { + bubbles: false, + cancelable: true, + detail: { + originalEvent: event, + contextmenu: false, + focusable: isFocusable(target), + target + } + }); + }); + } + if (!isTouchDevice()) { + cleanups.add(addDomEvent(doc, "focusin", onFocusin, true)); + cleanups.add(parentWin.addEventListener("focusin", onFocusin, true)); + cleanups.add(frames.addEventListener("focusin", onFocusin, true)); + } + return () => { + clearTimeout(timer); + pointerdownCleanups.forEach((fn) => fn()); + cleanups.forEach((fn) => fn()); + }; +} +function trackInteractOutside(nodeOrFn, options) { + const { defer } = options; + const func = defer ? raf : (v) => v(); + const cleanups = []; + cleanups.push( + func(() => { + const node = typeof nodeOrFn === "function" ? nodeOrFn() : nodeOrFn; + cleanups.push(trackInteractOutsideImpl(node, options)); + }) + ); + return () => { + cleanups.forEach((fn) => fn?.()); + }; +} +function fireCustomEvent(el, type, init) { + const win = el.ownerDocument.defaultView || window; + const event = new win.CustomEvent(type, init); + return el.dispatchEvent(event); +} + +// ../node_modules/.pnpm/@zag-js+dismissable@1.33.1/node_modules/@zag-js/dismissable/dist/index.mjs +function trackEscapeKeydown(node, fn) { + const handleKeyDown = (event) => { + if (event.key !== "Escape") return; + if (event.isComposing) return; + fn?.(event); + }; + return addDomEvent(getDocument(node), "keydown", handleKeyDown, { capture: true }); +} +var LAYER_REQUEST_DISMISS_EVENT = "layer:request-dismiss"; +var layerStack = { + layers: [], + branches: [], + recentlyRemoved: /* @__PURE__ */ new Set(), + count() { + return this.layers.length; + }, + pointerBlockingLayers() { + return this.layers.filter((layer) => layer.pointerBlocking); + }, + topMostPointerBlockingLayer() { + return [...this.pointerBlockingLayers()].slice(-1)[0]; + }, + hasPointerBlockingLayer() { + return this.pointerBlockingLayers().length > 0; + }, + isBelowPointerBlockingLayer(node) { + const index = this.indexOf(node); + const highestBlockingIndex = this.topMostPointerBlockingLayer() ? this.indexOf(this.topMostPointerBlockingLayer()?.node) : -1; + return index < highestBlockingIndex; + }, + isTopMost(node) { + const layer = this.layers[this.count() - 1]; + return layer?.node === node; + }, + getNestedLayers(node) { + return Array.from(this.layers).slice(this.indexOf(node) + 1); + }, + getLayersByType(type) { + return this.layers.filter((layer) => layer.type === type); + }, + getNestedLayersByType(node, type) { + const index = this.indexOf(node); + if (index === -1) return []; + return this.layers.slice(index + 1).filter((layer) => layer.type === type); + }, + getParentLayerOfType(node, type) { + const index = this.indexOf(node); + if (index <= 0) return void 0; + return this.layers.slice(0, index).reverse().find((layer) => layer.type === type); + }, + countNestedLayersOfType(node, type) { + return this.getNestedLayersByType(node, type).length; + }, + isInNestedLayer(node, target) { + const inNested = this.getNestedLayers(node).some((layer) => contains(layer.node, target)); + if (inNested) return true; + if (this.recentlyRemoved.size > 0) return true; + return false; + }, + isInBranch(target) { + return Array.from(this.branches).some((branch) => contains(branch, target)); + }, + add(layer) { + this.layers.push(layer); + this.syncLayers(); + }, + addBranch(node) { + this.branches.push(node); + }, + remove(node) { + const index = this.indexOf(node); + if (index < 0) return; + this.recentlyRemoved.add(node); + nextTick(() => this.recentlyRemoved.delete(node)); + if (index < this.count() - 1) { + const _layers = this.getNestedLayers(node); + _layers.forEach((layer) => layerStack.dismiss(layer.node, node)); + } + this.layers.splice(index, 1); + this.syncLayers(); + }, + removeBranch(node) { + const index = this.branches.indexOf(node); + if (index >= 0) this.branches.splice(index, 1); + }, + syncLayers() { + this.layers.forEach((layer, index) => { + layer.node.style.setProperty("--layer-index", `${index}`); + layer.node.removeAttribute("data-nested"); + layer.node.removeAttribute("data-has-nested"); + const parentOfSameType = this.getParentLayerOfType(layer.node, layer.type); + if (parentOfSameType) { + layer.node.setAttribute("data-nested", layer.type); + } + const nestedCount = this.countNestedLayersOfType(layer.node, layer.type); + if (nestedCount > 0) { + layer.node.setAttribute("data-has-nested", layer.type); + } + layer.node.style.setProperty("--nested-layer-count", `${nestedCount}`); + }); + }, + indexOf(node) { + return this.layers.findIndex((layer) => layer.node === node); + }, + dismiss(node, parent) { + const index = this.indexOf(node); + if (index === -1) return; + const layer = this.layers[index]; + addListenerOnce(node, LAYER_REQUEST_DISMISS_EVENT, (event) => { + layer.requestDismiss?.(event); + if (!event.defaultPrevented) { + layer?.dismiss(); + } + }); + fireCustomEvent2(node, LAYER_REQUEST_DISMISS_EVENT, { + originalLayer: node, + targetLayer: parent, + originalIndex: index, + targetIndex: parent ? this.indexOf(parent) : -1 + }); + this.syncLayers(); + }, + clear() { + this.remove(this.layers[0].node); + } +}; +function fireCustomEvent2(el, type, detail) { + const win = el.ownerDocument.defaultView || window; + const event = new win.CustomEvent(type, { cancelable: true, bubbles: true, detail }); + return el.dispatchEvent(event); +} +function addListenerOnce(el, type, callback) { + el.addEventListener(type, callback, { once: true }); +} +var originalBodyPointerEvents; +function assignPointerEventToLayers() { + layerStack.layers.forEach(({ node }) => { + node.style.pointerEvents = layerStack.isBelowPointerBlockingLayer(node) ? "none" : "auto"; + }); +} +function clearPointerEvent(node) { + node.style.pointerEvents = ""; +} +function disablePointerEventsOutside(node, persistentElements) { + const doc = getDocument(node); + const cleanups = []; + if (layerStack.hasPointerBlockingLayer() && !doc.body.hasAttribute("data-inert")) { + originalBodyPointerEvents = document.body.style.pointerEvents; + queueMicrotask(() => { + doc.body.style.pointerEvents = "none"; + doc.body.setAttribute("data-inert", ""); + }); + } + persistentElements?.forEach((el) => { + const [promise, abort] = waitForElement( + () => { + const node2 = el(); + return isHTMLElement(node2) ? node2 : null; + }, + { timeout: 1e3 } + ); + promise.then((el2) => cleanups.push(setStyle(el2, { pointerEvents: "auto" }))); + cleanups.push(abort); + }); + return () => { + if (layerStack.hasPointerBlockingLayer()) return; + queueMicrotask(() => { + doc.body.style.pointerEvents = originalBodyPointerEvents; + doc.body.removeAttribute("data-inert"); + if (doc.body.style.length === 0) doc.body.removeAttribute("style"); + }); + cleanups.forEach((fn) => fn()); + }; +} +function trackDismissableElementImpl(node, options) { + const { warnOnMissingNode = true } = options; + if (warnOnMissingNode && !node) { + warn("[@zag-js/dismissable] node is `null` or `undefined`"); + return; + } + if (!node) { + return; + } + const { onDismiss, onRequestDismiss, pointerBlocking, exclude: excludeContainers, debug, type = "dialog" } = options; + const layer = { dismiss: onDismiss, node, type, pointerBlocking, requestDismiss: onRequestDismiss }; + layerStack.add(layer); + assignPointerEventToLayers(); + function onPointerDownOutside(event) { + const target = getEventTarget(event.detail.originalEvent); + if (layerStack.isBelowPointerBlockingLayer(node) || layerStack.isInBranch(target)) return; + options.onPointerDownOutside?.(event); + options.onInteractOutside?.(event); + if (event.defaultPrevented) return; + if (debug) { + console.log("onPointerDownOutside:", event.detail.originalEvent); + } + onDismiss?.(); + } + function onFocusOutside(event) { + const target = getEventTarget(event.detail.originalEvent); + if (layerStack.isInBranch(target)) return; + options.onFocusOutside?.(event); + options.onInteractOutside?.(event); + if (event.defaultPrevented) return; + if (debug) { + console.log("onFocusOutside:", event.detail.originalEvent); + } + onDismiss?.(); + } + function onEscapeKeyDown(event) { + if (!layerStack.isTopMost(node)) return; + options.onEscapeKeyDown?.(event); + if (!event.defaultPrevented && onDismiss) { + event.preventDefault(); + onDismiss(); + } + } + function exclude(target) { + if (!node) return false; + const containers = typeof excludeContainers === "function" ? excludeContainers() : excludeContainers; + const _containers = Array.isArray(containers) ? containers : [containers]; + const persistentElements = options.persistentElements?.map((fn) => fn()).filter(isHTMLElement); + if (persistentElements) _containers.push(...persistentElements); + return _containers.some((node2) => contains(node2, target)) || layerStack.isInNestedLayer(node, target); + } + const cleanups = [ + pointerBlocking ? disablePointerEventsOutside(node, options.persistentElements) : void 0, + trackEscapeKeydown(node, onEscapeKeyDown), + trackInteractOutside(node, { exclude, onFocusOutside, onPointerDownOutside, defer: options.defer }) + ]; + return () => { + layerStack.remove(node); + assignPointerEventToLayers(); + clearPointerEvent(node); + cleanups.forEach((fn) => fn?.()); + }; +} +function trackDismissableElement(nodeOrFn, options) { + const { defer } = options; + const func = defer ? raf : (v) => v(); + const cleanups = []; + cleanups.push( + func(() => { + const node = isFunction(nodeOrFn) ? nodeOrFn() : nodeOrFn; + cleanups.push(trackDismissableElementImpl(node, options)); + }) + ); + return () => { + cleanups.forEach((fn) => fn?.()); + }; +} +function trackDismissableBranch(nodeOrFn, options = {}) { + const { defer } = options; + const func = defer ? raf : (v) => v(); + const cleanups = []; + cleanups.push( + func(() => { + const node = isFunction(nodeOrFn) ? nodeOrFn() : nodeOrFn; + if (!node) { + warn("[@zag-js/dismissable] branch node is `null` or `undefined`"); + return; + } + layerStack.addBranch(node); + cleanups.push(() => { + layerStack.removeBranch(node); + }); + }) + ); + return () => { + cleanups.forEach((fn) => fn?.()); + }; +} + +export { + trackDismissableElement, + trackDismissableBranch +}; diff --git a/priv/static/chunk-NYISECP7.mjs b/priv/static/chunk-NYISECP7.mjs new file mode 100644 index 0000000..a10f7da --- /dev/null +++ b/priv/static/chunk-NYISECP7.mjs @@ -0,0 +1,1075 @@ +import { + hasProp, + isEqual, + isObject +} from "./chunk-ZTKS6RKJ.mjs"; + +// ../node_modules/.pnpm/@zag-js+collection@1.33.1/node_modules/@zag-js/collection/dist/index.mjs +var __defProp = Object.defineProperty; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); +var fallback = { + itemToValue(item) { + if (typeof item === "string") return item; + if (isObject(item) && hasProp(item, "value")) return item.value; + return ""; + }, + itemToString(item) { + if (typeof item === "string") return item; + if (isObject(item) && hasProp(item, "label")) return item.label; + return fallback.itemToValue(item); + }, + isItemDisabled(item) { + if (isObject(item) && hasProp(item, "disabled")) return !!item.disabled; + return false; + } +}; +var ListCollection = class _ListCollection { + constructor(options) { + this.options = options; + __publicField(this, "items"); + __publicField(this, "indexMap", null); + __publicField(this, "copy", (items) => { + return new _ListCollection({ ...this.options, items: items ?? [...this.items] }); + }); + __publicField(this, "isEqual", (other) => { + return isEqual(this.items, other.items); + }); + __publicField(this, "setItems", (items) => { + return this.copy(items); + }); + __publicField(this, "getValues", (items = this.items) => { + const values = []; + for (const item of items) { + const value = this.getItemValue(item); + if (value != null) values.push(value); + } + return values; + }); + __publicField(this, "find", (value) => { + if (value == null) return null; + const index = this.indexOf(value); + return index !== -1 ? this.at(index) : null; + }); + __publicField(this, "findMany", (values) => { + const result = []; + for (const value of values) { + const item = this.find(value); + if (item != null) result.push(item); + } + return result; + }); + __publicField(this, "at", (index) => { + if (!this.options.groupBy && !this.options.groupSort) { + return this.items[index] ?? null; + } + let idx = 0; + const groups = this.group(); + for (const [, items] of groups) { + for (const item of items) { + if (idx === index) return item; + idx++; + } + } + return null; + }); + __publicField(this, "sortFn", (valueA, valueB) => { + const indexA = this.indexOf(valueA); + const indexB = this.indexOf(valueB); + return (indexA ?? 0) - (indexB ?? 0); + }); + __publicField(this, "sort", (values) => { + return [...values].sort(this.sortFn.bind(this)); + }); + __publicField(this, "getItemValue", (item) => { + if (item == null) return null; + return this.options.itemToValue?.(item) ?? fallback.itemToValue(item); + }); + __publicField(this, "getItemDisabled", (item) => { + if (item == null) return false; + return this.options.isItemDisabled?.(item) ?? fallback.isItemDisabled(item); + }); + __publicField(this, "stringifyItem", (item) => { + if (item == null) return null; + return this.options.itemToString?.(item) ?? fallback.itemToString(item); + }); + __publicField(this, "stringify", (value) => { + if (value == null) return null; + return this.stringifyItem(this.find(value)); + }); + __publicField(this, "stringifyItems", (items, separator = ", ") => { + const strs = []; + for (const item of items) { + const str = this.stringifyItem(item); + if (str != null) strs.push(str); + } + return strs.join(separator); + }); + __publicField(this, "stringifyMany", (value, separator) => { + return this.stringifyItems(this.findMany(value), separator); + }); + __publicField(this, "has", (value) => { + return this.indexOf(value) !== -1; + }); + __publicField(this, "hasItem", (item) => { + if (item == null) return false; + return this.has(this.getItemValue(item)); + }); + __publicField(this, "group", () => { + const { groupBy, groupSort } = this.options; + if (!groupBy) return [["", [...this.items]]]; + const groups = /* @__PURE__ */ new Map(); + this.items.forEach((item, index) => { + const groupKey = groupBy(item, index); + if (!groups.has(groupKey)) { + groups.set(groupKey, []); + } + groups.get(groupKey).push(item); + }); + let entries = Array.from(groups.entries()); + if (groupSort) { + entries.sort(([a], [b]) => { + if (typeof groupSort === "function") return groupSort(a, b); + if (Array.isArray(groupSort)) { + const indexA = groupSort.indexOf(a); + const indexB = groupSort.indexOf(b); + if (indexA === -1) return 1; + if (indexB === -1) return -1; + return indexA - indexB; + } + if (groupSort === "asc") return a.localeCompare(b); + if (groupSort === "desc") return b.localeCompare(a); + return 0; + }); + } + return entries; + }); + __publicField(this, "getNextValue", (value, step = 1, clamp = false) => { + let index = this.indexOf(value); + if (index === -1) return null; + index = clamp ? Math.min(index + step, this.size - 1) : index + step; + while (index <= this.size && this.getItemDisabled(this.at(index))) index++; + return this.getItemValue(this.at(index)); + }); + __publicField(this, "getPreviousValue", (value, step = 1, clamp = false) => { + let index = this.indexOf(value); + if (index === -1) return null; + index = clamp ? Math.max(index - step, 0) : index - step; + while (index >= 0 && this.getItemDisabled(this.at(index))) index--; + return this.getItemValue(this.at(index)); + }); + __publicField(this, "indexOf", (value) => { + if (value == null) return -1; + if (!this.options.groupBy && !this.options.groupSort) { + return this.items.findIndex((item) => this.getItemValue(item) === value); + } + if (!this.indexMap) { + this.indexMap = /* @__PURE__ */ new Map(); + let idx = 0; + const groups = this.group(); + for (const [, items] of groups) { + for (const item of items) { + const itemValue = this.getItemValue(item); + if (itemValue != null) { + this.indexMap.set(itemValue, idx); + } + idx++; + } + } + } + return this.indexMap.get(value) ?? -1; + }); + __publicField(this, "getByText", (text, current) => { + const currentIndex = current != null ? this.indexOf(current) : -1; + const isSingleKey = text.length === 1; + for (let i = 0; i < this.items.length; i++) { + const item = this.items[(currentIndex + i + 1) % this.items.length]; + if (isSingleKey && this.getItemValue(item) === current) continue; + if (this.getItemDisabled(item)) continue; + if (match(this.stringifyItem(item), text)) return item; + } + return void 0; + }); + __publicField(this, "search", (queryString, options2) => { + const { state, currentValue, timeout = 350 } = options2; + const search = state.keysSoFar + queryString; + const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]); + const query = isRepeated ? search[0] : search; + const item = this.getByText(query, currentValue); + const value = this.getItemValue(item); + function cleanup() { + clearTimeout(state.timer); + state.timer = -1; + } + function update(value2) { + state.keysSoFar = value2; + cleanup(); + if (value2 !== "") { + state.timer = +setTimeout(() => { + update(""); + cleanup(); + }, timeout); + } + } + update(search); + return value; + }); + __publicField(this, "update", (value, item) => { + let index = this.indexOf(value); + if (index === -1) return this; + return this.copy([...this.items.slice(0, index), item, ...this.items.slice(index + 1)]); + }); + __publicField(this, "upsert", (value, item, mode = "append") => { + let index = this.indexOf(value); + if (index === -1) { + const fn = mode === "append" ? this.append : this.prepend; + return fn(item); + } + return this.copy([...this.items.slice(0, index), item, ...this.items.slice(index + 1)]); + }); + __publicField(this, "insert", (index, ...items) => { + return this.copy(insert(this.items, index, ...items)); + }); + __publicField(this, "insertBefore", (value, ...items) => { + let toIndex = this.indexOf(value); + if (toIndex === -1) { + if (this.items.length === 0) toIndex = 0; + else return this; + } + return this.copy(insert(this.items, toIndex, ...items)); + }); + __publicField(this, "insertAfter", (value, ...items) => { + let toIndex = this.indexOf(value); + if (toIndex === -1) { + if (this.items.length === 0) toIndex = 0; + else return this; + } + return this.copy(insert(this.items, toIndex + 1, ...items)); + }); + __publicField(this, "prepend", (...items) => { + return this.copy(insert(this.items, 0, ...items)); + }); + __publicField(this, "append", (...items) => { + return this.copy(insert(this.items, this.items.length, ...items)); + }); + __publicField(this, "filter", (fn) => { + const filteredItems = this.items.filter((item, index) => fn(this.stringifyItem(item), index, item)); + return this.copy(filteredItems); + }); + __publicField(this, "remove", (...itemsOrValues) => { + const values = itemsOrValues.map( + (itemOrValue) => typeof itemOrValue === "string" ? itemOrValue : this.getItemValue(itemOrValue) + ); + return this.copy( + this.items.filter((item) => { + const value = this.getItemValue(item); + if (value == null) return false; + return !values.includes(value); + }) + ); + }); + __publicField(this, "move", (value, toIndex) => { + const fromIndex = this.indexOf(value); + if (fromIndex === -1) return this; + return this.copy(move(this.items, [fromIndex], toIndex)); + }); + __publicField(this, "moveBefore", (value, ...values) => { + let toIndex = this.items.findIndex((item) => this.getItemValue(item) === value); + if (toIndex === -1) return this; + let indices = values.map((value2) => this.items.findIndex((item) => this.getItemValue(item) === value2)).sort((a, b) => a - b); + return this.copy(move(this.items, indices, toIndex)); + }); + __publicField(this, "moveAfter", (value, ...values) => { + let toIndex = this.items.findIndex((item) => this.getItemValue(item) === value); + if (toIndex === -1) return this; + let indices = values.map((value2) => this.items.findIndex((item) => this.getItemValue(item) === value2)).sort((a, b) => a - b); + return this.copy(move(this.items, indices, toIndex + 1)); + }); + __publicField(this, "reorder", (fromIndex, toIndex) => { + return this.copy(move(this.items, [fromIndex], toIndex)); + }); + __publicField(this, "compareValue", (a, b) => { + const indexA = this.indexOf(a); + const indexB = this.indexOf(b); + if (indexA < indexB) return -1; + if (indexA > indexB) return 1; + return 0; + }); + __publicField(this, "range", (from, to) => { + let keys = []; + let key = from; + while (key != null) { + let item = this.find(key); + if (item) keys.push(key); + if (key === to) return keys; + key = this.getNextValue(key); + } + return []; + }); + __publicField(this, "getValueRange", (from, to) => { + if (from && to) { + if (this.compareValue(from, to) <= 0) { + return this.range(from, to); + } + return this.range(to, from); + } + return []; + }); + __publicField(this, "toString", () => { + let result = ""; + for (const item of this.items) { + const value = this.getItemValue(item); + const label = this.stringifyItem(item); + const disabled = this.getItemDisabled(item); + const itemString = [value, label, disabled].filter(Boolean).join(":"); + result += itemString + ","; + } + return result; + }); + __publicField(this, "toJSON", () => { + return { + size: this.size, + first: this.firstValue, + last: this.lastValue + }; + }); + this.items = [...options.items]; + } + /** + * Returns the number of items in the collection + */ + get size() { + return this.items.length; + } + /** + * Returns the first value in the collection + */ + get firstValue() { + let index = 0; + while (this.getItemDisabled(this.at(index))) index++; + return this.getItemValue(this.at(index)); + } + /** + * Returns the last value in the collection + */ + get lastValue() { + let index = this.size - 1; + while (this.getItemDisabled(this.at(index))) index--; + return this.getItemValue(this.at(index)); + } + *[Symbol.iterator]() { + yield* this.items; + } +}; +var match = (label, query) => { + return !!label?.toLowerCase().startsWith(query.toLowerCase()); +}; +function insert(items, index, ...values) { + return [...items.slice(0, index), ...values, ...items.slice(index)]; +} +function move(items, indices, toIndex) { + indices = [...indices].sort((a, b) => a - b); + const itemsToMove = indices.map((i) => items[i]); + for (let i = indices.length - 1; i >= 0; i--) { + items = [...items.slice(0, indices[i]), ...items.slice(indices[i] + 1)]; + } + toIndex = Math.max(0, toIndex - indices.filter((i) => i < toIndex).length); + return [...items.slice(0, toIndex), ...itemsToMove, ...items.slice(toIndex)]; +} +function access(node, indexPath, options) { + for (let i = 0; i < indexPath.length; i++) node = options.getChildren(node, indexPath.slice(i + 1))[indexPath[i]]; + return node; +} +function ancestorIndexPaths(indexPaths) { + const sortedPaths = sortIndexPaths(indexPaths); + const result = []; + const seen = /* @__PURE__ */ new Set(); + for (const indexPath of sortedPaths) { + const key = indexPath.join(); + if (!seen.has(key)) { + seen.add(key); + result.push(indexPath); + } + } + return result; +} +function compareIndexPaths(a, b) { + for (let i = 0; i < Math.min(a.length, b.length); i++) { + if (a[i] < b[i]) return -1; + if (a[i] > b[i]) return 1; + } + return a.length - b.length; +} +function sortIndexPaths(indexPaths) { + return indexPaths.sort(compareIndexPaths); +} +function find(node, options) { + let found; + visit(node, { + ...options, + onEnter: (child, indexPath) => { + if (options.predicate(child, indexPath)) { + found = child; + return "stop"; + } + } + }); + return found; +} +function findAll(node, options) { + const found = []; + visit(node, { + onEnter: (child, indexPath) => { + if (options.predicate(child, indexPath)) found.push(child); + }, + getChildren: options.getChildren + }); + return found; +} +function findIndexPath(node, options) { + let found; + visit(node, { + onEnter: (child, indexPath) => { + if (options.predicate(child, indexPath)) { + found = [...indexPath]; + return "stop"; + } + }, + getChildren: options.getChildren + }); + return found; +} +function reduce(node, options) { + let result = options.initialResult; + visit(node, { + ...options, + onEnter: (child, indexPath) => { + result = options.nextResult(result, child, indexPath); + } + }); + return result; +} +function flatMap(node, options) { + return reduce(node, { + ...options, + initialResult: [], + nextResult: (result, child, indexPath) => { + result.push(...options.transform(child, indexPath)); + return result; + } + }); +} +function filter(node, options) { + const { predicate, create, getChildren } = options; + const filterRecursive = (node2, indexPath) => { + const children = getChildren(node2, indexPath); + const filteredChildren = []; + children.forEach((child, index) => { + const childIndexPath = [...indexPath, index]; + const filteredChild = filterRecursive(child, childIndexPath); + if (filteredChild) filteredChildren.push(filteredChild); + }); + const isRoot = indexPath.length === 0; + const nodeMatches = predicate(node2, indexPath); + const hasFilteredChildren = filteredChildren.length > 0; + if (isRoot || nodeMatches || hasFilteredChildren) { + return create(node2, filteredChildren, indexPath); + } + return null; + }; + return filterRecursive(node, []) || create(node, [], []); +} +function flatten(rootNode, options) { + const nodes = []; + let idx = 0; + const idxMap = /* @__PURE__ */ new Map(); + const parentMap = /* @__PURE__ */ new Map(); + visit(rootNode, { + getChildren: options.getChildren, + onEnter: (node, indexPath) => { + if (!idxMap.has(node)) { + idxMap.set(node, idx++); + } + const children = options.getChildren(node, indexPath); + children.forEach((child) => { + if (!parentMap.has(child)) { + parentMap.set(child, node); + } + if (!idxMap.has(child)) { + idxMap.set(child, idx++); + } + }); + const _children = children.length > 0 ? children.map((child) => idxMap.get(child)) : void 0; + const parent = parentMap.get(node); + const _parent = parent ? idxMap.get(parent) : void 0; + const _index = idxMap.get(node); + nodes.push({ ...node, _children, _parent, _index }); + } + }); + return nodes; +} +function insertOperation(index, nodes) { + return { type: "insert", index, nodes }; +} +function removeOperation(indexes) { + return { type: "remove", indexes }; +} +function replaceOperation() { + return { type: "replace" }; +} +function splitIndexPath(indexPath) { + return [indexPath.slice(0, -1), indexPath[indexPath.length - 1]]; +} +function getInsertionOperations(indexPath, nodes, operations = /* @__PURE__ */ new Map()) { + const [parentIndexPath, index] = splitIndexPath(indexPath); + for (let i = parentIndexPath.length - 1; i >= 0; i--) { + const parentKey = parentIndexPath.slice(0, i).join(); + switch (operations.get(parentKey)?.type) { + case "remove": + continue; + } + operations.set(parentKey, replaceOperation()); + } + const operation = operations.get(parentIndexPath.join()); + switch (operation?.type) { + case "remove": + operations.set(parentIndexPath.join(), { + type: "removeThenInsert", + removeIndexes: operation.indexes, + insertIndex: index, + insertNodes: nodes + }); + break; + default: + operations.set(parentIndexPath.join(), insertOperation(index, nodes)); + } + return operations; +} +function getRemovalOperations(indexPaths) { + const operations = /* @__PURE__ */ new Map(); + const indexesToRemove = /* @__PURE__ */ new Map(); + for (const indexPath of indexPaths) { + const parentKey = indexPath.slice(0, -1).join(); + const value = indexesToRemove.get(parentKey) ?? []; + value.push(indexPath[indexPath.length - 1]); + indexesToRemove.set( + parentKey, + value.sort((a, b) => a - b) + ); + } + for (const indexPath of indexPaths) { + for (let i = indexPath.length - 2; i >= 0; i--) { + const parentKey = indexPath.slice(0, i).join(); + if (!operations.has(parentKey)) { + operations.set(parentKey, replaceOperation()); + } + } + } + for (const [parentKey, indexes] of indexesToRemove) { + operations.set(parentKey, removeOperation(indexes)); + } + return operations; +} +function getReplaceOperations(indexPath, node) { + const operations = /* @__PURE__ */ new Map(); + const [parentIndexPath, index] = splitIndexPath(indexPath); + for (let i = parentIndexPath.length - 1; i >= 0; i--) { + const parentKey = parentIndexPath.slice(0, i).join(); + operations.set(parentKey, replaceOperation()); + } + operations.set(parentIndexPath.join(), { + type: "removeThenInsert", + removeIndexes: [index], + insertIndex: index, + insertNodes: [node] + }); + return operations; +} +function mutate(node, operations, options) { + return map(node, { + ...options, + getChildren: (node2, indexPath) => { + const key = indexPath.join(); + const operation = operations.get(key); + switch (operation?.type) { + case "replace": + case "remove": + case "removeThenInsert": + case "insert": + return options.getChildren(node2, indexPath); + default: + return []; + } + }, + transform: (node2, children, indexPath) => { + const key = indexPath.join(); + const operation = operations.get(key); + switch (operation?.type) { + case "remove": + return options.create( + node2, + children.filter((_, index) => !operation.indexes.includes(index)), + indexPath + ); + case "removeThenInsert": + const updatedChildren = children.filter((_, index) => !operation.removeIndexes.includes(index)); + const adjustedIndex = operation.removeIndexes.reduce( + (index, removedIndex) => removedIndex < index ? index - 1 : index, + operation.insertIndex + ); + return options.create(node2, splice(updatedChildren, adjustedIndex, 0, ...operation.insertNodes), indexPath); + case "insert": + return options.create(node2, splice(children, operation.index, 0, ...operation.nodes), indexPath); + case "replace": + return options.create(node2, children, indexPath); + default: + return node2; + } + } + }); +} +function splice(array, start, deleteCount, ...items) { + return [...array.slice(0, start), ...items, ...array.slice(start + deleteCount)]; +} +function map(node, options) { + const childrenMap = {}; + visit(node, { + ...options, + onLeave: (child, indexPath) => { + const keyIndexPath = [0, ...indexPath]; + const key = keyIndexPath.join(); + const transformed = options.transform(child, childrenMap[key] ?? [], indexPath); + const parentKey = keyIndexPath.slice(0, -1).join(); + const parentChildren = childrenMap[parentKey] ?? []; + parentChildren.push(transformed); + childrenMap[parentKey] = parentChildren; + } + }); + return childrenMap[""][0]; +} +function insert2(node, options) { + const { nodes, at } = options; + if (at.length === 0) throw new Error(`Can't insert nodes at the root`); + const state = getInsertionOperations(at, nodes); + return mutate(node, state, options); +} +function replace(node, options) { + if (options.at.length === 0) return options.node; + const operations = getReplaceOperations(options.at, options.node); + return mutate(node, operations, options); +} +function remove(node, options) { + if (options.indexPaths.length === 0) return node; + for (const indexPath of options.indexPaths) { + if (indexPath.length === 0) throw new Error(`Can't remove the root node`); + } + const operations = getRemovalOperations(options.indexPaths); + return mutate(node, operations, options); +} +function move2(node, options) { + if (options.indexPaths.length === 0) return node; + for (const indexPath of options.indexPaths) { + if (indexPath.length === 0) throw new Error(`Can't move the root node`); + } + if (options.to.length === 0) throw new Error(`Can't move nodes to the root`); + const _ancestorIndexPaths = ancestorIndexPaths(options.indexPaths); + const nodesToInsert = _ancestorIndexPaths.map((indexPath) => access(node, indexPath, options)); + const operations = getInsertionOperations(options.to, nodesToInsert, getRemovalOperations(_ancestorIndexPaths)); + return mutate(node, operations, options); +} +function visit(node, options) { + const { onEnter, onLeave, getChildren } = options; + let indexPath = []; + let stack = [{ node }]; + const getIndexPath = options.reuseIndexPath ? () => indexPath : () => indexPath.slice(); + while (stack.length > 0) { + let wrapper = stack[stack.length - 1]; + if (wrapper.state === void 0) { + const enterResult = onEnter?.(wrapper.node, getIndexPath()); + if (enterResult === "stop") return; + wrapper.state = enterResult === "skip" ? -1 : 0; + } + const children = wrapper.children || getChildren(wrapper.node, getIndexPath()); + wrapper.children || (wrapper.children = children); + if (wrapper.state !== -1) { + if (wrapper.state < children.length) { + let currentIndex = wrapper.state; + indexPath.push(currentIndex); + stack.push({ node: children[currentIndex] }); + wrapper.state = currentIndex + 1; + continue; + } + const leaveResult = onLeave?.(wrapper.node, getIndexPath()); + if (leaveResult === "stop") return; + } + indexPath.pop(); + stack.pop(); + } +} +var TreeCollection = class _TreeCollection { + constructor(options) { + this.options = options; + __publicField(this, "rootNode"); + __publicField(this, "isEqual", (other) => { + return isEqual(this.rootNode, other.rootNode); + }); + __publicField(this, "getNodeChildren", (node) => { + return this.options.nodeToChildren?.(node) ?? fallbackMethods.nodeToChildren(node) ?? []; + }); + __publicField(this, "resolveIndexPath", (valueOrIndexPath) => { + return typeof valueOrIndexPath === "string" ? this.getIndexPath(valueOrIndexPath) : valueOrIndexPath; + }); + __publicField(this, "resolveNode", (valueOrIndexPath) => { + const indexPath = this.resolveIndexPath(valueOrIndexPath); + return indexPath ? this.at(indexPath) : void 0; + }); + __publicField(this, "getNodeChildrenCount", (node) => { + return this.options.nodeToChildrenCount?.(node) ?? fallbackMethods.nodeToChildrenCount(node); + }); + __publicField(this, "getNodeValue", (node) => { + return this.options.nodeToValue?.(node) ?? fallbackMethods.nodeToValue(node); + }); + __publicField(this, "getNodeDisabled", (node) => { + return this.options.isNodeDisabled?.(node) ?? fallbackMethods.isNodeDisabled(node); + }); + __publicField(this, "stringify", (value) => { + const node = this.findNode(value); + if (!node) return null; + return this.stringifyNode(node); + }); + __publicField(this, "stringifyNode", (node) => { + return this.options.nodeToString?.(node) ?? fallbackMethods.nodeToString(node); + }); + __publicField(this, "getFirstNode", (rootNode = this.rootNode, opts = {}) => { + let firstChild; + visit(rootNode, { + getChildren: this.getNodeChildren, + onEnter: (node, indexPath) => { + if (this.isSameNode(node, rootNode)) return; + if (opts.skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip"; + if (!firstChild && indexPath.length > 0 && !this.getNodeDisabled(node)) { + firstChild = node; + return "stop"; + } + } + }); + return firstChild; + }); + __publicField(this, "getLastNode", (rootNode = this.rootNode, opts = {}) => { + let lastChild; + visit(rootNode, { + getChildren: this.getNodeChildren, + onEnter: (node, indexPath) => { + if (this.isSameNode(node, rootNode)) return; + if (opts.skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip"; + if (indexPath.length > 0 && !this.getNodeDisabled(node)) { + lastChild = node; + } + } + }); + return lastChild; + }); + __publicField(this, "at", (indexPath) => { + return access(this.rootNode, indexPath, { + getChildren: this.getNodeChildren + }); + }); + __publicField(this, "findNode", (value, rootNode = this.rootNode) => { + return find(rootNode, { + getChildren: this.getNodeChildren, + predicate: (node) => this.getNodeValue(node) === value + }); + }); + __publicField(this, "findNodes", (values, rootNode = this.rootNode) => { + const v = new Set(values.filter((v2) => v2 != null)); + return findAll(rootNode, { + getChildren: this.getNodeChildren, + predicate: (node) => v.has(this.getNodeValue(node)) + }); + }); + __publicField(this, "sort", (values) => { + return values.reduce((acc, value) => { + const indexPath = this.getIndexPath(value); + if (indexPath) acc.push({ value, indexPath }); + return acc; + }, []).sort((a, b) => compareIndexPaths(a.indexPath, b.indexPath)).map(({ value }) => value); + }); + __publicField(this, "getIndexPath", (value) => { + return findIndexPath(this.rootNode, { + getChildren: this.getNodeChildren, + predicate: (node) => this.getNodeValue(node) === value + }); + }); + __publicField(this, "getValue", (indexPath) => { + const node = this.at(indexPath); + return node ? this.getNodeValue(node) : void 0; + }); + __publicField(this, "getValuePath", (indexPath) => { + if (!indexPath) return []; + const valuePath = []; + let currentPath = [...indexPath]; + while (currentPath.length > 0) { + const node = this.at(currentPath); + if (node) valuePath.unshift(this.getNodeValue(node)); + currentPath.pop(); + } + return valuePath; + }); + __publicField(this, "getDepth", (value) => { + const indexPath = findIndexPath(this.rootNode, { + getChildren: this.getNodeChildren, + predicate: (node) => this.getNodeValue(node) === value + }); + return indexPath?.length ?? 0; + }); + __publicField(this, "isSameNode", (node, other) => { + return this.getNodeValue(node) === this.getNodeValue(other); + }); + __publicField(this, "isRootNode", (node) => { + return this.isSameNode(node, this.rootNode); + }); + __publicField(this, "contains", (parentIndexPath, valueIndexPath) => { + if (!parentIndexPath || !valueIndexPath) return false; + return valueIndexPath.slice(0, parentIndexPath.length).every((_, i) => parentIndexPath[i] === valueIndexPath[i]); + }); + __publicField(this, "getNextNode", (value, opts = {}) => { + let found = false; + let nextNode; + visit(this.rootNode, { + getChildren: this.getNodeChildren, + onEnter: (node, indexPath) => { + if (this.isRootNode(node)) return; + const nodeValue = this.getNodeValue(node); + if (opts.skip?.({ value: nodeValue, node, indexPath })) { + if (nodeValue === value) { + found = true; + } + return "skip"; + } + if (found && !this.getNodeDisabled(node)) { + nextNode = node; + return "stop"; + } + if (nodeValue === value) { + found = true; + } + } + }); + return nextNode; + }); + __publicField(this, "getPreviousNode", (value, opts = {}) => { + let previousNode; + let found = false; + visit(this.rootNode, { + getChildren: this.getNodeChildren, + onEnter: (node, indexPath) => { + if (this.isRootNode(node)) return; + const nodeValue = this.getNodeValue(node); + if (opts.skip?.({ value: nodeValue, node, indexPath })) { + return "skip"; + } + if (nodeValue === value) { + found = true; + return "stop"; + } + if (!this.getNodeDisabled(node)) { + previousNode = node; + } + } + }); + return found ? previousNode : void 0; + }); + __publicField(this, "getParentNodes", (valueOrIndexPath) => { + const indexPath = this.resolveIndexPath(valueOrIndexPath)?.slice(); + if (!indexPath) return []; + const result = []; + while (indexPath.length > 0) { + indexPath.pop(); + const parentNode = this.at(indexPath); + if (parentNode && !this.isRootNode(parentNode)) { + result.unshift(parentNode); + } + } + return result; + }); + __publicField(this, "getDescendantNodes", (valueOrIndexPath, options2) => { + const parentNode = this.resolveNode(valueOrIndexPath); + if (!parentNode) return []; + const result = []; + visit(parentNode, { + getChildren: this.getNodeChildren, + onEnter: (node, nodeIndexPath) => { + if (nodeIndexPath.length === 0) return; + if (!options2?.withBranch && this.isBranchNode(node)) return; + result.push(node); + } + }); + return result; + }); + __publicField(this, "getDescendantValues", (valueOrIndexPath, options2) => { + const children = this.getDescendantNodes(valueOrIndexPath, options2); + return children.map((child) => this.getNodeValue(child)); + }); + __publicField(this, "getParentIndexPath", (indexPath) => { + return indexPath.slice(0, -1); + }); + __publicField(this, "getParentNode", (valueOrIndexPath) => { + const indexPath = this.resolveIndexPath(valueOrIndexPath); + return indexPath ? this.at(this.getParentIndexPath(indexPath)) : void 0; + }); + __publicField(this, "visit", (opts) => { + const { skip, ...rest } = opts; + visit(this.rootNode, { + ...rest, + getChildren: this.getNodeChildren, + onEnter: (node, indexPath) => { + if (this.isRootNode(node)) return; + if (skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip"; + return rest.onEnter?.(node, indexPath); + } + }); + }); + __publicField(this, "getPreviousSibling", (indexPath) => { + const parentNode = this.getParentNode(indexPath); + if (!parentNode) return; + const siblings = this.getNodeChildren(parentNode); + let idx = indexPath[indexPath.length - 1]; + while (--idx >= 0) { + const sibling = siblings[idx]; + if (!this.getNodeDisabled(sibling)) return sibling; + } + return; + }); + __publicField(this, "getNextSibling", (indexPath) => { + const parentNode = this.getParentNode(indexPath); + if (!parentNode) return; + const siblings = this.getNodeChildren(parentNode); + let idx = indexPath[indexPath.length - 1]; + while (++idx < siblings.length) { + const sibling = siblings[idx]; + if (!this.getNodeDisabled(sibling)) return sibling; + } + return; + }); + __publicField(this, "getSiblingNodes", (indexPath) => { + const parentNode = this.getParentNode(indexPath); + return parentNode ? this.getNodeChildren(parentNode) : []; + }); + __publicField(this, "getValues", (rootNode = this.rootNode) => { + const values = flatMap(rootNode, { + getChildren: this.getNodeChildren, + transform: (node) => [this.getNodeValue(node)] + }); + return values.slice(1); + }); + __publicField(this, "isValidDepth", (indexPath, depth) => { + if (depth == null) return true; + if (typeof depth === "function") return depth(indexPath.length); + return indexPath.length === depth; + }); + __publicField(this, "isBranchNode", (node) => { + return this.getNodeChildren(node).length > 0 || this.getNodeChildrenCount(node) != null; + }); + __publicField(this, "getBranchValues", (rootNode = this.rootNode, opts = {}) => { + let values = []; + visit(rootNode, { + getChildren: this.getNodeChildren, + onEnter: (node, indexPath) => { + if (indexPath.length === 0) return; + const nodeValue = this.getNodeValue(node); + if (opts.skip?.({ value: nodeValue, node, indexPath })) return "skip"; + if (this.isBranchNode(node) && this.isValidDepth(indexPath, opts.depth)) { + values.push(this.getNodeValue(node)); + } + } + }); + return values; + }); + __publicField(this, "flatten", (rootNode = this.rootNode) => { + return flatten(rootNode, { getChildren: this.getNodeChildren }); + }); + __publicField(this, "_create", (node, children) => { + if (this.getNodeChildren(node).length > 0 || children.length > 0) { + return { ...node, children }; + } + return { ...node }; + }); + __publicField(this, "_insert", (rootNode, indexPath, nodes) => { + return this.copy( + insert2(rootNode, { at: indexPath, nodes, getChildren: this.getNodeChildren, create: this._create }) + ); + }); + __publicField(this, "copy", (rootNode) => { + return new _TreeCollection({ ...this.options, rootNode }); + }); + __publicField(this, "_replace", (rootNode, indexPath, node) => { + return this.copy( + replace(rootNode, { at: indexPath, node, getChildren: this.getNodeChildren, create: this._create }) + ); + }); + __publicField(this, "_move", (rootNode, indexPaths, to) => { + return this.copy(move2(rootNode, { indexPaths, to, getChildren: this.getNodeChildren, create: this._create })); + }); + __publicField(this, "_remove", (rootNode, indexPaths) => { + return this.copy(remove(rootNode, { indexPaths, getChildren: this.getNodeChildren, create: this._create })); + }); + __publicField(this, "replace", (indexPath, node) => { + return this._replace(this.rootNode, indexPath, node); + }); + __publicField(this, "remove", (indexPaths) => { + return this._remove(this.rootNode, indexPaths); + }); + __publicField(this, "insertBefore", (indexPath, nodes) => { + const parentNode = this.getParentNode(indexPath); + return parentNode ? this._insert(this.rootNode, indexPath, nodes) : void 0; + }); + __publicField(this, "insertAfter", (indexPath, nodes) => { + const parentNode = this.getParentNode(indexPath); + if (!parentNode) return; + const nextIndex2 = [...indexPath.slice(0, -1), indexPath[indexPath.length - 1] + 1]; + return this._insert(this.rootNode, nextIndex2, nodes); + }); + __publicField(this, "move", (fromIndexPaths, toIndexPath) => { + return this._move(this.rootNode, fromIndexPaths, toIndexPath); + }); + __publicField(this, "filter", (predicate) => { + const filteredRoot = filter(this.rootNode, { + predicate, + getChildren: this.getNodeChildren, + create: this._create + }); + return this.copy(filteredRoot); + }); + __publicField(this, "toJSON", () => { + return this.getValues(this.rootNode); + }); + this.rootNode = options.rootNode; + } +}; +var fallbackMethods = { + nodeToValue(node) { + if (typeof node === "string") return node; + if (isObject(node) && hasProp(node, "value")) return node.value; + return ""; + }, + nodeToString(node) { + if (typeof node === "string") return node; + if (isObject(node) && hasProp(node, "label")) return node.label; + return fallbackMethods.nodeToValue(node); + }, + isNodeDisabled(node) { + if (isObject(node) && hasProp(node, "disabled")) return !!node.disabled; + return false; + }, + nodeToChildren(node) { + return node.children; + }, + nodeToChildrenCount(node) { + if (isObject(node) && hasProp(node, "childrenCount")) return node.childrenCount; + } +}; + +export { + ListCollection, + TreeCollection +}; diff --git a/priv/static/chunk-QD4Z4LOU.mjs b/priv/static/chunk-QD4Z4LOU.mjs new file mode 100644 index 0000000..00e2983 --- /dev/null +++ b/priv/static/chunk-QD4Z4LOU.mjs @@ -0,0 +1,163 @@ +import { + getDocument, + getEventTarget, + getWindow, + isMac, + isVirtualClick +} from "./chunk-ZTKS6RKJ.mjs"; + +// ../node_modules/.pnpm/@zag-js+focus-visible@1.33.1/node_modules/@zag-js/focus-visible/dist/index.mjs +function isValidKey(e) { + return !(e.metaKey || !isMac() && e.altKey || e.ctrlKey || e.key === "Control" || e.key === "Shift" || e.key === "Meta"); +} +var nonTextInputTypes = /* @__PURE__ */ new Set(["checkbox", "radio", "range", "color", "file", "image", "button", "submit", "reset"]); +function isKeyboardFocusEvent(isTextInput, modality, e) { + const target = e ? getEventTarget(e) : null; + const win = getWindow(target); + isTextInput = isTextInput || target instanceof win.HTMLInputElement && !nonTextInputTypes.has(target?.type) || target instanceof win.HTMLTextAreaElement || target instanceof win.HTMLElement && target.isContentEditable; + return !(isTextInput && modality === "keyboard" && e instanceof win.KeyboardEvent && !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key)); +} +var currentModality = null; +var changeHandlers = /* @__PURE__ */ new Set(); +var listenerMap = /* @__PURE__ */ new Map(); +var hasEventBeforeFocus = false; +var hasBlurredWindowRecently = false; +var FOCUS_VISIBLE_INPUT_KEYS = { + Tab: true, + Escape: true +}; +function triggerChangeHandlers(modality, e) { + for (let handler of changeHandlers) { + handler(modality, e); + } +} +function handleKeyboardEvent(e) { + hasEventBeforeFocus = true; + if (isValidKey(e)) { + currentModality = "keyboard"; + triggerChangeHandlers("keyboard", e); + } +} +function handlePointerEvent(e) { + currentModality = "pointer"; + if (e.type === "mousedown" || e.type === "pointerdown") { + hasEventBeforeFocus = true; + triggerChangeHandlers("pointer", e); + } +} +function handleClickEvent(e) { + if (isVirtualClick(e)) { + hasEventBeforeFocus = true; + currentModality = "virtual"; + } +} +function handleFocusEvent(e) { + const target = getEventTarget(e); + if (target === getWindow(target) || target === getDocument(target)) { + return; + } + if (!hasEventBeforeFocus && !hasBlurredWindowRecently) { + currentModality = "virtual"; + triggerChangeHandlers("virtual", e); + } + hasEventBeforeFocus = false; + hasBlurredWindowRecently = false; +} +function handleWindowBlur() { + hasEventBeforeFocus = false; + hasBlurredWindowRecently = true; +} +function setupGlobalFocusEvents(root) { + if (typeof window === "undefined" || listenerMap.get(getWindow(root))) { + return; + } + const win = getWindow(root); + const doc = getDocument(root); + let focus = win.HTMLElement.prototype.focus; + function patchedFocus() { + currentModality = "virtual"; + triggerChangeHandlers("virtual", null); + hasEventBeforeFocus = true; + focus.apply(this, arguments); + } + try { + Object.defineProperty(win.HTMLElement.prototype, "focus", { + configurable: true, + value: patchedFocus + }); + } catch { + } + doc.addEventListener("keydown", handleKeyboardEvent, true); + doc.addEventListener("keyup", handleKeyboardEvent, true); + doc.addEventListener("click", handleClickEvent, true); + win.addEventListener("focus", handleFocusEvent, true); + win.addEventListener("blur", handleWindowBlur, false); + if (typeof win.PointerEvent !== "undefined") { + doc.addEventListener("pointerdown", handlePointerEvent, true); + doc.addEventListener("pointermove", handlePointerEvent, true); + doc.addEventListener("pointerup", handlePointerEvent, true); + } else { + doc.addEventListener("mousedown", handlePointerEvent, true); + doc.addEventListener("mousemove", handlePointerEvent, true); + doc.addEventListener("mouseup", handlePointerEvent, true); + } + win.addEventListener( + "beforeunload", + () => { + tearDownWindowFocusTracking(root); + }, + { once: true } + ); + listenerMap.set(win, { focus }); +} +var tearDownWindowFocusTracking = (root, loadListener) => { + const win = getWindow(root); + const doc = getDocument(root); + const listenerData = listenerMap.get(win); + if (!listenerData) { + return; + } + try { + Object.defineProperty(win.HTMLElement.prototype, "focus", { + configurable: true, + value: listenerData.focus + }); + } catch { + } + doc.removeEventListener("keydown", handleKeyboardEvent, true); + doc.removeEventListener("keyup", handleKeyboardEvent, true); + doc.removeEventListener("click", handleClickEvent, true); + win.removeEventListener("focus", handleFocusEvent, true); + win.removeEventListener("blur", handleWindowBlur, false); + if (typeof win.PointerEvent !== "undefined") { + doc.removeEventListener("pointerdown", handlePointerEvent, true); + doc.removeEventListener("pointermove", handlePointerEvent, true); + doc.removeEventListener("pointerup", handlePointerEvent, true); + } else { + doc.removeEventListener("mousedown", handlePointerEvent, true); + doc.removeEventListener("mousemove", handlePointerEvent, true); + doc.removeEventListener("mouseup", handlePointerEvent, true); + } + listenerMap.delete(win); +}; +function isFocusVisible() { + return currentModality === "keyboard"; +} +function trackFocusVisible(props = {}) { + const { isTextInput, autoFocus, onChange, root } = props; + setupGlobalFocusEvents(root); + onChange?.({ isFocusVisible: autoFocus || isFocusVisible(), modality: currentModality }); + const handler = (modality, e) => { + if (!isKeyboardFocusEvent(!!isTextInput, modality, e)) return; + onChange?.({ isFocusVisible: isFocusVisible(), modality }); + }; + changeHandlers.add(handler); + return () => { + changeHandlers.delete(handler); + }; +} + +export { + isFocusVisible, + trackFocusVisible +}; diff --git a/priv/static/chunk-T4BCXOJK.mjs b/priv/static/chunk-T4BCXOJK.mjs new file mode 100644 index 0000000..634578d --- /dev/null +++ b/priv/static/chunk-T4BCXOJK.mjs @@ -0,0 +1,1953 @@ +import { + compact, + getComputedStyle as getComputedStyle2, + getWindow, + isHTMLElement, + isNull, + noop, + raf +} from "./chunk-ZTKS6RKJ.mjs"; + +// ../node_modules/.pnpm/@floating-ui+utils@0.2.10/node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs +var sides = ["top", "right", "bottom", "left"]; +var min = Math.min; +var max = Math.max; +var round = Math.round; +var floor = Math.floor; +var createCoords = (v) => ({ + x: v, + y: v +}); +var oppositeSideMap = { + left: "right", + right: "left", + bottom: "top", + top: "bottom" +}; +var oppositeAlignmentMap = { + start: "end", + end: "start" +}; +function clamp(start, value, end) { + return max(start, min(value, end)); +} +function evaluate(value, param) { + return typeof value === "function" ? value(param) : value; +} +function getSide(placement) { + return placement.split("-")[0]; +} +function getAlignment(placement) { + return placement.split("-")[1]; +} +function getOppositeAxis(axis) { + return axis === "x" ? "y" : "x"; +} +function getAxisLength(axis) { + return axis === "y" ? "height" : "width"; +} +var yAxisSides = /* @__PURE__ */ new Set(["top", "bottom"]); +function getSideAxis(placement) { + return yAxisSides.has(getSide(placement)) ? "y" : "x"; +} +function getAlignmentAxis(placement) { + return getOppositeAxis(getSideAxis(placement)); +} +function getAlignmentSides(placement, rects, rtl) { + if (rtl === void 0) { + rtl = false; + } + const alignment = getAlignment(placement); + const alignmentAxis = getAlignmentAxis(placement); + const length = getAxisLength(alignmentAxis); + let mainAlignmentSide = alignmentAxis === "x" ? alignment === (rtl ? "end" : "start") ? "right" : "left" : alignment === "start" ? "bottom" : "top"; + if (rects.reference[length] > rects.floating[length]) { + mainAlignmentSide = getOppositePlacement(mainAlignmentSide); + } + return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)]; +} +function getExpandedPlacements(placement) { + const oppositePlacement = getOppositePlacement(placement); + return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; +} +function getOppositeAlignmentPlacement(placement) { + return placement.replace(/start|end/g, (alignment) => oppositeAlignmentMap[alignment]); +} +var lrPlacement = ["left", "right"]; +var rlPlacement = ["right", "left"]; +var tbPlacement = ["top", "bottom"]; +var btPlacement = ["bottom", "top"]; +function getSideList(side, isStart, rtl) { + switch (side) { + case "top": + case "bottom": + if (rtl) return isStart ? rlPlacement : lrPlacement; + return isStart ? lrPlacement : rlPlacement; + case "left": + case "right": + return isStart ? tbPlacement : btPlacement; + default: + return []; + } +} +function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { + const alignment = getAlignment(placement); + let list = getSideList(getSide(placement), direction === "start", rtl); + if (alignment) { + list = list.map((side) => side + "-" + alignment); + if (flipAlignment) { + list = list.concat(list.map(getOppositeAlignmentPlacement)); + } + } + return list; +} +function getOppositePlacement(placement) { + return placement.replace(/left|right|bottom|top/g, (side) => oppositeSideMap[side]); +} +function expandPaddingObject(padding) { + return { + top: 0, + right: 0, + bottom: 0, + left: 0, + ...padding + }; +} +function getPaddingObject(padding) { + return typeof padding !== "number" ? expandPaddingObject(padding) : { + top: padding, + right: padding, + bottom: padding, + left: padding + }; +} +function rectToClientRect(rect) { + const { + x, + y, + width, + height + } = rect; + return { + width, + height, + top: y, + left: x, + right: x + width, + bottom: y + height, + x, + y + }; +} + +// ../node_modules/.pnpm/@floating-ui+core@1.7.4/node_modules/@floating-ui/core/dist/floating-ui.core.mjs +function computeCoordsFromPlacement(_ref, placement, rtl) { + let { + reference, + floating + } = _ref; + const sideAxis = getSideAxis(placement); + const alignmentAxis = getAlignmentAxis(placement); + const alignLength = getAxisLength(alignmentAxis); + const side = getSide(placement); + const isVertical = sideAxis === "y"; + const commonX = reference.x + reference.width / 2 - floating.width / 2; + const commonY = reference.y + reference.height / 2 - floating.height / 2; + const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2; + let coords; + switch (side) { + case "top": + coords = { + x: commonX, + y: reference.y - floating.height + }; + break; + case "bottom": + coords = { + x: commonX, + y: reference.y + reference.height + }; + break; + case "right": + coords = { + x: reference.x + reference.width, + y: commonY + }; + break; + case "left": + coords = { + x: reference.x - floating.width, + y: commonY + }; + break; + default: + coords = { + x: reference.x, + y: reference.y + }; + } + switch (getAlignment(placement)) { + case "start": + coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1); + break; + case "end": + coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1); + break; + } + return coords; +} +async function detectOverflow(state, options) { + var _await$platform$isEle; + if (options === void 0) { + options = {}; + } + const { + x, + y, + platform: platform2, + rects, + elements, + strategy + } = state; + const { + boundary = "clippingAncestors", + rootBoundary = "viewport", + elementContext = "floating", + altBoundary = false, + padding = 0 + } = evaluate(options, state); + const paddingObject = getPaddingObject(padding); + const altContext = elementContext === "floating" ? "reference" : "floating"; + const element = elements[altBoundary ? altContext : elementContext]; + const clippingClientRect = rectToClientRect(await platform2.getClippingRect({ + element: ((_await$platform$isEle = await (platform2.isElement == null ? void 0 : platform2.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || await (platform2.getDocumentElement == null ? void 0 : platform2.getDocumentElement(elements.floating)), + boundary, + rootBoundary, + strategy + })); + const rect = elementContext === "floating" ? { + x, + y, + width: rects.floating.width, + height: rects.floating.height + } : rects.reference; + const offsetParent = await (platform2.getOffsetParent == null ? void 0 : platform2.getOffsetParent(elements.floating)); + const offsetScale = await (platform2.isElement == null ? void 0 : platform2.isElement(offsetParent)) ? await (platform2.getScale == null ? void 0 : platform2.getScale(offsetParent)) || { + x: 1, + y: 1 + } : { + x: 1, + y: 1 + }; + const elementClientRect = rectToClientRect(platform2.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform2.convertOffsetParentRelativeRectToViewportRelativeRect({ + elements, + rect, + offsetParent, + strategy + }) : rect); + return { + top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y, + bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y, + left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x, + right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x + }; +} +var computePosition = async (reference, floating, config) => { + const { + placement = "bottom", + strategy = "absolute", + middleware = [], + platform: platform2 + } = config; + const validMiddleware = middleware.filter(Boolean); + const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(floating)); + let rects = await platform2.getElementRects({ + reference, + floating, + strategy + }); + let { + x, + y + } = computeCoordsFromPlacement(rects, placement, rtl); + let statefulPlacement = placement; + let middlewareData = {}; + let resetCount = 0; + for (let i = 0; i < validMiddleware.length; i++) { + var _platform$detectOverf; + const { + name, + fn + } = validMiddleware[i]; + const { + x: nextX, + y: nextY, + data, + reset + } = await fn({ + x, + y, + initialPlacement: placement, + placement: statefulPlacement, + strategy, + middlewareData, + rects, + platform: { + ...platform2, + detectOverflow: (_platform$detectOverf = platform2.detectOverflow) != null ? _platform$detectOverf : detectOverflow + }, + elements: { + reference, + floating + } + }); + x = nextX != null ? nextX : x; + y = nextY != null ? nextY : y; + middlewareData = { + ...middlewareData, + [name]: { + ...middlewareData[name], + ...data + } + }; + if (reset && resetCount <= 50) { + resetCount++; + if (typeof reset === "object") { + if (reset.placement) { + statefulPlacement = reset.placement; + } + if (reset.rects) { + rects = reset.rects === true ? await platform2.getElementRects({ + reference, + floating, + strategy + }) : reset.rects; + } + ({ + x, + y + } = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); + } + i = -1; + } + } + return { + x, + y, + placement: statefulPlacement, + strategy, + middlewareData + }; +}; +var arrow = (options) => ({ + name: "arrow", + options, + async fn(state) { + const { + x, + y, + placement, + rects, + platform: platform2, + elements, + middlewareData + } = state; + const { + element, + padding = 0 + } = evaluate(options, state) || {}; + if (element == null) { + return {}; + } + const paddingObject = getPaddingObject(padding); + const coords = { + x, + y + }; + const axis = getAlignmentAxis(placement); + const length = getAxisLength(axis); + const arrowDimensions = await platform2.getDimensions(element); + const isYAxis = axis === "y"; + const minProp = isYAxis ? "top" : "left"; + const maxProp = isYAxis ? "bottom" : "right"; + const clientProp = isYAxis ? "clientHeight" : "clientWidth"; + const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length]; + const startDiff = coords[axis] - rects.reference[axis]; + const arrowOffsetParent = await (platform2.getOffsetParent == null ? void 0 : platform2.getOffsetParent(element)); + let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0; + if (!clientSize || !await (platform2.isElement == null ? void 0 : platform2.isElement(arrowOffsetParent))) { + clientSize = elements.floating[clientProp] || rects.floating[length]; + } + const centerToReference = endDiff / 2 - startDiff / 2; + const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1; + const minPadding = min(paddingObject[minProp], largestPossiblePadding); + const maxPadding = min(paddingObject[maxProp], largestPossiblePadding); + const min$1 = minPadding; + const max2 = clientSize - arrowDimensions[length] - maxPadding; + const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference; + const offset3 = clamp(min$1, center, max2); + const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset3 && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0; + const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max2 : 0; + return { + [axis]: coords[axis] + alignmentOffset, + data: { + [axis]: offset3, + centerOffset: center - offset3 - alignmentOffset, + ...shouldAddOffset && { + alignmentOffset + } + }, + reset: shouldAddOffset + }; + } +}); +var flip = function(options) { + if (options === void 0) { + options = {}; + } + return { + name: "flip", + options, + async fn(state) { + var _middlewareData$arrow, _middlewareData$flip; + const { + placement, + middlewareData, + rects, + initialPlacement, + platform: platform2, + elements + } = state; + const { + mainAxis: checkMainAxis = true, + crossAxis: checkCrossAxis = true, + fallbackPlacements: specifiedFallbackPlacements, + fallbackStrategy = "bestFit", + fallbackAxisSideDirection = "none", + flipAlignment = true, + ...detectOverflowOptions + } = evaluate(options, state); + if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { + return {}; + } + const side = getSide(placement); + const initialSideAxis = getSideAxis(initialPlacement); + const isBasePlacement = getSide(initialPlacement) === initialPlacement; + const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)); + const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); + const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== "none"; + if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) { + fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl)); + } + const placements2 = [initialPlacement, ...fallbackPlacements]; + const overflow = await platform2.detectOverflow(state, detectOverflowOptions); + const overflows = []; + let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || []; + if (checkMainAxis) { + overflows.push(overflow[side]); + } + if (checkCrossAxis) { + const sides2 = getAlignmentSides(placement, rects, rtl); + overflows.push(overflow[sides2[0]], overflow[sides2[1]]); + } + overflowsData = [...overflowsData, { + placement, + overflows + }]; + if (!overflows.every((side2) => side2 <= 0)) { + var _middlewareData$flip2, _overflowsData$filter; + const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1; + const nextPlacement = placements2[nextIndex]; + if (nextPlacement) { + const ignoreCrossAxisOverflow = checkCrossAxis === "alignment" ? initialSideAxis !== getSideAxis(nextPlacement) : false; + if (!ignoreCrossAxisOverflow || // We leave the current main axis only if every placement on that axis + // overflows the main axis. + overflowsData.every((d) => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) { + return { + data: { + index: nextIndex, + overflows: overflowsData + }, + reset: { + placement: nextPlacement + } + }; + } + } + let resetPlacement = (_overflowsData$filter = overflowsData.filter((d) => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement; + if (!resetPlacement) { + switch (fallbackStrategy) { + case "bestFit": { + var _overflowsData$filter2; + const placement2 = (_overflowsData$filter2 = overflowsData.filter((d) => { + if (hasFallbackAxisSideDirection) { + const currentSideAxis = getSideAxis(d.placement); + return currentSideAxis === initialSideAxis || // Create a bias to the `y` side axis due to horizontal + // reading directions favoring greater width. + currentSideAxis === "y"; + } + return true; + }).map((d) => [d.placement, d.overflows.filter((overflow2) => overflow2 > 0).reduce((acc, overflow2) => acc + overflow2, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0]; + if (placement2) { + resetPlacement = placement2; + } + break; + } + case "initialPlacement": + resetPlacement = initialPlacement; + break; + } + } + if (placement !== resetPlacement) { + return { + reset: { + placement: resetPlacement + } + }; + } + } + return {}; + } + }; +}; +function getSideOffsets(overflow, rect) { + return { + top: overflow.top - rect.height, + right: overflow.right - rect.width, + bottom: overflow.bottom - rect.height, + left: overflow.left - rect.width + }; +} +function isAnySideFullyClipped(overflow) { + return sides.some((side) => overflow[side] >= 0); +} +var hide = function(options) { + if (options === void 0) { + options = {}; + } + return { + name: "hide", + options, + async fn(state) { + const { + rects, + platform: platform2 + } = state; + const { + strategy = "referenceHidden", + ...detectOverflowOptions + } = evaluate(options, state); + switch (strategy) { + case "referenceHidden": { + const overflow = await platform2.detectOverflow(state, { + ...detectOverflowOptions, + elementContext: "reference" + }); + const offsets = getSideOffsets(overflow, rects.reference); + return { + data: { + referenceHiddenOffsets: offsets, + referenceHidden: isAnySideFullyClipped(offsets) + } + }; + } + case "escaped": { + const overflow = await platform2.detectOverflow(state, { + ...detectOverflowOptions, + altBoundary: true + }); + const offsets = getSideOffsets(overflow, rects.floating); + return { + data: { + escapedOffsets: offsets, + escaped: isAnySideFullyClipped(offsets) + } + }; + } + default: { + return {}; + } + } + } + }; +}; +var originSides = /* @__PURE__ */ new Set(["left", "top"]); +async function convertValueToCoords(state, options) { + const { + placement, + platform: platform2, + elements + } = state; + const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)); + const side = getSide(placement); + const alignment = getAlignment(placement); + const isVertical = getSideAxis(placement) === "y"; + const mainAxisMulti = originSides.has(side) ? -1 : 1; + const crossAxisMulti = rtl && isVertical ? -1 : 1; + const rawValue = evaluate(options, state); + let { + mainAxis, + crossAxis, + alignmentAxis + } = typeof rawValue === "number" ? { + mainAxis: rawValue, + crossAxis: 0, + alignmentAxis: null + } : { + mainAxis: rawValue.mainAxis || 0, + crossAxis: rawValue.crossAxis || 0, + alignmentAxis: rawValue.alignmentAxis + }; + if (alignment && typeof alignmentAxis === "number") { + crossAxis = alignment === "end" ? alignmentAxis * -1 : alignmentAxis; + } + return isVertical ? { + x: crossAxis * crossAxisMulti, + y: mainAxis * mainAxisMulti + } : { + x: mainAxis * mainAxisMulti, + y: crossAxis * crossAxisMulti + }; +} +var offset = function(options) { + if (options === void 0) { + options = 0; + } + return { + name: "offset", + options, + async fn(state) { + var _middlewareData$offse, _middlewareData$arrow; + const { + x, + y, + placement, + middlewareData + } = state; + const diffCoords = await convertValueToCoords(state, options); + if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { + return {}; + } + return { + x: x + diffCoords.x, + y: y + diffCoords.y, + data: { + ...diffCoords, + placement + } + }; + } + }; +}; +var shift = function(options) { + if (options === void 0) { + options = {}; + } + return { + name: "shift", + options, + async fn(state) { + const { + x, + y, + placement, + platform: platform2 + } = state; + const { + mainAxis: checkMainAxis = true, + crossAxis: checkCrossAxis = false, + limiter = { + fn: (_ref) => { + let { + x: x2, + y: y2 + } = _ref; + return { + x: x2, + y: y2 + }; + } + }, + ...detectOverflowOptions + } = evaluate(options, state); + const coords = { + x, + y + }; + const overflow = await platform2.detectOverflow(state, detectOverflowOptions); + const crossAxis = getSideAxis(getSide(placement)); + const mainAxis = getOppositeAxis(crossAxis); + let mainAxisCoord = coords[mainAxis]; + let crossAxisCoord = coords[crossAxis]; + if (checkMainAxis) { + const minSide = mainAxis === "y" ? "top" : "left"; + const maxSide = mainAxis === "y" ? "bottom" : "right"; + const min2 = mainAxisCoord + overflow[minSide]; + const max2 = mainAxisCoord - overflow[maxSide]; + mainAxisCoord = clamp(min2, mainAxisCoord, max2); + } + if (checkCrossAxis) { + const minSide = crossAxis === "y" ? "top" : "left"; + const maxSide = crossAxis === "y" ? "bottom" : "right"; + const min2 = crossAxisCoord + overflow[minSide]; + const max2 = crossAxisCoord - overflow[maxSide]; + crossAxisCoord = clamp(min2, crossAxisCoord, max2); + } + const limitedCoords = limiter.fn({ + ...state, + [mainAxis]: mainAxisCoord, + [crossAxis]: crossAxisCoord + }); + return { + ...limitedCoords, + data: { + x: limitedCoords.x - x, + y: limitedCoords.y - y, + enabled: { + [mainAxis]: checkMainAxis, + [crossAxis]: checkCrossAxis + } + } + }; + } + }; +}; +var limitShift = function(options) { + if (options === void 0) { + options = {}; + } + return { + options, + fn(state) { + const { + x, + y, + placement, + rects, + middlewareData + } = state; + const { + offset: offset3 = 0, + mainAxis: checkMainAxis = true, + crossAxis: checkCrossAxis = true + } = evaluate(options, state); + const coords = { + x, + y + }; + const crossAxis = getSideAxis(placement); + const mainAxis = getOppositeAxis(crossAxis); + let mainAxisCoord = coords[mainAxis]; + let crossAxisCoord = coords[crossAxis]; + const rawOffset = evaluate(offset3, state); + const computedOffset = typeof rawOffset === "number" ? { + mainAxis: rawOffset, + crossAxis: 0 + } : { + mainAxis: 0, + crossAxis: 0, + ...rawOffset + }; + if (checkMainAxis) { + const len = mainAxis === "y" ? "height" : "width"; + const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis; + const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis; + if (mainAxisCoord < limitMin) { + mainAxisCoord = limitMin; + } else if (mainAxisCoord > limitMax) { + mainAxisCoord = limitMax; + } + } + if (checkCrossAxis) { + var _middlewareData$offse, _middlewareData$offse2; + const len = mainAxis === "y" ? "width" : "height"; + const isOriginSide = originSides.has(getSide(placement)); + const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis); + const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0); + if (crossAxisCoord < limitMin) { + crossAxisCoord = limitMin; + } else if (crossAxisCoord > limitMax) { + crossAxisCoord = limitMax; + } + } + return { + [mainAxis]: mainAxisCoord, + [crossAxis]: crossAxisCoord + }; + } + }; +}; +var size = function(options) { + if (options === void 0) { + options = {}; + } + return { + name: "size", + options, + async fn(state) { + var _state$middlewareData, _state$middlewareData2; + const { + placement, + rects, + platform: platform2, + elements + } = state; + const { + apply = () => { + }, + ...detectOverflowOptions + } = evaluate(options, state); + const overflow = await platform2.detectOverflow(state, detectOverflowOptions); + const side = getSide(placement); + const alignment = getAlignment(placement); + const isYAxis = getSideAxis(placement) === "y"; + const { + width, + height + } = rects.floating; + let heightSide; + let widthSide; + if (side === "top" || side === "bottom") { + heightSide = side; + widthSide = alignment === (await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)) ? "start" : "end") ? "left" : "right"; + } else { + widthSide = side; + heightSide = alignment === "end" ? "top" : "bottom"; + } + const maximumClippingHeight = height - overflow.top - overflow.bottom; + const maximumClippingWidth = width - overflow.left - overflow.right; + const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight); + const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth); + const noShift = !state.middlewareData.shift; + let availableHeight = overflowAvailableHeight; + let availableWidth = overflowAvailableWidth; + if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) { + availableWidth = maximumClippingWidth; + } + if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) { + availableHeight = maximumClippingHeight; + } + if (noShift && !alignment) { + const xMin = max(overflow.left, 0); + const xMax = max(overflow.right, 0); + const yMin = max(overflow.top, 0); + const yMax = max(overflow.bottom, 0); + if (isYAxis) { + availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : max(overflow.left, overflow.right)); + } else { + availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : max(overflow.top, overflow.bottom)); + } + } + await apply({ + ...state, + availableWidth, + availableHeight + }); + const nextDimensions = await platform2.getDimensions(elements.floating); + if (width !== nextDimensions.width || height !== nextDimensions.height) { + return { + reset: { + rects: true + } + }; + } + return {}; + } + }; +}; + +// ../node_modules/.pnpm/@floating-ui+utils@0.2.10/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.mjs +function hasWindow() { + return typeof window !== "undefined"; +} +function getNodeName(node) { + if (isNode(node)) { + return (node.nodeName || "").toLowerCase(); + } + return "#document"; +} +function getWindow2(node) { + var _node$ownerDocument; + return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; +} +function getDocumentElement(node) { + var _ref; + return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement; +} +function isNode(value) { + if (!hasWindow()) { + return false; + } + return value instanceof Node || value instanceof getWindow2(value).Node; +} +function isElement(value) { + if (!hasWindow()) { + return false; + } + return value instanceof Element || value instanceof getWindow2(value).Element; +} +function isHTMLElement2(value) { + if (!hasWindow()) { + return false; + } + return value instanceof HTMLElement || value instanceof getWindow2(value).HTMLElement; +} +function isShadowRoot(value) { + if (!hasWindow() || typeof ShadowRoot === "undefined") { + return false; + } + return value instanceof ShadowRoot || value instanceof getWindow2(value).ShadowRoot; +} +var invalidOverflowDisplayValues = /* @__PURE__ */ new Set(["inline", "contents"]); +function isOverflowElement(element) { + const { + overflow, + overflowX, + overflowY, + display + } = getComputedStyle3(element); + return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !invalidOverflowDisplayValues.has(display); +} +var tableElements = /* @__PURE__ */ new Set(["table", "td", "th"]); +function isTableElement(element) { + return tableElements.has(getNodeName(element)); +} +var topLayerSelectors = [":popover-open", ":modal"]; +function isTopLayer(element) { + return topLayerSelectors.some((selector) => { + try { + return element.matches(selector); + } catch (_e) { + return false; + } + }); +} +var transformProperties = ["transform", "translate", "scale", "rotate", "perspective"]; +var willChangeValues = ["transform", "translate", "scale", "rotate", "perspective", "filter"]; +var containValues = ["paint", "layout", "strict", "content"]; +function isContainingBlock(elementOrCss) { + const webkit = isWebKit(); + const css = isElement(elementOrCss) ? getComputedStyle3(elementOrCss) : elementOrCss; + return transformProperties.some((value) => css[value] ? css[value] !== "none" : false) || (css.containerType ? css.containerType !== "normal" : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== "none" : false) || !webkit && (css.filter ? css.filter !== "none" : false) || willChangeValues.some((value) => (css.willChange || "").includes(value)) || containValues.some((value) => (css.contain || "").includes(value)); +} +function getContainingBlock(element) { + let currentNode = getParentNode(element); + while (isHTMLElement2(currentNode) && !isLastTraversableNode(currentNode)) { + if (isContainingBlock(currentNode)) { + return currentNode; + } else if (isTopLayer(currentNode)) { + return null; + } + currentNode = getParentNode(currentNode); + } + return null; +} +function isWebKit() { + if (typeof CSS === "undefined" || !CSS.supports) return false; + return CSS.supports("-webkit-backdrop-filter", "none"); +} +var lastTraversableNodeNames = /* @__PURE__ */ new Set(["html", "body", "#document"]); +function isLastTraversableNode(node) { + return lastTraversableNodeNames.has(getNodeName(node)); +} +function getComputedStyle3(element) { + return getWindow2(element).getComputedStyle(element); +} +function getNodeScroll(element) { + if (isElement(element)) { + return { + scrollLeft: element.scrollLeft, + scrollTop: element.scrollTop + }; + } + return { + scrollLeft: element.scrollX, + scrollTop: element.scrollY + }; +} +function getParentNode(node) { + if (getNodeName(node) === "html") { + return node; + } + const result = ( + // Step into the shadow DOM of the parent of a slotted node. + node.assignedSlot || // DOM Element detected. + node.parentNode || // ShadowRoot detected. + isShadowRoot(node) && node.host || // Fallback. + getDocumentElement(node) + ); + return isShadowRoot(result) ? result.host : result; +} +function getNearestOverflowAncestor(node) { + const parentNode = getParentNode(node); + if (isLastTraversableNode(parentNode)) { + return node.ownerDocument ? node.ownerDocument.body : node.body; + } + if (isHTMLElement2(parentNode) && isOverflowElement(parentNode)) { + return parentNode; + } + return getNearestOverflowAncestor(parentNode); +} +function getOverflowAncestors(node, list, traverseIframes) { + var _node$ownerDocument2; + if (list === void 0) { + list = []; + } + if (traverseIframes === void 0) { + traverseIframes = true; + } + const scrollableAncestor = getNearestOverflowAncestor(node); + const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body); + const win = getWindow2(scrollableAncestor); + if (isBody) { + const frameElement = getFrameElement(win); + return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []); + } + return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes)); +} +function getFrameElement(win) { + return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null; +} + +// ../node_modules/.pnpm/@floating-ui+dom@1.7.5/node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs +function getCssDimensions(element) { + const css = getComputedStyle3(element); + let width = parseFloat(css.width) || 0; + let height = parseFloat(css.height) || 0; + const hasOffset = isHTMLElement2(element); + const offsetWidth = hasOffset ? element.offsetWidth : width; + const offsetHeight = hasOffset ? element.offsetHeight : height; + const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight; + if (shouldFallback) { + width = offsetWidth; + height = offsetHeight; + } + return { + width, + height, + $: shouldFallback + }; +} +function unwrapElement(element) { + return !isElement(element) ? element.contextElement : element; +} +function getScale(element) { + const domElement = unwrapElement(element); + if (!isHTMLElement2(domElement)) { + return createCoords(1); + } + const rect = domElement.getBoundingClientRect(); + const { + width, + height, + $ + } = getCssDimensions(domElement); + let x = ($ ? round(rect.width) : rect.width) / width; + let y = ($ ? round(rect.height) : rect.height) / height; + if (!x || !Number.isFinite(x)) { + x = 1; + } + if (!y || !Number.isFinite(y)) { + y = 1; + } + return { + x, + y + }; +} +var noOffsets = /* @__PURE__ */ createCoords(0); +function getVisualOffsets(element) { + const win = getWindow2(element); + if (!isWebKit() || !win.visualViewport) { + return noOffsets; + } + return { + x: win.visualViewport.offsetLeft, + y: win.visualViewport.offsetTop + }; +} +function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) { + if (isFixed === void 0) { + isFixed = false; + } + if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow2(element)) { + return false; + } + return isFixed; +} +function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) { + if (includeScale === void 0) { + includeScale = false; + } + if (isFixedStrategy === void 0) { + isFixedStrategy = false; + } + const clientRect = element.getBoundingClientRect(); + const domElement = unwrapElement(element); + let scale = createCoords(1); + if (includeScale) { + if (offsetParent) { + if (isElement(offsetParent)) { + scale = getScale(offsetParent); + } + } else { + scale = getScale(element); + } + } + const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0); + let x = (clientRect.left + visualOffsets.x) / scale.x; + let y = (clientRect.top + visualOffsets.y) / scale.y; + let width = clientRect.width / scale.x; + let height = clientRect.height / scale.y; + if (domElement) { + const win = getWindow2(domElement); + const offsetWin = offsetParent && isElement(offsetParent) ? getWindow2(offsetParent) : offsetParent; + let currentWin = win; + let currentIFrame = getFrameElement(currentWin); + while (currentIFrame && offsetParent && offsetWin !== currentWin) { + const iframeScale = getScale(currentIFrame); + const iframeRect = currentIFrame.getBoundingClientRect(); + const css = getComputedStyle3(currentIFrame); + const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; + const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; + x *= iframeScale.x; + y *= iframeScale.y; + width *= iframeScale.x; + height *= iframeScale.y; + x += left; + y += top; + currentWin = getWindow2(currentIFrame); + currentIFrame = getFrameElement(currentWin); + } + } + return rectToClientRect({ + width, + height, + x, + y + }); +} +function getWindowScrollBarX(element, rect) { + const leftScroll = getNodeScroll(element).scrollLeft; + if (!rect) { + return getBoundingClientRect(getDocumentElement(element)).left + leftScroll; + } + return rect.left + leftScroll; +} +function getHTMLOffset(documentElement, scroll) { + const htmlRect = documentElement.getBoundingClientRect(); + const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect); + const y = htmlRect.top + scroll.scrollTop; + return { + x, + y + }; +} +function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { + let { + elements, + rect, + offsetParent, + strategy + } = _ref; + const isFixed = strategy === "fixed"; + const documentElement = getDocumentElement(offsetParent); + const topLayer = elements ? isTopLayer(elements.floating) : false; + if (offsetParent === documentElement || topLayer && isFixed) { + return rect; + } + let scroll = { + scrollLeft: 0, + scrollTop: 0 + }; + let scale = createCoords(1); + const offsets = createCoords(0); + const isOffsetParentAnElement = isHTMLElement2(offsetParent); + if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { + if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { + scroll = getNodeScroll(offsetParent); + } + if (isHTMLElement2(offsetParent)) { + const offsetRect = getBoundingClientRect(offsetParent); + scale = getScale(offsetParent); + offsets.x = offsetRect.x + offsetParent.clientLeft; + offsets.y = offsetRect.y + offsetParent.clientTop; + } + } + const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); + return { + width: rect.width * scale.x, + height: rect.height * scale.y, + x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x, + y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.y + }; +} +function getClientRects(element) { + return Array.from(element.getClientRects()); +} +function getDocumentRect(element) { + const html = getDocumentElement(element); + const scroll = getNodeScroll(element); + const body = element.ownerDocument.body; + const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth); + const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); + let x = -scroll.scrollLeft + getWindowScrollBarX(element); + const y = -scroll.scrollTop; + if (getComputedStyle3(body).direction === "rtl") { + x += max(html.clientWidth, body.clientWidth) - width; + } + return { + width, + height, + x, + y + }; +} +var SCROLLBAR_MAX = 25; +function getViewportRect(element, strategy) { + const win = getWindow2(element); + const html = getDocumentElement(element); + const visualViewport = win.visualViewport; + let width = html.clientWidth; + let height = html.clientHeight; + let x = 0; + let y = 0; + if (visualViewport) { + width = visualViewport.width; + height = visualViewport.height; + const visualViewportBased = isWebKit(); + if (!visualViewportBased || visualViewportBased && strategy === "fixed") { + x = visualViewport.offsetLeft; + y = visualViewport.offsetTop; + } + } + const windowScrollbarX = getWindowScrollBarX(html); + if (windowScrollbarX <= 0) { + const doc = html.ownerDocument; + const body = doc.body; + const bodyStyles = getComputedStyle(body); + const bodyMarginInline = doc.compatMode === "CSS1Compat" ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0; + const clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline); + if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) { + width -= clippingStableScrollbarWidth; + } + } else if (windowScrollbarX <= SCROLLBAR_MAX) { + width += windowScrollbarX; + } + return { + width, + height, + x, + y + }; +} +var absoluteOrFixed = /* @__PURE__ */ new Set(["absolute", "fixed"]); +function getInnerBoundingClientRect(element, strategy) { + const clientRect = getBoundingClientRect(element, true, strategy === "fixed"); + const top = clientRect.top + element.clientTop; + const left = clientRect.left + element.clientLeft; + const scale = isHTMLElement2(element) ? getScale(element) : createCoords(1); + const width = element.clientWidth * scale.x; + const height = element.clientHeight * scale.y; + const x = left * scale.x; + const y = top * scale.y; + return { + width, + height, + x, + y + }; +} +function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) { + let rect; + if (clippingAncestor === "viewport") { + rect = getViewportRect(element, strategy); + } else if (clippingAncestor === "document") { + rect = getDocumentRect(getDocumentElement(element)); + } else if (isElement(clippingAncestor)) { + rect = getInnerBoundingClientRect(clippingAncestor, strategy); + } else { + const visualOffsets = getVisualOffsets(element); + rect = { + x: clippingAncestor.x - visualOffsets.x, + y: clippingAncestor.y - visualOffsets.y, + width: clippingAncestor.width, + height: clippingAncestor.height + }; + } + return rectToClientRect(rect); +} +function hasFixedPositionAncestor(element, stopNode) { + const parentNode = getParentNode(element); + if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { + return false; + } + return getComputedStyle3(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode); +} +function getClippingElementAncestors(element, cache) { + const cachedResult = cache.get(element); + if (cachedResult) { + return cachedResult; + } + let result = getOverflowAncestors(element, [], false).filter((el) => isElement(el) && getNodeName(el) !== "body"); + let currentContainingBlockComputedStyle = null; + const elementIsFixed = getComputedStyle3(element).position === "fixed"; + let currentNode = elementIsFixed ? getParentNode(element) : element; + while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { + const computedStyle = getComputedStyle3(currentNode); + const currentNodeIsContaining = isContainingBlock(currentNode); + if (!currentNodeIsContaining && computedStyle.position === "fixed") { + currentContainingBlockComputedStyle = null; + } + const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === "static" && !!currentContainingBlockComputedStyle && absoluteOrFixed.has(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode); + if (shouldDropCurrentNode) { + result = result.filter((ancestor) => ancestor !== currentNode); + } else { + currentContainingBlockComputedStyle = computedStyle; + } + currentNode = getParentNode(currentNode); + } + cache.set(element, result); + return result; +} +function getClippingRect(_ref) { + let { + element, + boundary, + rootBoundary, + strategy + } = _ref; + const elementClippingAncestors = boundary === "clippingAncestors" ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary); + const clippingAncestors = [...elementClippingAncestors, rootBoundary]; + const firstClippingAncestor = clippingAncestors[0]; + const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => { + const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy); + accRect.top = max(rect.top, accRect.top); + accRect.right = min(rect.right, accRect.right); + accRect.bottom = min(rect.bottom, accRect.bottom); + accRect.left = max(rect.left, accRect.left); + return accRect; + }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy)); + return { + width: clippingRect.right - clippingRect.left, + height: clippingRect.bottom - clippingRect.top, + x: clippingRect.left, + y: clippingRect.top + }; +} +function getDimensions(element) { + const { + width, + height + } = getCssDimensions(element); + return { + width, + height + }; +} +function getRectRelativeToOffsetParent(element, offsetParent, strategy) { + const isOffsetParentAnElement = isHTMLElement2(offsetParent); + const documentElement = getDocumentElement(offsetParent); + const isFixed = strategy === "fixed"; + const rect = getBoundingClientRect(element, true, isFixed, offsetParent); + let scroll = { + scrollLeft: 0, + scrollTop: 0 + }; + const offsets = createCoords(0); + function setLeftRTLScrollbarOffset() { + offsets.x = getWindowScrollBarX(documentElement); + } + if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { + if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { + scroll = getNodeScroll(offsetParent); + } + if (isOffsetParentAnElement) { + const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent); + offsets.x = offsetRect.x + offsetParent.clientLeft; + offsets.y = offsetRect.y + offsetParent.clientTop; + } else if (documentElement) { + setLeftRTLScrollbarOffset(); + } + } + if (isFixed && !isOffsetParentAnElement && documentElement) { + setLeftRTLScrollbarOffset(); + } + const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); + const x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x; + const y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y; + return { + x, + y, + width: rect.width, + height: rect.height + }; +} +function isStaticPositioned(element) { + return getComputedStyle3(element).position === "static"; +} +function getTrueOffsetParent(element, polyfill) { + if (!isHTMLElement2(element) || getComputedStyle3(element).position === "fixed") { + return null; + } + if (polyfill) { + return polyfill(element); + } + let rawOffsetParent = element.offsetParent; + if (getDocumentElement(element) === rawOffsetParent) { + rawOffsetParent = rawOffsetParent.ownerDocument.body; + } + return rawOffsetParent; +} +function getOffsetParent(element, polyfill) { + const win = getWindow2(element); + if (isTopLayer(element)) { + return win; + } + if (!isHTMLElement2(element)) { + let svgOffsetParent = getParentNode(element); + while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) { + if (isElement(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) { + return svgOffsetParent; + } + svgOffsetParent = getParentNode(svgOffsetParent); + } + return win; + } + let offsetParent = getTrueOffsetParent(element, polyfill); + while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) { + offsetParent = getTrueOffsetParent(offsetParent, polyfill); + } + if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) { + return win; + } + return offsetParent || getContainingBlock(element) || win; +} +var getElementRects = async function(data) { + const getOffsetParentFn = this.getOffsetParent || getOffsetParent; + const getDimensionsFn = this.getDimensions; + const floatingDimensions = await getDimensionsFn(data.floating); + return { + reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy), + floating: { + x: 0, + y: 0, + width: floatingDimensions.width, + height: floatingDimensions.height + } + }; +}; +function isRTL(element) { + return getComputedStyle3(element).direction === "rtl"; +} +var platform = { + convertOffsetParentRelativeRectToViewportRelativeRect, + getDocumentElement, + getClippingRect, + getOffsetParent, + getElementRects, + getClientRects, + getDimensions, + getScale, + isElement, + isRTL +}; +function rectsAreEqual(a, b) { + return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height; +} +function observeMove(element, onMove) { + let io = null; + let timeoutId; + const root = getDocumentElement(element); + function cleanup() { + var _io; + clearTimeout(timeoutId); + (_io = io) == null || _io.disconnect(); + io = null; + } + function refresh(skip, threshold) { + if (skip === void 0) { + skip = false; + } + if (threshold === void 0) { + threshold = 1; + } + cleanup(); + const elementRectForRootMargin = element.getBoundingClientRect(); + const { + left, + top, + width, + height + } = elementRectForRootMargin; + if (!skip) { + onMove(); + } + if (!width || !height) { + return; + } + const insetTop = floor(top); + const insetRight = floor(root.clientWidth - (left + width)); + const insetBottom = floor(root.clientHeight - (top + height)); + const insetLeft = floor(left); + const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px"; + const options = { + rootMargin, + threshold: max(0, min(1, threshold)) || 1 + }; + let isFirstUpdate = true; + function handleObserve(entries) { + const ratio = entries[0].intersectionRatio; + if (ratio !== threshold) { + if (!isFirstUpdate) { + return refresh(); + } + if (!ratio) { + timeoutId = setTimeout(() => { + refresh(false, 1e-7); + }, 1e3); + } else { + refresh(false, ratio); + } + } + if (ratio === 1 && !rectsAreEqual(elementRectForRootMargin, element.getBoundingClientRect())) { + refresh(); + } + isFirstUpdate = false; + } + try { + io = new IntersectionObserver(handleObserve, { + ...options, + // Handle