@ngrok/mantle 0.71.1 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -33
- package/dist/accordion.d.ts +1 -1
- package/dist/agent.json +2 -1
- package/dist/alert-dialog.d.ts +8 -8
- package/dist/alert.d.ts +3 -3
- package/dist/anchor.d.ts +1 -1
- package/dist/{as-child-DQHfEmYB.d.ts → as-child-C2PttRwz.d.ts} +1 -1
- package/dist/badge.d.ts +2 -2
- package/dist/{button-Bq0x5Pv4.d.ts → button-CoGmk7_d.d.ts} +6 -6
- package/dist/button.d.ts +3 -3
- package/dist/card.d.ts +1 -1
- package/dist/checkbox.d.ts +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/checkbox.js.map +1 -1
- package/dist/code-block.d.ts +41 -15
- package/dist/code-block.js +2 -2
- package/dist/code-block.js.map +1 -1
- package/dist/code-block_highlight-utils.d.ts +2 -2
- package/dist/code-block_highlight-utils.js +1 -1
- package/dist/code.d.ts +1 -1
- package/dist/color.d.ts +1 -1
- package/dist/combobox.d.ts +2 -2
- package/dist/combobox.js +1 -1
- package/dist/combobox.js.map +1 -1
- package/dist/command.d.ts +8 -8
- package/dist/command.js +1 -1
- package/dist/compose-refs-DZ3cPi47.js.map +1 -1
- package/dist/{copy-to-clipboard-DjOD_Mwb.js → copy-to-clipboard-CNMRyck4.js} +1 -1
- package/dist/{copy-to-clipboard-DjOD_Mwb.js.map → copy-to-clipboard-CNMRyck4.js.map} +1 -1
- package/dist/data-table.d.ts +3 -3
- package/dist/data-table.js +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/{deep-non-nullable-VFm1T3JZ.d.ts → deep-non-nullable-CT7hWCFG.d.ts} +1 -1
- package/dist/description-list.d.ts +1 -1
- package/dist/{dialog-BHzl9eye.js → dialog-B1KCB7JT.js} +2 -2
- package/dist/dialog-B1KCB7JT.js.map +1 -0
- package/dist/dialog.d.ts +2 -2
- package/dist/dialog.js +1 -1
- package/dist/{direction-DtBAQn7p.d.ts → direction-CVntIxOS.d.ts} +1 -1
- package/dist/{direction-DsB-pD9V.js → direction-HqPHXGIs.js} +1 -1
- package/dist/{direction-DsB-pD9V.js.map → direction-HqPHXGIs.js.map} +1 -1
- package/dist/{dropdown-menu-CzUNYIfA.d.ts → dropdown-menu-DVvNlA72.d.ts} +2 -2
- package/dist/{dropdown-menu-Ducs2SEn.js → dropdown-menu-DY4w933w.js} +2 -2
- package/dist/{dropdown-menu-Ducs2SEn.js.map → dropdown-menu-DY4w933w.js.map} +1 -1
- package/dist/dropdown-menu.d.ts +1 -1
- package/dist/dropdown-menu.js +1 -1
- package/dist/empty.d.ts +2 -2
- package/dist/field.d.ts +569 -0
- package/dist/field.js +2 -0
- package/dist/field.js.map +1 -0
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +1 -1
- package/dist/hooks.js.map +1 -1
- package/dist/{icon-DKMJm20j.d.ts → icon-D_BMDi_q.d.ts} +2 -2
- package/dist/{icon-button-BnK4K7YK.d.ts → icon-button-Dty-yfE2.d.ts} +3 -3
- package/dist/icon.d.ts +3 -3
- package/dist/icons.d.ts +3 -3
- package/dist/icons.js +1 -1
- package/dist/{in-view-pia_SVdE.js → in-view-BLZVEGFC.js} +1 -1
- package/dist/{in-view-pia_SVdE.js.map → in-view-BLZVEGFC.js.map} +1 -1
- package/dist/{in-view-Da08Bx6l.d.ts → in-view-DdIrfU4u.d.ts} +1 -1
- package/dist/{index-DkMUaYsw.d.ts → index-CVk4t5hk.d.ts} +1 -1
- package/dist/{index-DOJUH34Z.d.ts → index-DIBURJqf.d.ts} +3 -3
- package/dist/{index-rtz7SwEq.d.ts → index-TI92Xpg5.d.ts} +1 -1
- package/dist/index-j46YISoN.d.ts +22 -0
- package/dist/input.d.ts +192 -3
- package/dist/input.js +1 -1
- package/dist/input.js.map +1 -1
- package/dist/{is-input-CUEWaxtA.js → is-input-CEEoHxXN.js} +1 -1
- package/dist/{is-input-CUEWaxtA.js.map → is-input-CEEoHxXN.js.map} +1 -1
- package/dist/{kbd-CAVUiqBT.js → kbd-CbMxDL9E.js} +1 -1
- package/dist/{kbd-CAVUiqBT.js.map → kbd-CbMxDL9E.js.map} +1 -1
- package/dist/kbd.js +1 -1
- package/dist/label-x6FcOpxc.js +2 -0
- package/dist/label-x6FcOpxc.js.map +1 -0
- package/dist/label.d.ts +9 -0
- package/dist/label.js +1 -2
- package/dist/llms.txt +2 -1
- package/dist/mantle.css +73 -0
- package/dist/media-object.d.ts +1 -1
- package/dist/multi-select.d.ts +2 -2
- package/dist/multi-select.js +1 -1
- package/dist/multi-select.js.map +1 -1
- package/dist/otp-input.d.ts +3 -2
- package/dist/otp-input.js +1 -1
- package/dist/otp-input.js.map +1 -1
- package/dist/pagination.d.ts +3 -3
- package/dist/pagination.js +1 -1
- package/dist/popover-CoZxokw_.js +2 -0
- package/dist/popover-CoZxokw_.js.map +1 -0
- package/dist/popover.js +1 -2
- package/dist/{primitive-tyw4V7Vf.d.ts → primitive-Ed9cel2r.d.ts} +1 -1
- package/dist/progress.js +1 -1
- package/dist/progress.js.map +1 -1
- package/dist/radio-group.d.ts +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/resolve-pre-rendered-props-BfWe69-w.js +13 -0
- package/dist/resolve-pre-rendered-props-BfWe69-w.js.map +1 -0
- package/dist/{resolve-pre-rendered-props-CNUnH1fU.d.ts → resolve-pre-rendered-props-DxvamgE6.d.ts} +152 -4
- package/dist/sandboxed-on-click.d.ts +1 -1
- package/dist/{select-DZutJxyr.d.ts → select-8ymlL8kC.d.ts} +3 -3
- package/dist/select-BBB_e15a.js +2 -0
- package/dist/select-BBB_e15a.js.map +1 -0
- package/dist/select.d.ts +1 -1
- package/dist/select.js +1 -1
- package/dist/{separator-DSOIrnhj.js → separator-awchG4LI.js} +1 -1
- package/dist/{separator-DSOIrnhj.js.map → separator-awchG4LI.js.map} +1 -1
- package/dist/separator.d.ts +1 -1
- package/dist/separator.js +1 -1
- package/dist/sheet.d.ts +2 -2
- package/dist/sheet.js +1 -1
- package/dist/sheet.js.map +1 -1
- package/dist/slot.d.ts +2 -22
- package/dist/{sort-DzCsa6Qj.js → sort-mXo37xN2.js} +2 -2
- package/dist/{sort-DzCsa6Qj.js.map → sort-mXo37xN2.js.map} +1 -1
- package/dist/split-button.d.ts +3 -3
- package/dist/split-button.js +1 -1
- package/dist/{svg-only-BtBvFy-N.d.ts → svg-only-CLbMy439.d.ts} +2 -2
- package/dist/switch.d.ts +2 -1
- package/dist/switch.js +1 -1
- package/dist/switch.js.map +1 -1
- package/dist/{table-BsNJBKiq.d.ts → table-BWxS7pXj.d.ts} +1 -1
- package/dist/{table-Cl4nlRMR.js → table-CHd39aT-.js} +1 -1
- package/dist/{table-Cl4nlRMR.js.map → table-CHd39aT-.js.map} +1 -1
- package/dist/table.d.ts +1 -1
- package/dist/table.js +1 -1
- package/dist/tabs.js +1 -1
- package/dist/text-area.d.ts +1 -1
- package/dist/text-area.js +1 -1
- package/dist/text-area.js.map +1 -1
- package/dist/theme.d.ts +2 -2
- package/dist/{themes-DIEYkvNl.d.ts → themes-f2W5S6xS.d.ts} +1 -1
- package/dist/toast.d.ts +3 -3
- package/dist/{traffic-policy-file-C6LHYrIU.js → traffic-policy-file-BwHHdhWJ.js} +1 -1
- package/dist/{traffic-policy-file-C6LHYrIU.js.map → traffic-policy-file-BwHHdhWJ.js.map} +1 -1
- package/dist/{types-DoV0R5Ja.d.ts → types-DnghL1WE.d.ts} +1 -1
- package/dist/types.d.ts +5 -5
- package/dist/use-copy-to-clipboard-CTgtLjUg.js +2 -0
- package/dist/{use-copy-to-clipboard-C7vsjJe-.js.map → use-copy-to-clipboard-CTgtLjUg.js.map} +1 -1
- package/dist/use-isomorphic-layout-effect-CNSD0lhi.js +2 -0
- package/dist/use-isomorphic-layout-effect-CNSD0lhi.js.map +1 -0
- package/dist/{use-prefers-reduced-motion-aXfsyo-k.js → use-prefers-reduced-motion-YUurmkwx.js} +1 -1
- package/dist/{use-prefers-reduced-motion-aXfsyo-k.js.map → use-prefers-reduced-motion-YUurmkwx.js.map} +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +1 -1
- package/dist/validation-BYME8rWN.js +2 -0
- package/dist/validation-BYME8rWN.js.map +1 -0
- package/dist/validation-DF1z7YDr.d.ts +108 -0
- package/dist/{variant-props-DUmSIQK8.d.ts → variant-props-B4io4uA_.d.ts} +2 -2
- package/dist/{with-style-props-3iFrBR08.d.ts → with-style-props-CW8buMhK.d.ts} +1 -1
- package/package.json +15 -10
- package/dist/dialog-BHzl9eye.js.map +0 -1
- package/dist/index-C91lxoX9.d.ts +0 -146
- package/dist/label.js.map +0 -1
- package/dist/popover.js.map +0 -1
- package/dist/resolve-pre-rendered-props-C-kiaLHj.js +0 -13
- package/dist/resolve-pre-rendered-props-C-kiaLHj.js.map +0 -1
- package/dist/select-DOgdZO0Q.js +0 -2
- package/dist/select-DOgdZO0Q.js.map +0 -1
- package/dist/types-DG0WQLTL.d.ts +0 -78
- package/dist/use-copy-to-clipboard-C7vsjJe-.js +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.js","names":[],"sources":["../src/components/field/field-context.ts","../src/components/field/error-helpers.ts","../src/components/field/field.tsx"],"sourcesContent":["import { createContext, type ComponentProps } from \"react\";\nimport { parseValidation, type ValidationProp, type ValidationState } from \"./validation.js\";\n\n/**\n * ARIA props that `Field.Control` applies to the field's focusable control.\n */\ntype FieldControlAriaProps = {\n\t/**\n\t * IDREFs for helper or error text that describe the focusable control.\n\t */\n\t\"aria-describedby\"?: string;\n\t/**\n\t * IDREFs for error text that represent the control's current error state.\n\t */\n\t\"aria-errormessage\"?: string;\n\t/**\n\t * The resolved ARIA invalid state for the focusable control.\n\t */\n\t\"aria-invalid\"?: ComponentProps<\"input\">[\"aria-invalid\"];\n};\n\n/**\n * Context value owned by `Field.Item` and consumed by message/control parts.\n *\n * `Field.Item` owns the two stable slot IDs (one for the description, one for\n * the error list) so consumers don't manage ARIA wiring themselves. The IDs\n * are emitted unconditionally; ARIA treats unresolved IDREFs as no-ops, so\n * dangling IDs (when no description or error list is rendered) are harmless.\n */\ntype FieldItemContextValue = {\n\t/**\n\t * Stable ID for the rendered `Field.Description` slot, generated once per\n\t * `Field.Item`.\n\t */\n\tdescriptionId: string;\n\t/**\n\t * Stable ID for the rendered `Field.Errors` / `Field.ErrorList` slot,\n\t * generated once per `Field.Item`.\n\t */\n\terrorId: string;\n\t/**\n\t * `true` while a non-empty `Field.Errors` / `Field.ErrorList` is mounted\n\t * under this `Field.Item`. Used by `Field.Item` to infer an `\"error\"`\n\t * validation state when no explicit `validation` prop is supplied.\n\t */\n\thasErrors: boolean;\n\t/**\n\t * Marks a non-empty `Field.Errors` / `Field.ErrorList` as mounted. Returns\n\t * its cleanup callback.\n\t */\n\tregisterError: () => () => void;\n\t/**\n\t * Validation state inferred or supplied by the surrounding `Field.Item`.\n\t */\n\tvalidation?: ValidationState;\n};\n\n/**\n * Options for resolving the ARIA props that `Field.Control` applies.\n */\ntype ResolveFieldControlAriaPropsOptions = {\n\t/**\n\t * Explicit `aria-invalid` value lifted off the control's child element.\n\t * A non-false value forces the resolved validation state to `\"error\"`.\n\t */\n\t\"aria-invalid\"?: ComponentProps<\"input\">[\"aria-invalid\"];\n\t/**\n\t * The nearest `Field.Item` context, when the control is rendered inside one.\n\t */\n\tcontext: FieldItemContextValue | null;\n\t/**\n\t * Explicit validation override supplied to `Field.Control`.\n\t */\n\tvalidation?: ValidationProp;\n};\n\n/**\n * Context shared by the parts of a single `Field.Item`.\n */\nconst FieldItemContext = createContext<FieldItemContextValue | null>(null);\n\n/**\n * Resolves the ARIA and validation props that `Field.Control` applies to its\n * focusable child. Field owns the `aria-describedby` / `aria-errormessage`\n * contract — the resolver emits the surrounding `Field.Item`'s description and\n * error slot IDs directly, without merging anything from the control element.\n */\nconst resolveFieldControlAriaProps = ({\n\t\"aria-invalid\": ariaInvalid,\n\tcontext,\n\tvalidation,\n}: ResolveFieldControlAriaPropsOptions) => {\n\tconst parsedValidation = parseValidation({\n\t\t\"aria-invalid\": ariaInvalid,\n\t\tdefaultAriaInvalid: false,\n\t\tvalidation: validation ?? context?.validation,\n\t});\n\t// Always emit both slot IDs in aria-describedby when inside a Field.Item.\n\t// Per WAI-ARIA, unresolved IDREFs are ignored by assistive tech, so the\n\t// dangling ID is harmless when the corresponding slot isn't rendered and\n\t// lets us skip mount-time bookkeeping. aria-errormessage is additionally\n\t// spec-gated on aria-invalid=\"true\", so it stays inert until the field is\n\t// actually invalid.\n\tconst ariaDescribedBy = context ? `${context.descriptionId} ${context.errorId}` : undefined;\n\tconst ariaErrorMessage = parsedValidation.isInvalid && context ? context.errorId : undefined;\n\n\treturn {\n\t\tariaProps: {\n\t\t\t\"aria-describedby\": ariaDescribedBy,\n\t\t\t\"aria-errormessage\": ariaErrorMessage,\n\t\t\t\"aria-invalid\": parsedValidation.ariaInvalid,\n\t\t} satisfies FieldControlAriaProps,\n\t\tvalidation: parsedValidation.validation,\n\t};\n};\n\nexport {\n\t//,\n\tFieldItemContext,\n\tresolveFieldControlAriaProps,\n};\nexport type {\n\t//,\n\tFieldControlAriaProps,\n\tFieldItemContextValue,\n};\n","import { Children, Fragment, isValidElement, type ReactNode } from \"react\";\n\n/**\n * A validation message accepted by `Field.Errors`. Non-string absence values\n * are allowed so consumers can pass mapped validator output directly while\n * still rendering only real strings.\n */\ntype FieldErrorMessage = string | null | undefined | false;\n\ntype RenderableContentProps = { children?: ReactNode };\n\n/**\n * Options for checking a manual `Field.ErrorList` subtree.\n */\ntype HasRenderableErrorListChildrenOptions = {\n\t/**\n\t * The list children to inspect before rendering or wiring ARIA IDs.\n\t */\n\tchildren: ReactNode;\n\t/**\n\t * The local `Field.ErrorItem` component type from `field.tsx`. Passed in\n\t * (rather than imported) to avoid a circular dependency between\n\t * `field.tsx` and this helpers module.\n\t */\n\terrorItemType: unknown;\n};\n\n/**\n * Normalizes validator output into display-ready message strings without\n * coupling `Field.Errors` to a specific form library's error object shape.\n */\nconst normalizeErrorMessages = (messages: readonly FieldErrorMessage[] | undefined) =>\n\tmessages\n\t\t?.map((message) => (typeof message === \"string\" ? message.trim() : \"\"))\n\t\t.filter((message) => message.length > 0) ?? [];\n\n/**\n * Returns `true` when the supplied children would produce visible content in\n * a `Field.ErrorItem` — i.e. they are not `null`, `undefined`, `false`, or a\n * whitespace-only string. Used by both `Field.ErrorItem`'s render guard and\n * the `Field.ErrorList` walker so an empty item is identified consistently.\n */\nconst isErrorItemRenderable = (children: ReactNode): boolean => {\n\t// Booleans (including `true`) render nothing in React, so a `cond && expr`\n\t// pattern that resolves to `true` would otherwise count as renderable here.\n\tif (children == null || typeof children === \"boolean\") {\n\t\treturn false;\n\t}\n\tif (typeof children === \"string\" && children.trim().length === 0) {\n\t\treturn false;\n\t}\n\treturn true;\n};\n\n/**\n * Empty-detection walker for `Field.ErrorList`. Recurses through Fragments so\n * error items wrapped in conditional `<>…</>` still register, applies\n * `Field.ErrorItem`'s render guard so empty items do not count as content,\n * and treats any other child (custom component, host element, etc.) as\n * opaque-and-therefore-renderable.\n */\nconst hasRenderableErrorListChildren = ({\n\tchildren,\n\terrorItemType,\n}: HasRenderableErrorListChildrenOptions): boolean => {\n\tlet found = false;\n\n\tChildren.forEach(children, (child) => {\n\t\tif (found || child == null || typeof child === \"boolean\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (typeof child === \"string\") {\n\t\t\tif (child.trim().length > 0) {\n\t\t\t\tfound = true;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isValidElement<RenderableContentProps>(child)) {\n\t\t\tfound = true;\n\t\t\treturn;\n\t\t}\n\n\t\tif (child.type === errorItemType) {\n\t\t\tfound = isErrorItemRenderable(child.props.children);\n\t\t\treturn;\n\t\t}\n\n\t\tif (child.type === Fragment) {\n\t\t\tfound = hasRenderableErrorListChildren({\n\t\t\t\tchildren: child.props.children,\n\t\t\t\terrorItemType,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tfound = true;\n\t});\n\n\treturn found;\n};\n\nexport {\n\t//,\n\thasRenderableErrorListChildren,\n\tisErrorItemRenderable,\n\tnormalizeErrorMessages,\n};\nexport type {\n\t//,\n\tFieldErrorMessage,\n};\n","import { QuestionIcon } from \"@phosphor-icons/react/Question\";\nimport {\n\tcloneElement,\n\ttype ComponentRef,\n\ttype ComponentProps,\n\tforwardRef,\n\tisValidElement,\n\ttype ReactElement,\n\ttype ReactNode,\n\tuseCallback,\n\tuseContext,\n\tuseId,\n\tuseMemo,\n\tuseState,\n} from \"react\";\nimport invariant from \"tiny-invariant\";\nimport { useIsomorphicLayoutEffect } from \"../../hooks/use-isomorphic-layout-effect.js\";\nimport type { WithAsChild } from \"../../types/as-child.js\";\nimport { cx } from \"../../utils/cx/cx.js\";\nimport { IconButton, type IconButtonProps } from \"../button/icon-button.js\";\nimport { Label } from \"../label/label.js\";\nimport { Popover } from \"../popover/index.js\";\nimport { Slot } from \"../slot/index.js\";\nimport {\n\tFieldItemContext,\n\tresolveFieldControlAriaProps,\n\ttype FieldControlAriaProps,\n} from \"./field-context.js\";\nimport {\n\thasRenderableErrorListChildren,\n\tisErrorItemRenderable,\n\tnormalizeErrorMessages,\n\ttype FieldErrorMessage,\n} from \"./error-helpers.js\";\nimport { FieldValidationProvider, resolveValidation, type WithValidation } from \"./validation.js\";\n\n/**\n * Props for the `Field.Errors` convenience renderer. It owns its generated\n * children, so use `Field.ErrorList` / `Field.ErrorItem` directly when custom\n * list contents or polymorphic list markup are needed.\n */\ntype FieldErrorsProps = Omit<ComponentProps<\"ul\">, \"children\" | \"id\"> & {\n\t/**\n\t * Validation messages to render. Strings are trimmed, and empty, nullish,\n\t * or false values are ignored before rendering the list.\n\t */\n\tmessages?: readonly FieldErrorMessage[];\n};\n\n/**\n * Renders a semantic `<fieldset>` for grouping related controls under a\n * single accessible name. Resets the default browser fieldset chrome\n * (border, padding, `min-width` quirk) so it composes cleanly with\n * `Field.Legend` and `Field.Group`. Always renders a real `<fieldset>` so\n * the grouping semantics cannot be accidentally removed.\n *\n * Reach for `Field.Set` when the grouping carries semantic weight — most\n * commonly a `RadioGroup` (where the legend names the question the radios\n * answer) or a set of related checkboxes. For laying out unrelated fields\n * with consistent spacing, prefer `Field.Group` on its own.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Set>\n * <Field.Legend>Notification frequency</Field.Legend>\n * <RadioGroup.Root name=\"frequency\" defaultValue=\"daily\">\n * <RadioGroup.Item value=\"daily\" id=\"freq-daily\">…</RadioGroup.Item>\n * <RadioGroup.Item value=\"weekly\" id=\"freq-weekly\">…</RadioGroup.Item>\n * </RadioGroup.Root>\n * </Field.Set>\n * ```\n */\nconst FieldSet = forwardRef<ComponentRef<\"fieldset\">, ComponentProps<\"fieldset\">>(\n\t({ className, ...props }, ref) => {\n\t\treturn (\n\t\t\t<fieldset\n\t\t\t\tref={ref}\n\t\t\t\tdata-slot=\"field-set\"\n\t\t\t\tclassName={cx(\"flex w-full min-w-0 flex-col gap-4 border-0 p-0\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nFieldSet.displayName = \"FieldSet\";\n\n/**\n * The caption for a `Field.Set`. Always renders a semantic `<legend>` styled\n * to match the `Label` component so a fieldset reads like a section header,\n * and gives screen readers an accessible name for the surrounding group\n * (e.g. \"Notification frequency, group, Daily\").\n *\n * **Spacing.** Has a default `mb-1.5` so the legend sits 6px above the next\n * sibling — matching the figma. We use a margin (not the parent `Field.Set`'s\n * flex `gap`) because `<legend>` has special browser rendering inside a\n * `<fieldset>` that ignores the parent's flex `gap`. Override the default\n * with any `mb-*` utility on `Field.Legend`.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Set>\n * <Field.Legend>Notification frequency</Field.Legend>\n * <RadioGroup.Root name=\"frequency\" defaultValue=\"daily\">\n * <RadioGroup.Item value=\"daily\" id=\"freq-daily\">…</RadioGroup.Item>\n * <RadioGroup.Item value=\"weekly\" id=\"freq-weekly\">…</RadioGroup.Item>\n * </RadioGroup.Root>\n * </Field.Set>\n * ```\n */\nconst Legend = forwardRef<ComponentRef<\"legend\">, ComponentProps<\"legend\">>(\n\t({ className, ...props }, ref) => {\n\t\treturn (\n\t\t\t<legend\n\t\t\t\tref={ref}\n\t\t\t\tdata-slot=\"field-legend\"\n\t\t\t\t// `mb-1.5` (not the parent's `gap-*`) drives the Legend ↔ next-sibling\n\t\t\t\t// spacing because `<legend>` has special browser rendering inside a\n\t\t\t\t// `<fieldset>` that ignores the parent's flex `gap`. Pairs with\n\t\t\t\t// RadioGroup.Item's own `py-1` for a 10px text-bottom-to-radio rhythm\n\t\t\t\t// matching the figma. Override with any `mb-*` utility on Field.Legend.\n\t\t\t\tclassName={cx(\"text-strong mb-1.5 text-sm font-medium font-sans\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nLegend.displayName = \"FieldLegend\";\n\n/**\n * Horizontal layout container for the label area of a field. Aligns a\n * `<Field.Label>` (which may contain `Field.Optional`) with adjacent affordances\n * like a help-icon `Popover.Trigger` on a shared center line with a tight\n * `gap-1`. Center-alignment is used (not baseline) so SVG icon buttons —\n * which have no text baseline — sit visually centered next to the label\n * text rather than dropping to the box bottom.\n *\n * Use this when the label needs sibling decorations that can't live inside\n * the `<Field.Label>` itself (e.g. an interactive help button — clicking inside a\n * `<label>` would forward focus to the associated control). For a label\n * with only an `(Optional)` suffix, place `Field.Optional` directly inside\n * the `<Field.Label>` instead — no `LabelRow` needed.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst LabelRow = forwardRef<ComponentRef<\"div\">, ComponentProps<\"div\"> & WithAsChild>(\n\t({ asChild, className, ...props }, ref) => {\n\t\tconst Comp = asChild ? Slot : \"div\";\n\n\t\treturn (\n\t\t\t<Comp\n\t\t\t\tref={ref}\n\t\t\t\tdata-slot=\"field-label-row\"\n\t\t\t\tclassName={cx(\"flex items-center gap-1\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nLabelRow.displayName = \"FieldLabelRow\";\n\n/**\n * `Popover.Root` re-export for the help-affordance pattern. Pair with\n * `Field.HelpTrigger` (renders a default question-mark `IconButton`) and\n * `Field.HelpContent` (the popover body) to drop a help button into a\n * `Field.LabelRow` without manually wiring `Popover` + `IconButton` +\n * `QuestionIcon`. All `Popover.Root` props are forwarded — pass `modal`,\n * `defaultOpen`, etc. as needed.\n *\n * Popover (not Tooltip) so the affordance is reachable on touch devices.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst Help = Popover.Root;\n\n/**\n * Props for the default help popover trigger. A contextual label is required\n * so repeated help affordances do not all share the same accessible name.\n */\ntype FieldHelpTriggerProps = Partial<Omit<IconButtonProps, \"icon\" | \"label\">> &\n\tPick<IconButtonProps, \"label\"> & {\n\t\t/**\n\t\t * The icon to render inside the trigger button. Defaults to a Phosphor\n\t\t * `QuestionIcon` so the most common case only needs a contextual label.\n\t\t */\n\t\ticon?: ReactNode;\n\t};\n\n/**\n * The trigger for a `Field.Help` popover — a `Popover.Trigger` wired to a\n * ghost-appearance `IconButton` with a default Phosphor `QuestionIcon`.\n * Requires a contextual screen-reader label, and accepts `icon` or other\n * `IconButton` props for visual customization.\n *\n * Pre-styled with `text-body` (matching the figma) so the icon reads as\n * subtle metadata at rest; `IconButton`'s ghost `hover:text-strong` still\n * brightens it on interaction. Carries a default `-my-0.5` so the 24px\n * `xs` button keeps a full click target while contributing only 20px to\n * the `Field.LabelRow` flex line — matching the label's text line-height\n * so the label text is not pushed off-center.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst HelpTrigger = forwardRef<ComponentRef<\"button\">, FieldHelpTriggerProps>(\n\t(\n\t\t{\n\t\t\tappearance = \"ghost\",\n\t\t\tclassName,\n\t\t\ticon = <QuestionIcon />,\n\t\t\tlabel,\n\t\t\tsize = \"xs\",\n\t\t\ttype = \"button\",\n\t\t\t...props\n\t\t},\n\t\tref,\n\t) => (\n\t\t<Popover.Trigger asChild>\n\t\t\t<IconButton\n\t\t\t\tref={ref}\n\t\t\t\tappearance={appearance}\n\t\t\t\t// `-my-0.5` keeps the 24px (`size-6`) `xs` IconButton click target while\n\t\t\t\t// trimming 4px (2px each side) off its flex-line contribution so the row\n\t\t\t\t// height matches the label's 20px line-height. Without this the trigger\n\t\t\t\t// drives the LabelRow to 24px and pushes the label text down 2px.\n\t\t\t\tclassName={cx(\"text-body -my-0.5\", className)}\n\t\t\t\ticon={icon}\n\t\t\t\tlabel={label}\n\t\t\t\tsize={size}\n\t\t\t\ttype={type}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</Popover.Trigger>\n\t),\n);\nHelpTrigger.displayName = \"FieldHelpTrigger\";\n\n/**\n * The popover body for a `Field.Help`. Wraps `Popover.Content` so all\n * positioning / sizing options (`side`, `align`, `preferredWidth`, etc.)\n * work as expected, and overrides the inherited `data-slot` to\n * `field-help-content` so consumers can target it as part of a `Field`\n * subtree without colliding with other popovers.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst HelpContent = forwardRef<ComponentRef<\"div\">, ComponentProps<typeof Popover.Content>>(\n\t(props, ref) => <Popover.Content ref={ref} data-slot=\"field-help-content\" {...props} />,\n);\nHelpContent.displayName = \"FieldHelpContent\";\n\n/**\n * Inline \"(Optional)\" suffix to mark a field as optional. Defaults to the\n * literal string `(Optional)` so the common case is `<Field.Optional />` with\n * no children — pass children to translate or replace the text. Renders a\n * `<span>` in `text-muted` at `text-sm` / `font-normal` so it reads as\n * secondary metadata next to the bolder Label text.\n *\n * Place inside the `<Field.Label>` so screen readers announce it as part of the\n * accessible name (e.g. \"Email, Optional, edit text\"). Pair with a small\n * `gap` on the label's flex layout, or rely on the natural inline spacing.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst Optional = forwardRef<ComponentRef<\"span\">, ComponentProps<\"span\"> & WithAsChild>(\n\t({ asChild, children, className, ...props }, ref) => {\n\t\tconst Comp = asChild ? Slot : \"span\";\n\n\t\treturn (\n\t\t\t<Comp\n\t\t\t\tref={ref}\n\t\t\t\tdata-slot=\"field-optional\"\n\t\t\t\tclassName={cx(\"text-muted text-sm font-normal font-sans\", className)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children ?? \"(Optional)\"}\n\t\t\t</Comp>\n\t\t);\n\t},\n);\nOptional.displayName = \"FieldOptional\";\n\n/**\n * Layout container that stacks multiple `Field.Item`s vertically with\n * `gap-4` between them. This is the default way to compose multiple fields\n * — most forms only need a `Field.Group` of `Field.Item`s. Reach for\n * `Field.Set` + `Field.Legend` only when the grouping carries semantic\n * weight (e.g. a `RadioGroup` or related checkboxes).\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst Group = forwardRef<ComponentRef<\"div\">, ComponentProps<\"div\"> & WithAsChild>(\n\t({ asChild, className, ...props }, ref) => {\n\t\tconst Comp = asChild ? Slot : \"div\";\n\n\t\treturn (\n\t\t\t<Comp\n\t\t\t\tref={ref}\n\t\t\t\tdata-slot=\"field-group\"\n\t\t\t\tclassName={cx(\"flex w-full flex-col gap-4\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nGroup.displayName = \"FieldGroup\";\n\n/**\n * A single form field — `Label`, a control (`Input`, `Select`, `Checkbox`,\n * etc.), and any `Field.Description`, `Field.Errors`, or `Field.ErrorList` siblings stacked\n * vertically with a consistent `gap-1.5` so help and error messaging sit\n * tightly under the input.\n *\n * Renders a plain `<div>` — the `<label htmlFor>` ↔ control association\n * already provides the right semantics for a single field, so no implicit\n * `role` is added. `Field.Item` owns the contextual description/error IDs\n * that `Field.Control` applies to the focusable control. Rendered errors\n * infer an `\"error\"` validation state unless `validation` is supplied as an\n * explicit override.\n *\n * **Single-slot constraint.** A `Field.Item` owns one description ID and one\n * errors ID, so render at most one `Field.Description` and one\n * `Field.Errors` *or* `Field.ErrorList` (not both) per item. A second instance\n * would duplicate the slot `id` in the DOM. Pass multiple messages to\n * `Field.Errors`, or multiple `Field.ErrorItem` children to `Field.ErrorList`,\n * instead.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst Item = forwardRef<ComponentRef<\"div\">, ComponentProps<\"div\"> & WithAsChild & WithValidation>(\n\t({ asChild, children, className, validation: validationProp, ...props }, ref) => {\n\t\tconst Comp = asChild ? Slot : \"div\";\n\t\tconst descriptionId = useId();\n\t\tconst errorId = useId();\n\t\tconst [hasErrors, setHasErrors] = useState(false);\n\t\tconst validation = resolveValidation(validationProp ?? (hasErrors ? \"error\" : undefined));\n\n\t\tconst registerError = useCallback(() => {\n\t\t\tsetHasErrors(true);\n\n\t\t\treturn () => {\n\t\t\t\tsetHasErrors(false);\n\t\t\t};\n\t\t}, []);\n\n\t\tconst context = useMemo(\n\t\t\t() => ({\n\t\t\t\tdescriptionId,\n\t\t\t\terrorId,\n\t\t\t\thasErrors,\n\t\t\t\tregisterError,\n\t\t\t\tvalidation,\n\t\t\t}),\n\t\t\t[descriptionId, errorId, hasErrors, registerError, validation],\n\t\t);\n\n\t\treturn (\n\t\t\t<FieldItemContext.Provider value={context}>\n\t\t\t\t<FieldValidationProvider validation={validation}>\n\t\t\t\t\t<Comp\n\t\t\t\t\t\tref={ref}\n\t\t\t\t\t\tdata-slot=\"field-item\"\n\t\t\t\t\t\tdata-validation={validation}\n\t\t\t\t\t\tclassName={cx(\"flex w-full flex-col gap-1.5\", className)}\n\t\t\t\t\t\t{...props}\n\t\t\t\t\t>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</Comp>\n\t\t\t\t</FieldValidationProvider>\n\t\t\t</FieldItemContext.Provider>\n\t\t);\n\t},\n);\nItem.displayName = \"FieldItem\";\n\ntype FieldControlSlotProps = Omit<\n\tComponentProps<typeof Slot>,\n\t\"aria-describedby\" | \"aria-errormessage\" | \"aria-invalid\" | \"children\"\n>;\n\n/**\n * Element-child form of `Field.Control`. Renders via `Slot`, so it accepts\n * any HTML/Slot props and a forwarded ref — those land on the single child\n * element along with the generated ARIA props.\n */\ntype FieldControlElementProps = FieldControlSlotProps &\n\tWithValidation & {\n\t\t/**\n\t\t * A single control element to receive the field ARIA props.\n\t\t */\n\t\tchildren: ReactElement;\n\t};\n\n/**\n * Render-prop form of `Field.Control`. The caller owns the rendered element,\n * so `Field.Control` itself renders nothing — extra DOM props and `ref` have\n * no element to attach to and are intentionally not part of this variant.\n * Slot props are marked `never` so passing e.g. `className` alongside a\n * render-prop child is a type error rather than a silently ignored prop.\n */\ntype FieldControlRenderProps = WithValidation & {\n\t/**\n\t * A render function that places the field ARIA props onto a control of\n\t * the caller's choosing. Used for compound controls or wrappers where\n\t * `Slot` cannot reach the focusable element.\n\t */\n\tchildren: (props: FieldControlAriaProps) => ReactNode;\n} & { [K in keyof FieldControlSlotProps]?: never };\n\n/**\n * Props for `Field.Control`. A discriminated union over how the caller\n * supplies the focusable element:\n *\n * - `FieldControlElementProps` — pass a single React element child and\n * `Field.Control` clones the generated ARIA props onto it via `Slot`.\n * Accepts the full Slot prop surface plus a forwarded ref.\n * - `FieldControlRenderProps` — pass a render function that receives the\n * ARIA props and places them on a control of the caller's choosing. The\n * caller owns the rendered element, so DOM/Slot props and `ref` are\n * excluded from this variant at the type level.\n *\n * @example\n * ```tsx\n * // Element form — Slot clones ARIA props onto <Input/>\n * <Field.Control>\n * <Input id=\"email\" name=\"email\" />\n * </Field.Control>\n *\n * // Render-prop form — caller spreads ARIA props onto its own element\n * <Field.Control>\n * {(ariaProps) => (\n * <label>\n * Accept terms\n * <input type=\"checkbox\" {...ariaProps} />\n * </label>\n * )}\n * </Field.Control>\n * ```\n */\ntype FieldControlProps = FieldControlElementProps | FieldControlRenderProps;\n\n/**\n * Applies `Field.Item` description, error, and validation state to a single\n * focusable control. It always behaves like an `asChild` slot: pass one child\n * element to receive the generated ARIA props, or use a function child to\n * place those props manually. For compound controls, wrap the focusable part\n * that receives ARIA props (for example `Select.Trigger`, not `Select.Root`).\n * Pass `validation` here only when the control needs to override the\n * surrounding `Field.Item` state.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst Control = forwardRef<HTMLElement, FieldControlProps>(\n\t({ children, validation, ...props }, ref) => {\n\t\tconst context = useContext(FieldItemContext);\n\n\t\tif (typeof children === \"function\") {\n\t\t\tconst baseControlState = resolveFieldControlAriaProps({ context, validation });\n\t\t\treturn (\n\t\t\t\t<FieldValidationProvider validation={baseControlState.validation}>\n\t\t\t\t\t{children(baseControlState.ariaProps)}\n\t\t\t\t</FieldValidationProvider>\n\t\t\t);\n\t\t}\n\n\t\tinvariant(\n\t\t\tisValidElement<FieldControlAriaProps>(children),\n\t\t\t\"Field.Control expects a single React element child (or a render-prop function). Got a non-element value (string, array, fragment, null, or undefined). Wrap the control in a single element, or use the function child form: <Field.Control>{(props) => <YourControl {...props} />}</Field.Control>.\",\n\t\t);\n\n\t\t// `aria-describedby` and `aria-errormessage` are owned by Field; values\n\t\t// supplied on the child element are overwritten by `cloneElement`\n\t\t// below. `aria-invalid` is the one ARIA prop a child may meaningfully\n\t\t// override (e.g., explicit `aria-invalid=\"false\"` to suppress an\n\t\t// inferred error).\n\t\tconst childControlState = resolveFieldControlAriaProps({\n\t\t\t\"aria-invalid\": children.props[\"aria-invalid\"],\n\t\t\tcontext,\n\t\t\tvalidation,\n\t\t});\n\n\t\treturn (\n\t\t\t<FieldValidationProvider validation={childControlState.validation}>\n\t\t\t\t<Slot ref={ref} {...props}>\n\t\t\t\t\t{cloneElement(children, childControlState.ariaProps)}\n\t\t\t\t</Slot>\n\t\t\t</FieldValidationProvider>\n\t\t);\n\t},\n);\nControl.displayName = \"FieldControl\";\n\n/**\n * Helper / hint text. Renders a `<p>` in the muted body color so it reads\n * as secondary to the bolder content above it. Use inside `Field.Item`, below\n * the control, to clarify expected format or constraints for that single field.\n *\n * **At most one per `Field.Item`.** `Field.Item` owns a single description\n * slot ID and applies it via context, so a second `Field.Description` would\n * duplicate that `id` in the DOM.\n *\n * **Auto-tighten.** When this description sits directly after rendered\n * `Field.Errors` or `Field.ErrorList` output, the parent's `gap-1.5`\n * collapses via a matching negative top margin so error list + helper read as\n * one tight block. Pass any margin utility (`mt-1`, `mt-0`, etc.) to override\n * — the rule's specificity is flattened to `(0,1,0)` so a single user class wins.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst Description = forwardRef<ComponentRef<\"p\">, Omit<ComponentProps<\"p\">, \"id\"> & WithAsChild>(\n\t({ asChild, className, ...props }, ref) => {\n\t\tconst Comp = asChild ? Slot : \"p\";\n\t\tconst context = useContext(FieldItemContext);\n\n\t\treturn (\n\t\t\t<Comp\n\t\t\t\tref={ref}\n\t\t\t\tdata-slot=\"field-description\"\n\t\t\t\tid={context?.descriptionId}\n\t\t\t\tclassName={cx(\n\t\t\t\t\t\"text-body text-sm leading-4\",\n\t\t\t\t\t// When this description sits directly after a Field.ErrorList\n\t\t\t\t\t// sibling, collapse the parent's gap-1.5 with a matching negative\n\t\t\t\t\t// top margin so the list + helper read as one tight block.\n\t\t\t\t\t// Wrapping the matched selector in :where() flattens its specificity\n\t\t\t\t\t// to (0,1,0) so a user-supplied margin utility (mt-2, mt-0, etc.)\n\t\t\t\t\t// passed on Field.Description still overrides cleanly.\n\t\t\t\t\t\"[:where([data-slot=field-error-list]+&)]:-mt-1.5\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nDescription.displayName = \"FieldDescription\";\n\n/**\n * A single error message list item for a field. Renders an `<li>` in\n * `text-danger-600` so it stands out from a sibling `Field.Description`.\n * Must be rendered inside a `Field.ErrorList`. Empty or blank children render\n * nothing so message-less validator results do not produce empty list items.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.Label htmlFor=\"username\">Username</Field.Label>\n * <Field.Control>\n * <Input id=\"username\" name=\"username\" />\n * </Field.Control>\n * <Field.ErrorList>\n * <Field.ErrorItem>Must be at least 3 characters.</Field.ErrorItem>\n * <Field.ErrorItem>Use letters, numbers, hyphens, or underscores.</Field.ErrorItem>\n * </Field.ErrorList>\n * <Field.Description>Pick something memorable.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst FieldErrorItem = forwardRef<ComponentRef<\"li\">, ComponentProps<\"li\">>(\n\t({ children, className, ...props }, ref) => {\n\t\tif (!isErrorItemRenderable(children)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn (\n\t\t\t<li\n\t\t\t\tref={ref}\n\t\t\t\tdata-slot=\"field-error\"\n\t\t\t\tclassName={cx(\"text-danger-600 text-sm leading-4\", className)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</li>\n\t\t);\n\t},\n);\nFieldErrorItem.displayName = \"FieldErrorItem\";\n\n/**\n * Convenience renderer for string validation messages. Trims each message,\n * filters empty values, and renders a semantic `Field.ErrorList` containing\n * one `Field.ErrorItem` per remaining message.\n *\n * Accepts strings directly so product code can map any validation library's\n * error shape into messages without coupling Mantle to that library.\n * Deliberately does not support `asChild` because it owns the generated list\n * items; use `Field.ErrorList` with `asChild` for custom list markup.\n *\n * **At most one per `Field.Item`.** `Field.Errors` renders a `Field.ErrorList`\n * under the hood, and `Field.Item` owns a single errors slot ID. Use either\n * `Field.Errors` *or* `Field.ErrorList` per item, not both, and pass multiple\n * messages to one `Field.Errors` instead of rendering it twice.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst FieldErrors = forwardRef<ComponentRef<\"ul\">, FieldErrorsProps>(\n\t({ messages, ...props }, ref) => (\n\t\t<FieldErrorList ref={ref} {...props}>\n\t\t\t{normalizeErrorMessages(messages).map((message, index) => (\n\t\t\t\t<FieldErrorItem key={index}>{message}</FieldErrorItem>\n\t\t\t))}\n\t\t</FieldErrorList>\n\t),\n);\nFieldErrors.displayName = \"FieldErrors\";\n\n/**\n * Wraps one or more `Field.ErrorItem` children in a semantic `<ul>` with\n * `role=\"list\"` so a list of validation errors is announced as a list by\n * screen readers, including Safari/VoiceOver combinations that drop list\n * semantics when list styling is removed. Renders nothing when no children are\n * passed, or when all `Field.ErrorItem` children are empty.\n *\n * The list strips its default browser styling (`list-none`, `p-0`, `m-0`) and\n * stacks items as a flex column with no gap so consecutive errors read as a\n * single tight block.\n *\n * **At most one per `Field.Item`.** `Field.Item` owns a single errors slot ID\n * and applies it via context. Use either `Field.ErrorList` *or* `Field.Errors`\n * per item, not both, and put multiple `Field.ErrorItem` children inside the\n * single list instead of rendering two `Field.ErrorList`s.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.Label htmlFor=\"username\">Username</Field.Label>\n * <Field.Control>\n * <Input id=\"username\" name=\"username\" />\n * </Field.Control>\n * <Field.ErrorList>\n * <Field.ErrorItem>Must be at least 3 characters.</Field.ErrorItem>\n * <Field.ErrorItem>Use letters, numbers, hyphens, or underscores.</Field.ErrorItem>\n * </Field.ErrorList>\n * <Field.Description>Pick something memorable.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst FieldErrorList = forwardRef<\n\tComponentRef<\"ul\">,\n\tOmit<ComponentProps<\"ul\">, \"id\"> & WithAsChild\n>(({ asChild, children, className, ...props }, ref) => {\n\tconst hasRenderableChildren = hasRenderableErrorListChildren({\n\t\tchildren,\n\t\terrorItemType: FieldErrorItem,\n\t});\n\tconst context = useContext(FieldItemContext);\n\tconst registerError = context?.registerError;\n\n\tuseIsomorphicLayoutEffect(() => {\n\t\tif (!hasRenderableChildren || registerError == null) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn registerError();\n\t}, [hasRenderableChildren, registerError]);\n\n\tif (!hasRenderableChildren) {\n\t\treturn null;\n\t}\n\n\tconst Comp = asChild ? Slot : \"ul\";\n\n\treturn (\n\t\t<Comp\n\t\t\tref={ref}\n\t\t\tdata-slot=\"field-error-list\"\n\t\t\tid={context?.errorId}\n\t\t\trole=\"list\"\n\t\t\tclassName={cx(\"m-0 flex w-full flex-col list-none p-0\", className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t</Comp>\n\t);\n});\nFieldErrorList.displayName = \"FieldErrorList\";\n\n/**\n * Compound component for semantic, accessible form fields. Composes a\n * `Field.Label`, control (`Input`, `Select`, etc.), `Field.Description`, and\n * validation errors (`Field.Errors` / `Field.ErrorList` + `Field.ErrorItem`)\n * with consistent spacing and ARIA wiring. Stack multiple fields with\n * `Field.Group`; use `Field.Set` + `Field.Legend` for radios / related\n * checkboxes that share one accessible name.\n *\n * @see https://mantle.ngrok.com/components/field\n *\n * @example\n * Composition:\n * ```\n * Field.Group\n * └── Field.Item\n * ├── Field.LabelRow\n * │ ├── Field.Label\n * │ │ └── Field.Optional\n * │ └── Field.Help\n * │ ├── Field.HelpTrigger\n * │ └── Field.HelpContent\n * ├── Field.Control\n * │ └── (control)\n * ├── Field.Errors (or)\n * ├── Field.ErrorList\n * │ └── Field.ErrorItem\n * └── Field.Description\n * ```\n *\n * @example\n * ```tsx\n * <Field.Group>\n * <Field.Item>\n * <Field.LabelRow>\n * <Field.Label htmlFor=\"api-key\">\n * API key <Field.Optional />\n * </Field.Label>\n * <Field.Help>\n * <Field.HelpTrigger label=\"What is an API key?\" />\n * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n * </Field.Help>\n * </Field.LabelRow>\n * <Field.Control>\n * <Input id=\"api-key\" name=\"apiKey\" />\n * </Field.Control>\n * <Field.Errors messages={[\"API key is required.\"]} />\n * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n * </Field.Item>\n * </Field.Group>\n * ```\n */\nconst Field = {\n\t/**\n\t * A single form field. Provides message IDs and validation state to\n\t * `Field.Control`; rendered errors infer `\"error\"` validation.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tItem,\n\t/**\n\t * Applies generated field ARIA props and validation state to a single\n\t * focusable control. Accepts either a single React element child (cloned\n\t * via `Slot`) or a render-prop child that receives the ARIA props for\n\t * cases where `Slot` cannot reach the focusable element — for example a\n\t * `<label>`-wrapped native checkbox, or a third-party component that\n\t * needs the props placed manually on an inner input.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * // Element child — Slot clones the generated ARIA props onto <Input/>.\n\t * <Field.Item>\n\t * <Field.Label htmlFor=\"api-key\">API key</Field.Label>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * </Field.Item>\n\t * ```\n\t *\n\t * @example\n\t * ```tsx\n\t * // Render-prop child — caller spreads the ARIA props onto an inner\n\t * // element when the focusable target is nested (e.g. inside a <label>).\n\t * <Field.Item>\n\t * <Field.Control>\n\t * {(ariaProps) => (\n\t * <label>\n\t * Accept terms\n\t * <input type=\"checkbox\" {...ariaProps} />\n\t * </label>\n\t * )}\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"You must accept the terms.\"]} />\n\t * </Field.Item>\n\t * ```\n\t */\n\tControl,\n\t/**\n\t * Layout container that stacks multiple `Field.Item`s vertically.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tGroup,\n\t/**\n\t * Semantic `<fieldset>` for related controls that share one accessible\n\t * name from `Field.Legend`.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Set>\n\t * <Field.Legend>Notification frequency</Field.Legend>\n\t * <RadioGroup.Root name=\"frequency\" defaultValue=\"daily\">\n\t * <RadioGroup.Item value=\"daily\" id=\"freq-daily\">…</RadioGroup.Item>\n\t * <RadioGroup.Item value=\"weekly\" id=\"freq-weekly\">…</RadioGroup.Item>\n\t * </RadioGroup.Root>\n\t * </Field.Set>\n\t * ```\n\t */\n\tSet: FieldSet,\n\t/**\n\t * Caption for `Field.Set`. Renders a semantic `<legend>`.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Set>\n\t * <Field.Legend>Notification frequency</Field.Legend>\n\t * <RadioGroup.Root name=\"frequency\" defaultValue=\"daily\">\n\t * <RadioGroup.Item value=\"daily\" id=\"freq-daily\">…</RadioGroup.Item>\n\t * <RadioGroup.Item value=\"weekly\" id=\"freq-weekly\">…</RadioGroup.Item>\n\t * </RadioGroup.Root>\n\t * </Field.Set>\n\t * ```\n\t */\n\tLegend,\n\t/**\n\t * The Mantle `Label`, exposed on `Field` for field composition.\n\t *\n\t * @see https://mantle.ngrok.com/components/label\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tLabel,\n\t/**\n\t * Horizontal layout container for the label area of a field. Aligns a\n\t * `<Field.Label>` (which may contain `Field.Optional`) with adjacent affordances\n\t * like a help-icon `Popover.Trigger` on a shared center line with `gap-1`.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tLabelRow,\n\t/**\n\t * `Popover.Root` re-export for the help-affordance pattern. Pair with\n\t * `Field.HelpTrigger` and `Field.HelpContent` to drop a `?` button next to\n\t * a label without manually composing Popover + IconButton + QuestionIcon.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tHelp,\n\t/**\n\t * Trigger for a `Field.Help` popover — a ghost `IconButton` with a default\n\t * `QuestionIcon`. Requires a contextual `label`; pass `icon` to swap the\n\t * glyph, or other `IconButton` props (`size`, `appearance`, etc.) to customize.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tHelpTrigger,\n\t/**\n\t * Body of a `Field.Help` popover. Re-exports `Popover.Content` so all\n\t * positioning / sizing options (`side`, `align`, `preferredWidth`, etc.)\n\t * work as expected.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tHelpContent,\n\t/**\n\t * Inline \"(Optional)\" suffix to mark a field as optional. Default content\n\t * is `(Optional)`; pass children to translate or replace it. Place inside\n\t * the `<Field.Label>` so screen readers announce it as part of the accessible\n\t * name.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tOptional,\n\t/**\n\t * Helper / hint text rendered below the control in the muted body color.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tDescription,\n\t/**\n\t * Convenience renderer for validation messages. Trims string messages,\n\t * filters empty values, and renders a `Field.ErrorList` with one\n\t * `Field.ErrorItem` for each remaining message.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.LabelRow>\n\t * <Field.Label htmlFor=\"api-key\">\n\t * API key <Field.Optional />\n\t * </Field.Label>\n\t * <Field.Help>\n\t * <Field.HelpTrigger label=\"What is an API key?\" />\n\t * <Field.HelpContent>Copy this from the dashboard.</Field.HelpContent>\n\t * </Field.Help>\n\t * </Field.LabelRow>\n\t * <Field.Control>\n\t * <Input id=\"api-key\" name=\"apiKey\" />\n\t * </Field.Control>\n\t * <Field.Errors messages={[\"API key is required.\"]} />\n\t * <Field.Description>You can find this in the ngrok dashboard.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tErrors: FieldErrors,\n\t/**\n\t * Wraps one or more `Field.ErrorItem` children in a semantic `<ul>`.\n\t * Renders nothing when given no renderable children.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.Label htmlFor=\"username\">Username</Field.Label>\n\t * <Field.Control>\n\t * <Input id=\"username\" name=\"username\" />\n\t * </Field.Control>\n\t * <Field.ErrorList>\n\t * <Field.ErrorItem>Must be at least 3 characters.</Field.ErrorItem>\n\t * <Field.ErrorItem>Use letters, numbers, hyphens, or underscores.</Field.ErrorItem>\n\t * </Field.ErrorList>\n\t * <Field.Description>Pick something memorable.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tErrorList: FieldErrorList,\n\t/**\n\t * A single error message list item for a field. Renders an `<li>` in\n\t * `text-danger-600` and must be nested inside a `Field.ErrorList`.\n\t *\n\t * @see https://mantle.ngrok.com/components/field\n\t *\n\t * @example\n\t * ```tsx\n\t * <Field.Group>\n\t * <Field.Item>\n\t * <Field.Label htmlFor=\"username\">Username</Field.Label>\n\t * <Field.Control>\n\t * <Input id=\"username\" name=\"username\" />\n\t * </Field.Control>\n\t * <Field.ErrorList>\n\t * <Field.ErrorItem>Must be at least 3 characters.</Field.ErrorItem>\n\t * <Field.ErrorItem>Use letters, numbers, hyphens, or underscores.</Field.ErrorItem>\n\t * </Field.ErrorList>\n\t * <Field.Description>Pick something memorable.</Field.Description>\n\t * </Field.Item>\n\t * </Field.Group>\n\t * ```\n\t */\n\tErrorItem: FieldErrorItem,\n} as const;\n\nexport {\n\t//,\n\tField,\n};\n"],"mappings":"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"}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as MarginType, o as useComposedRefs, s as copyToClipboard } from "./in-view-
|
|
1
|
+
import { n as MarginType, o as useComposedRefs, s as copyToClipboard } from "./in-view-DdIrfU4u.js";
|
|
2
2
|
import { RefObject, useEffect } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/hooks/use-breakpoint.d.ts
|
package/dist/hooks.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"./use-matches-media-query-CojcYxlA.js";import{r as t}from"./browser-only-QPyyfLaB.js";import{n}from"./compose-refs-DZ3cPi47.js";import{t as r}from"./use-copy-to-clipboard-
|
|
1
|
+
import{t as e}from"./use-matches-media-query-CojcYxlA.js";import{r as t}from"./browser-only-QPyyfLaB.js";import{n}from"./compose-refs-DZ3cPi47.js";import{t as r}from"./use-copy-to-clipboard-CTgtLjUg.js";import{t as i}from"./use-isomorphic-layout-effect-CNSD0lhi.js";import{n as a,t as o}from"./use-prefers-reduced-motion-YUurmkwx.js";import{t as s}from"./in-view-BLZVEGFC.js";import{useCallback as c,useEffect as l,useMemo as u,useReducer as d,useRef as f,useState as p,useSyncExternalStore as m}from"react";const h=[`2xl`,`xl`,`lg`,`md`,`sm`,`xs`,`2xs`],g=[`default`,...h];function _(){return m(j,M,()=>`default`)}function v(e){return m(P(e),I(e),()=>!1)}const y={"2xl":`(min-width: 96rem)`,xl:`(min-width: 80rem)`,lg:`(min-width: 64rem)`,md:`(min-width: 48rem)`,sm:`(min-width: 40rem)`,xs:`(min-width: 30rem)`,"2xs":`(min-width: 22.5rem)`},b={"2xl":`(max-width: 95.99rem)`,xl:`(max-width: 79.99rem)`,lg:`(max-width: 63.99rem)`,md:`(max-width: 47.99rem)`,sm:`(max-width: 39.99rem)`,xs:`(max-width: 29.99rem)`,"2xs":`(max-width: 22.49rem)`};let x=null,S=null;function C(){return x||={"2xl":window.matchMedia(y[`2xl`]),xl:window.matchMedia(y.xl),lg:window.matchMedia(y.lg),md:window.matchMedia(y.md),sm:window.matchMedia(y.sm),xs:window.matchMedia(y.xs),"2xs":window.matchMedia(y[`2xs`])},x}function w(e){return S||={"2xl":window.matchMedia(b[`2xl`]),xl:window.matchMedia(b.xl),lg:window.matchMedia(b.lg),md:window.matchMedia(b.md),sm:window.matchMedia(b.sm),xs:window.matchMedia(b.xs),"2xs":window.matchMedia(b[`2xs`])},S[e]}let T=`default`;const E=new Set;let D=!1;function O(){let e=C();for(let t of h)if(e[t].matches)return t;return`default`}let k=!1;function A(){k||(k=!0,requestAnimationFrame(()=>{k=!1;let e=O();if(e!==T){T=e;for(let e of E)e()}}))}function j(e){if(E.add(e),!D){D=!0;let e=C();T=O();for(let t of Object.values(e))t.addEventListener(`change`,A)}return e(),()=>{if(E.delete(e),E.size===0&&D){D=!1;let e=C();for(let t of Object.values(e))t.removeEventListener(`change`,A)}}}function M(){return T}const N=new Map;function P(e){let t=N.get(e);return t||(t=t=>{let n=w(e),r=!1,i=()=>{r||(r=!0,requestAnimationFrame(()=>{r=!1,t()}))};return n.addEventListener(`change`,i),()=>{n.removeEventListener(`change`,i)}},N.set(e,t),t)}const F=new Map;function I(e){let t=F.get(e);return t||(t=()=>w(e).matches,F.set(e,t),t)}function L(e){let t=f(e);return l(()=>{t.current=e}),u(()=>((...e)=>t.current?.(...e)),[])}function R(e,t){let n=L(e),r=f(0);return l(()=>()=>window.clearTimeout(r.current),[]),c((...e)=>{window.clearTimeout(r.current),r.current=window.setTimeout(()=>n(...e),t.waitMs)},[n,t.waitMs])}const z=(e=`mantle`)=>u(()=>B(e),[e]);function B(e=`mantle`){return[e.trim()||`mantle`,V()].join(`-`)}function V(){return Math.random().toString(36).substring(2,9)}function H(){let e=a();return u(()=>e?`auto`:`smooth`,[e])}function U(e,{root:t,margin:n,amount:r,once:i=!1,initial:a=!1}={}){let[o,c]=p(a);return l(()=>{if(!e.current||i&&o)return;function a(){return c(!0),i?void 0:()=>c(!1)}let l={root:t&&t.current||void 0,margin:n,amount:r};return s(e.current,a,l)},[t,e,n,i,r]),o}function W(e,t){switch(t.type){case`push`:return{undoStack:[...e.undoStack,t.snapshot],redoStack:[]};case`undo`:{if(e.undoStack.length===0)return e;let n=e.undoStack.slice(0,-1);return e.undoStack[e.undoStack.length-1]===void 0?e:{undoStack:n,redoStack:[...e.redoStack,t.current]}}case`redo`:{if(e.redoStack.length===0)return e;let n=e.redoStack.slice(0,-1);return e.redoStack[e.redoStack.length-1]===void 0?e:{undoStack:[...e.undoStack,t.current],redoStack:n}}}}function G(){let[e,t]=d(W,{undoStack:[],redoStack:[]}),n=c(e=>{t({type:`push`,snapshot:e})},[]),r=c(n=>{let r=e.undoStack[e.undoStack.length-1];if(r!==void 0)return t({type:`undo`,current:n}),r},[e.undoStack]),i=c(n=>{let r=e.redoStack[e.redoStack.length-1];if(r!==void 0)return t({type:`redo`,current:n}),r},[e.redoStack]);return u(()=>({canUndo:e.undoStack.length>0,canRedo:e.redoStack.length>0,push:n,undo:r,redo:i}),[e.undoStack.length,e.redoStack.length,n,r,i])}export{g as breakpoints,o as getPrefersReducedMotion,_ as useBreakpoint,L as useCallbackRef,n as useComposedRefs,r as useCopyToClipboard,R as useDebouncedCallback,U as useInView,v as useIsBelowBreakpoint,t as useIsHydrated,i as useIsomorphicLayoutEffect,e as useMatchesMediaQuery,a as usePrefersReducedMotion,z as useRandomStableId,H as useScrollBehavior,G as useUndoRedo};
|
|
2
2
|
//# sourceMappingURL=hooks.js.map
|
package/dist/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","names":[],"sources":["../src/hooks/use-breakpoint.tsx","../src/hooks/use-callback-ref.tsx","../src/hooks/use-debounced-callback.tsx","../src/hooks/use-isomorphic-layout-effect.tsx","../src/hooks/use-random-stable-id.tsx","../src/hooks/use-scroll-behavior.tsx","../src/hooks/use-in-view.tsx","../src/hooks/use-undo-redo.tsx"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\n/**\n * Tailwind CSS breakpoints in descending order (largest → smallest).\n *\n * These correspond to Tailwind’s default `theme.screens` config and are used\n * to determine the current viewport size.\n *\n * @see https://tailwindcss.com/docs/screens\n *\n * @example\n * \"2xl\" // ≥96rem (1536px)\n * \"xl\" // ≥80rem (1280px)\n * \"lg\" // ≥64rem (1024px)\n * \"md\" // ≥48rem (768px)\n * \"sm\" // ≥40rem (640px)\n * \"xs\" // ≥30rem (480px)\n * \"2xs\" // ≥22.5rem (360px)\n */\nconst tailwindBreakpoints = [\"2xl\", \"xl\", \"lg\", \"md\", \"sm\", \"xs\", \"2xs\"] as const;\n\n/**\n * A valid Tailwind CSS breakpoint identifier.\n *\n * @example\n * const bp: TailwindBreakpoint = \"md\"; // ≥48rem (768px)\n *\n * @example\n * \"2xl\" // ≥96rem (1536px)\n * \"xl\" // ≥80rem (1280px)\n * \"lg\" // ≥64rem (1024px)\n * \"md\" // ≥48rem (768px)\n * \"sm\" // ≥40rem (640px)\n * \"xs\" // ≥30rem (480px)\n * \"2xs\" // ≥22.5rem (360px)\n */\ntype TailwindBreakpoint = (typeof tailwindBreakpoints)[number];\n\n/**\n * Mantle’s breakpoint set, extending Tailwind’s with `\"default\"`.\n *\n * `\"default\"` represents the base (0px and up) viewport,\n * useful for defining fallbacks or mobile-first styles.\n *\n * @example\n * \"default\" // ≥0rem (0px)\n * \"2xs\" // ≥22.5rem (360px)\n * \"xs\" // ≥30rem (480px)\n * \"sm\" // ≥40rem (640px)\n * \"md\" // ≥48rem (768px)\n * \"lg\" // ≥64rem (1024px)\n * \"xl\" // ≥80rem (1280px)\n * \"2xl\" // ≥96rem (1536px)\n */\nconst breakpoints = [\"default\", ...tailwindBreakpoints] as const;\n\n/**\n * A valid Mantle breakpoint identifier.\n *\n * Includes Tailwind’s standard breakpoints plus `\"default\"` for 0px+.\n *\n * @example\n * const bp: Breakpoint = \"default\"; // ≥0px\n *\n * @example\n * \"default\" // ≥0rem (0px)\n * \"2xs\" // ≥22.5rem (360px)\n * \"xs\" // ≥30rem (480px)\n * \"sm\" // ≥40rem (640px)\n * \"md\" // ≥48rem (768px)\n * \"lg\" // ≥64rem (1024px)\n * \"xl\" // ≥80rem (1280px)\n * \"2xl\" // ≥96rem (1536px)\n */\ntype Breakpoint = (typeof breakpoints)[number];\n\n/**\n * React hook that returns the current breakpoint based on the viewport width.\n *\n * Uses a singleton subscription to a set of min-width media queries and returns\n * the largest matching breakpoint. Designed for React 18+ with\n * `useSyncExternalStore`.\n *\n * @returns {Breakpoint} The current breakpoint that matches the viewport width.\n *\n * @example\n * const breakpoint = useBreakpoint();\n * if (breakpoint === \"lg\") {\n * // Do something for large screens and above\n * }\n */\nfunction useBreakpoint(): Breakpoint {\n\treturn useSyncExternalStore(\n\t\tsubscribeToBreakpointChanges,\n\t\tgetCurrentBreakpointSnapshot,\n\t\t() => \"default\", // SSR fallback\n\t);\n}\n\n/**\n * React hook that returns true if the current viewport width is below the specified breakpoint.\n *\n * This hook uses `window.matchMedia` with a max-width media query and leverages\n * `useSyncExternalStore` to stay compliant with React's concurrent rendering model.\n *\n * @param {TailwindBreakpoint} breakpoint - The breakpoint to check against (e.g., \"md\", \"lg\").\n *\n * @returns {boolean} `true` if the viewport width is below the breakpoint, otherwise `false`.\n *\n * @example\n * // Check if viewport is below medium (768px)\n * const isBelowMd = useIsBelowBreakpoint(\"md\");\n */\nfunction useIsBelowBreakpoint(breakpoint: TailwindBreakpoint): boolean {\n\treturn useSyncExternalStore(\n\t\tcreateBelowBreakpointSubscribe(breakpoint),\n\t\tcreateBelowBreakpointGetSnapshot(breakpoint),\n\t\t() => false, // SSR fallback - assume desktop\n\t);\n}\n\nexport {\n\t//,\n\tbreakpoints,\n\tuseBreakpoint,\n\tuseIsBelowBreakpoint,\n};\n\nexport type {\n\t//,\n\tBreakpoint,\n\tTailwindBreakpoint,\n};\n\n/**\n * A CSS media query string representing a minimum width in `rem` units.\n *\n * @example\n * const query: MinWidthQuery = \"(min-width: 48rem)\";\n *\n * @private\n */\ntype MinWidthQuery = `(min-width: ${number}rem)`;\n\n/**\n * A CSS media query string representing a maximum width in `rem` units.\n *\n * @example\n * const query: MaxWidthQuery = \"(max-width: 47.99rem)\";\n *\n * @private\n */\ntype MaxWidthQuery = `(max-width: ${number}rem)`;\n\n/**\n * Precomputed min-width media query strings for each Tailwind breakpoint.\n *\n * Using constants avoids template string work in hot paths and ensures type\n * safety against the `MinWidthQuery` template literal type.\n *\n * @remarks\n * These are expressed in `rem`. If your CSS breakpoints are in `px`, consider\n * aligning units to avoid JS/CSS drift when `html{font-size}` changes.\n *\n * @private\n */\nconst breakpointQueries = {\n\t\"2xl\": \"(min-width: 96rem)\" as const,\n\txl: \"(min-width: 80rem)\" as const,\n\tlg: \"(min-width: 64rem)\" as const,\n\tmd: \"(min-width: 48rem)\" as const,\n\tsm: \"(min-width: 40rem)\" as const,\n\txs: \"(min-width: 30rem)\" as const,\n\t\"2xs\": \"(min-width: 22.5rem)\" as const,\n} as const satisfies Record<TailwindBreakpoint, MinWidthQuery>;\n\n/**\n * Precomputed max-width media query strings used by `useIsBelowBreakpoint`.\n *\n * The `-0.01rem` offset avoids overlap at exact boundaries.\n *\n * @private\n */\nconst belowBreakpointQueries = {\n\t\"2xl\": \"(max-width: 95.99rem)\" as const, // 96 - 0.01\n\txl: \"(max-width: 79.99rem)\" as const, // 80 - 0.01\n\tlg: \"(max-width: 63.99rem)\" as const, // 64 - 0.01\n\tmd: \"(max-width: 47.99rem)\" as const, // 48 - 0.01\n\tsm: \"(max-width: 39.99rem)\" as const, // 40 - 0.01\n\txs: \"(max-width: 29.99rem)\" as const, // 30 - 0.01\n\t\"2xs\": \"(max-width: 22.49rem)\" as const, // 22.5 - 0.01\n} as const satisfies Record<TailwindBreakpoint, MaxWidthQuery>;\n\n/**\n * Lazily-initialized cache of `MediaQueryList` objects for min-width queries.\n *\n * Initialized on first access to remain SSR-safe (no `window` at import time).\n *\n * @private\n */\nlet minWidthMQLs: Record<TailwindBreakpoint, MediaQueryList> | null = null;\n\n/**\n * Lazily-initialized cache of `MediaQueryList` objects for max-width queries.\n *\n * Used by `useIsBelowBreakpoint`. Also SSR-safe by lazy access.\n *\n * @private\n */\nlet maxWidthMQLs: Record<TailwindBreakpoint, MediaQueryList> | null = null;\n\n/**\n * Get (and lazily create) the cached `MediaQueryList` objects for min-width queries.\n *\n * @returns A record of `MediaQueryList` keyed by Tailwind breakpoint.\n * @private\n */\nfunction getMinWidthMQLs(): Record<TailwindBreakpoint, MediaQueryList> {\n\tif (!minWidthMQLs) {\n\t\tminWidthMQLs = {\n\t\t\t\"2xl\": window.matchMedia(breakpointQueries[\"2xl\"]),\n\t\t\txl: window.matchMedia(breakpointQueries.xl),\n\t\t\tlg: window.matchMedia(breakpointQueries.lg),\n\t\t\tmd: window.matchMedia(breakpointQueries.md),\n\t\t\tsm: window.matchMedia(breakpointQueries.sm),\n\t\t\txs: window.matchMedia(breakpointQueries.xs),\n\t\t\t\"2xs\": window.matchMedia(breakpointQueries[\"2xs\"]),\n\t\t};\n\t}\n\treturn minWidthMQLs;\n}\n\n/**\n * Get (and lazily create) the cached `MediaQueryList` for a specific max-width breakpoint.\n *\n * @param breakpoint - Tailwind breakpoint identifier (e.g., \"md\").\n * @returns The corresponding `MediaQueryList`.\n * @private\n */\nfunction getMaxWidthMQL(breakpoint: TailwindBreakpoint): MediaQueryList {\n\tif (!maxWidthMQLs) {\n\t\tmaxWidthMQLs = {\n\t\t\t\"2xl\": window.matchMedia(belowBreakpointQueries[\"2xl\"]),\n\t\t\txl: window.matchMedia(belowBreakpointQueries.xl),\n\t\t\tlg: window.matchMedia(belowBreakpointQueries.lg),\n\t\t\tmd: window.matchMedia(belowBreakpointQueries.md),\n\t\t\tsm: window.matchMedia(belowBreakpointQueries.sm),\n\t\t\txs: window.matchMedia(belowBreakpointQueries.xs),\n\t\t\t\"2xs\": window.matchMedia(belowBreakpointQueries[\"2xs\"]),\n\t\t};\n\t}\n\treturn maxWidthMQLs[breakpoint];\n}\n\n/**\n * Current breakpoint value used by the singleton store backing `useBreakpoint`.\n *\n * Initialized to `\"default\"` and updated on media-query change events.\n *\n * @private\n */\nlet currentBreakpointValue: Breakpoint = \"default\";\n\n/**\n * Set of component listeners subscribed to the singleton breakpoint store.\n *\n * Each listener is invoked when the current breakpoint value changes.\n *\n * @private\n */\nconst breakpointListeners = new Set<() => void>();\n\n/**\n * Flag indicating whether global media-query listeners are currently attached.\n *\n * Prevents duplicate registrations and enables full teardown when unused.\n *\n * @private\n */\nlet breakpointSubscriptionActive = false;\n\n/**\n * Compute the current breakpoint by checking cached min-width MQLs\n * from largest to smallest.\n *\n * @returns {Breakpoint} The largest matching breakpoint, or `\"default\"`.\n * @private\n */\nfunction getCurrentBreakpoint(): Breakpoint {\n\tconst mqls = getMinWidthMQLs();\n\tfor (const breakpoint of tailwindBreakpoints) {\n\t\tif (mqls[breakpoint].matches) {\n\t\t\treturn breakpoint;\n\t\t}\n\t}\n\treturn \"default\";\n}\n\n/**\n * Update the current breakpoint value and notify all listeners.\n *\n * Uses `requestAnimationFrame` to coalesce rapid resize events and minimize\n * re-renders during active window resizing.\n *\n * @private\n */\nlet breakpointUpdatePending = false;\nfunction updateCurrentBreakpoint() {\n\tif (!breakpointUpdatePending) {\n\t\tbreakpointUpdatePending = true;\n\t\trequestAnimationFrame(() => {\n\t\t\tbreakpointUpdatePending = false;\n\t\t\tconst newBreakpoint = getCurrentBreakpoint();\n\t\t\tif (newBreakpoint !== currentBreakpointValue) {\n\t\t\t\tcurrentBreakpointValue = newBreakpoint;\n\t\t\t\tfor (const listener of breakpointListeners) {\n\t\t\t\t\tlistener();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n}\n\n/**\n * Subscribe a component to breakpoint changes (singleton pattern).\n *\n * Ensures only one set of MQL listeners exists app-wide. Also reconciles the\n * `useSyncExternalStore` initial snapshot/subscribe race by invoking the\n * subscriber once on mount.\n *\n * @param callback - Listener invoked when the breakpoint value may have changed.\n * @returns Cleanup function to unsubscribe the listener.\n * @private\n */\nfunction subscribeToBreakpointChanges(callback: () => void) {\n\tbreakpointListeners.add(callback);\n\n\t// Attach global listeners once\n\tif (!breakpointSubscriptionActive) {\n\t\tbreakpointSubscriptionActive = true;\n\t\tconst mqls = getMinWidthMQLs();\n\n\t\t// Initialize current value synchronously\n\t\tcurrentBreakpointValue = getCurrentBreakpoint();\n\n\t\t// Attach listeners to all breakpoint MQLs\n\t\tfor (const mql of Object.values(mqls)) {\n\t\t\tmql.addEventListener(\"change\", updateCurrentBreakpoint);\n\t\t}\n\t}\n\n\t// Reconcile initial getSnapshot vs subscribe ordering\n\tcallback();\n\n\t// Cleanup\n\treturn () => {\n\t\tbreakpointListeners.delete(callback);\n\n\t\t// Tear down global listeners when no one is listening\n\t\tif (breakpointListeners.size === 0 && breakpointSubscriptionActive) {\n\t\t\tbreakpointSubscriptionActive = false;\n\t\t\tconst mqls = getMinWidthMQLs();\n\t\t\tfor (const mql of Object.values(mqls)) {\n\t\t\t\tmql.removeEventListener(\"change\", updateCurrentBreakpoint);\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Return the current breakpoint value from the singleton store.\n *\n * Used as the `getSnapshot` for `useSyncExternalStore`.\n *\n * @returns {Breakpoint} The latest computed breakpoint.\n * @private\n */\nfunction getCurrentBreakpointSnapshot(): Breakpoint {\n\treturn currentBreakpointValue;\n}\n\n/**\n * Cached `subscribe` functions keyed by breakpoint.\n *\n * Without caching, `useSyncExternalStore` receives a new function reference on\n * every render, causing it to tear down and re-attach the MQL listener each\n * time — the primary source of resize sluggishness.\n *\n * @private\n */\nconst belowBreakpointSubscribeCache = new Map<\n\tTailwindBreakpoint,\n\t(callback: () => void) => () => void\n>();\n\n/**\n * Get (or create and cache) a `subscribe` function for a specific \"below\" breakpoint.\n *\n * Uses a cached `MediaQueryList` and rAF-throttled change handler to avoid\n * bursty updates during resize.\n *\n * @param breakpoint - Tailwind breakpoint identifier (e.g., \"lg\").\n * @returns A stable `subscribe` function suitable for `useSyncExternalStore`.\n * @private\n */\nfunction createBelowBreakpointSubscribe(breakpoint: TailwindBreakpoint) {\n\tlet cached = belowBreakpointSubscribeCache.get(breakpoint);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tcached = (callback: () => void) => {\n\t\tconst mediaQuery = getMaxWidthMQL(breakpoint);\n\n\t\t// rAF throttle the change callback during active resize\n\t\tlet pending = false;\n\t\tconst onChange = () => {\n\t\t\tif (!pending) {\n\t\t\t\tpending = true;\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tpending = false;\n\t\t\t\t\tcallback();\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tmediaQuery.addEventListener(\"change\", onChange);\n\t\treturn () => {\n\t\t\tmediaQuery.removeEventListener(\"change\", onChange);\n\t\t};\n\t};\n\n\tbelowBreakpointSubscribeCache.set(breakpoint, cached);\n\treturn cached;\n}\n\n/**\n * Cached `getSnapshot` functions keyed by breakpoint.\n *\n * Ensures `useSyncExternalStore` receives a referentially stable function,\n * preventing unnecessary subscription churn.\n *\n * @private\n */\nconst belowBreakpointSnapshotCache = new Map<TailwindBreakpoint, () => boolean>();\n\n/**\n * Get (or create and cache) a `getSnapshot` function for a specific \"below\" breakpoint.\n *\n * Uses the cached `MediaQueryList` for the target breakpoint.\n *\n * @param breakpoint - Tailwind breakpoint identifier (e.g., \"lg\").\n * @returns A stable function that returns `true` when the viewport is below the breakpoint.\n * @private\n */\nfunction createBelowBreakpointGetSnapshot(breakpoint: TailwindBreakpoint) {\n\tlet cached = belowBreakpointSnapshotCache.get(breakpoint);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tcached = () => {\n\t\tconst mediaQuery = getMaxWidthMQL(breakpoint);\n\t\treturn mediaQuery.matches;\n\t};\n\n\tbelowBreakpointSnapshotCache.set(breakpoint, cached);\n\treturn cached;\n}\n","import { useEffect, useMemo, useRef } from \"react\";\n\n/**\n * Returns a memoized callback that always invokes the latest version of the\n * provided callback, while preserving a stable function identity across\n * renders.\n *\n * Use this when you need to pass a callback to a child component, an event\n * handler, or a hook dependency array, but the consumer should not re-run /\n * re-render simply because the callback's identity changed. The returned\n * function never changes reference, but internally always calls through to\n * the latest `callback` passed in.\n *\n * Most commonly used as an internal building block for other hooks (for\n * example, {@link useDebouncedCallback}). It is also re-exported publicly\n * for consumers that need the same pattern.\n *\n * @param callback - The callback to wrap. May be `undefined`, in which case\n * invoking the returned function is a no-op until a callback is provided\n * on a subsequent render.\n * @returns A stable function with the same signature as `callback` that\n * forwards its arguments to the most recent `callback` value.\n *\n * @example\n * // Pass a stable handler to a memoized child without re-rendering it\n * const onSelect = useCallbackRef((id: string) => {\n * // reads the latest `props.items` without being in deps\n * props.onSelectItem(id, props.items);\n * });\n *\n * return <MemoizedList onSelect={onSelect} />;\n */\nfunction useCallbackRef<T extends (...args: unknown[]) => unknown>(callback: T | undefined): T {\n\tconst callbackRef = useRef(callback);\n\n\tuseEffect(() => {\n\t\tcallbackRef.current = callback;\n\t});\n\n\treturn useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport {\n\t//,\n\tuseCallbackRef,\n};\n","import { useCallback, useEffect, useRef } from \"react\";\nimport { useCallbackRef } from \"./use-callback-ref.js\";\n\n/**\n * Options for {@link useDebouncedCallback}.\n */\ntype Options = {\n\t/**\n\t * The delay in milliseconds to wait between the last invocation and\n\t * actually running the callback.\n\t */\n\twaitMs: number;\n};\n\n/**\n * Returns a debounced version of the provided callback. Each call resets a\n * timer; the underlying callback only runs after `options.waitMs` of\n * inactivity has elapsed.\n *\n * Useful for limiting rapid invocations such as search-as-you-type inputs,\n * window resize handlers, or expensive button-press handlers. The pending\n * timer is automatically cleared on unmount.\n *\n * The debounced function always invokes the latest version of `callbackFn`,\n * so callers do not need to memoize it. The returned function's identity\n * only changes when `options.waitMs` changes, so it is safe to include in\n * dependency arrays.\n *\n * @param callbackFn - The function to debounce. The latest reference passed\n * on each render is always used when the timer fires.\n * @param options - Debounce options.\n * @param options.waitMs - Milliseconds of inactivity to wait before calling\n * `callbackFn`.\n * @returns A function with the same parameter list as `callbackFn` that\n * schedules (or reschedules) the underlying call.\n *\n * @example\n * // Debounce a search input by 300ms\n * const [query, setQuery] = useState(\"\");\n * const search = useDebouncedCallback((value: string) => {\n * fetchResults(value);\n * }, { waitMs: 300 });\n *\n * return (\n * <input\n * value={query}\n * onChange={(event) => {\n * setQuery(event.target.value);\n * search(event.target.value);\n * }}\n * />\n * );\n */\nfunction useDebouncedCallback<T extends (...args: unknown[]) => unknown>(\n\tcallbackFn: T,\n\toptions: Options,\n) {\n\tconst stableCallbackFn = useCallbackRef(callbackFn);\n\tconst debounceTimerRef = useRef(0);\n\tuseEffect(() => () => window.clearTimeout(debounceTimerRef.current), []);\n\n\treturn useCallback(\n\t\t(...args: Parameters<T>) => {\n\t\t\twindow.clearTimeout(debounceTimerRef.current);\n\t\t\tdebounceTimerRef.current = window.setTimeout(() => stableCallbackFn(...args), options.waitMs);\n\t\t},\n\t\t[stableCallbackFn, options.waitMs],\n\t);\n}\n\nexport {\n\t//,\n\tuseDebouncedCallback,\n};\n","import { useEffect, useLayoutEffect } from \"react\";\n\n/**\n * A drop-in replacement for `useLayoutEffect` that does not warn during\n * server-side rendering.\n *\n * Resolves to `useLayoutEffect` in the browser (where it can read layout and\n * synchronously re-render before paint) and to `useEffect` on the server\n * (where layout effects are a no-op and React would otherwise log a\n * \"useLayoutEffect does nothing on the server\" warning).\n *\n * Use this whenever you need the timing semantics of `useLayoutEffect` in\n * code that may also execute during SSR. It is most often used internally\n * by other Mantle hooks and components.\n *\n * @param effect - The imperative function that may return a cleanup\n * function — same signature as React's `useLayoutEffect` / `useEffect`.\n * @param deps - Optional dependency list, same semantics as\n * `useLayoutEffect`.\n * @returns Nothing.\n *\n * @example\n * // Measure an element synchronously after layout\n * const ref = useRef<HTMLDivElement>(null);\n * const [width, setWidth] = useState(0);\n *\n * useIsomorphicLayoutEffect(() => {\n * if (ref.current) {\n * setWidth(ref.current.getBoundingClientRect().width);\n * }\n * }, []);\n *\n * return <div ref={ref}>Width: {width}</div>;\n */\nexport const useIsomorphicLayoutEffect =\n\ttypeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import { useMemo } from \"react\";\n\n/**\n * React hook that returns a random, stable id (e.g. `\"mantle-a3f9k7q\"`)\n * suitable for DOM `id` attributes and `aria-*` references.\n *\n * Unlike React's built-in `useId`, the generated suffix does not contain\n * special characters (`:`). The default id is safe to use directly in CSS\n * selectors and `querySelector` calls; if you provide a custom `prefix`,\n * keep it selector-safe or escape the final id with `CSS.escape()` before\n * querying. The id is generated once for the lifetime of the component and\n * is stable across re-renders, but a new value is produced when `prefix`\n * changes.\n *\n * @param prefix - Optional string prepended to the generated suffix.\n * Whitespace-only or empty values fall back to `\"mantle\"`. Use a\n * selector-safe prefix if you plan to reference the id in CSS selectors\n * without escaping. Defaults to `\"mantle\"`.\n * @returns A string of the form `\"<prefix>-<7-char-random>\"`.\n *\n * @example\n * // Associate a label with a custom input\n * const id = useRandomStableId(\"email-input\");\n *\n * return (\n * <>\n * <label htmlFor={id}>Email</label>\n * <input id={id} type=\"email\" />\n * </>\n * );\n *\n * @example\n * // Use as an aria-controls reference\n * const panelId = useRandomStableId(\"panel\");\n *\n * return (\n * <>\n * <button aria-controls={panelId}>Toggle</button>\n * <div id={panelId}>Panel contents</div>\n * </>\n * );\n */\nconst useRandomStableId = (prefix = \"mantle\") => useMemo(() => randomStableId(prefix), [prefix]);\n\nexport {\n\t//,\n\tuseRandomStableId,\n};\n\nfunction randomStableId(prefix = \"mantle\") {\n\tconst safePrefix = prefix.trim() || \"mantle\";\n\treturn [safePrefix, randomPostfix()].join(\"-\");\n}\n\nfunction randomPostfix() {\n\treturn Math.random().toString(36).substring(2, 9);\n}\n","import { useMemo } from \"react\";\nimport { usePrefersReducedMotion } from \"./use-prefers-reduced-motion.js\";\n\n/**\n * `scroll-behavior` values:\n *\n * - `\"auto\"` — scrolling happens instantly (no animation).\n * - `\"smooth\"` — scrolling animates smoothly using a user-agent–defined easing and duration.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior#values\n */\nexport type ScrollBehavior = \"auto\" | \"smooth\";\n\n/**\n * React hook that returns a {@link ScrollBehavior} value (`\"auto\"` or\n * `\"smooth\"`) that respects the user's motion preference.\n *\n * Internally calls {@link usePrefersReducedMotion}: when reduced motion is\n * preferred, this returns `\"auto\"` (no animated scroll); otherwise it\n * returns `\"smooth\"`. Pair this with `window.scrollTo`,\n * `Element.scrollIntoView`, or any other scroll API that accepts a\n * `behavior` option to avoid forcing animations on users who have opted\n * out of motion. The conservative SSR default also prevents \"first paint\"\n * scroll animations.\n *\n * @returns `\"auto\"` when the user prefers reduced motion, otherwise\n * `\"smooth\"`.\n *\n * @example\n * // Scroll to the top of the page on a button click\n * const behavior = useScrollBehavior();\n *\n * return (\n * <button onClick={() => window.scrollTo({ top: 0, behavior })}>\n * Back to top\n * </button>\n * );\n *\n * @example\n * // Bring a referenced section into view\n * const behavior = useScrollBehavior();\n * const sectionRef = useRef<HTMLElement>(null);\n *\n * function focusSection() {\n * sectionRef.current?.scrollIntoView({ behavior, block: \"start\" });\n * }\n *\n * @see {@link usePrefersReducedMotion}\n * @see CSS `scroll-behavior` property (values: `\"auto\"`, `\"smooth\"`).\n */\nexport function useScrollBehavior(): ScrollBehavior {\n\tconst prefersReducedMotion = usePrefersReducedMotion();\n\n\treturn useMemo(() => (prefersReducedMotion ? \"auto\" : \"smooth\"), [prefersReducedMotion]);\n}\n","\"use client\";\n\nimport { type RefObject, useEffect, useState } from \"react\";\nimport type { InViewOptions, MarginType } from \"../utils/in-view.js\";\nimport { inView } from \"../utils/in-view.js\";\n\n/**\n * Options for the `useInView` hook.\n */\ntype UseInViewOptions = {\n\t/**\n\t * A ref to a scrollable container element to use as the intersection root.\n\t * Defaults to the browser viewport.\n\t */\n\troot?: RefObject<Element | null>;\n\n\t/**\n\t * Expand or contract the detected area from each side of the root's bounding box.\n\t * Uses the same syntax as the CSS `margin` shorthand (e.g. `\"10px\"`, `\"10% 20px\"`).\n\t */\n\tmargin?: MarginType;\n\n\t/**\n\t * How much of the element must be visible before it is considered in view.\n\t * - `\"some\"` (default): Any part of the element is visible.\n\t * - `\"all\"`: The entire element is visible.\n\t * - `number`: An intersection ratio between `0` and `1` (e.g. `0.5` for 50%).\n\t */\n\tamount?: \"some\" | \"all\" | number;\n\n\t/**\n\t * If `true`, stop observing once the element enters the viewport for the\n\t * first time. Useful for one-shot entrance animations.\n\t * Defaults to `false`.\n\t */\n\tonce?: boolean;\n\n\t/**\n\t * The initial visibility state returned before the observer has attached.\n\t * Defaults to `false`.\n\t */\n\tinitial?: boolean;\n};\n\n/**\n * React hook that tracks whether a DOM element is visible within the viewport\n * (or a scrollable container) using the `IntersectionObserver` API.\n *\n * @param ref - A ref attached to the element to observe.\n * @param options - Options controlling the scroll root, margin, threshold,\n * initial state, and one-time detection.\n * @returns `true` if the element is currently in view, otherwise `false`.\n *\n * @example\n * // Basic usage\n * const ref = useRef<HTMLDivElement>(null);\n * const isInView = useInView(ref);\n *\n * return <div ref={ref}>{isInView ? \"Visible!\" : \"Hidden\"}</div>;\n *\n * @example\n * // Trigger once when the element first enters the viewport\n * const ref = useRef<HTMLDivElement>(null);\n * const isInView = useInView(ref, { once: true });\n *\n * return (\n * <div\n * ref={ref}\n * style={{ opacity: isInView ? 1 : 0, transition: \"opacity 0.5s\" }}\n * />\n * );\n *\n * @example\n * // Require 50% of the element to be visible\n * const ref = useRef<HTMLDivElement>(null);\n * const isInView = useInView(ref, { amount: 0.5 });\n */\nfunction useInView(\n\tref: RefObject<Element | null>,\n\t{ root, margin, amount, once = false, initial = false }: UseInViewOptions = {},\n): boolean {\n\tconst [isInView, setInView] = useState(initial);\n\n\tuseEffect(() => {\n\t\tif (!ref.current || (once && isInView)) {\n\t\t\treturn;\n\t\t}\n\n\t\tfunction onEnter() {\n\t\t\tsetInView(true);\n\t\t\treturn once ? undefined : () => setInView(false);\n\t\t}\n\n\t\tconst options: InViewOptions = {\n\t\t\troot: (root && root.current) || undefined,\n\t\t\tmargin,\n\t\t\tamount,\n\t\t};\n\n\t\treturn inView(ref.current, onEnter, options);\n\t\t/**\n\t\t * Intentionally omit `isInView` from deps. The effect must only re-run\n\t\t * when the observation parameters change, not when visibility changes.\n\t\t * Including `isInView` would restart the observer (disconnect + reconnect)\n\t\t * on every enter/leave event, causing wasteful churn for the common\n\t\t * `once=false` case.\n\t\t */\n\t\t// oxlint-disable-next-line react-hooks/exhaustive-deps\n\t}, [root, ref, margin, once, amount]);\n\n\treturn isInView;\n}\n\nexport { useInView };\nexport type { UseInViewOptions };\n","import { useCallback, useMemo, useReducer } from \"react\";\n\ntype UndoRedoState<T> = {\n\tundoStack: T[];\n\tredoStack: T[];\n};\n\ntype UndoRedoAction<T> =\n\t| { type: \"push\"; snapshot: T }\n\t| { type: \"undo\"; current: T }\n\t| { type: \"redo\"; current: T };\n\nfunction undoRedoReducer<T>(state: UndoRedoState<T>, action: UndoRedoAction<T>): UndoRedoState<T> {\n\tswitch (action.type) {\n\t\tcase \"push\": {\n\t\t\treturn {\n\t\t\t\tundoStack: [...state.undoStack, action.snapshot],\n\t\t\t\tredoStack: [],\n\t\t\t};\n\t\t}\n\t\tcase \"undo\": {\n\t\t\tif (state.undoStack.length === 0) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\tconst undoStack = state.undoStack.slice(0, -1);\n\t\t\tconst previous = state.undoStack[state.undoStack.length - 1];\n\t\t\tif (previous === undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tundoStack,\n\t\t\t\tredoStack: [...state.redoStack, action.current],\n\t\t\t};\n\t\t}\n\t\tcase \"redo\": {\n\t\t\tif (state.redoStack.length === 0) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\tconst redoStack = state.redoStack.slice(0, -1);\n\t\t\tconst next = state.redoStack[state.redoStack.length - 1];\n\t\t\tif (next === undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tundoStack: [...state.undoStack, action.current],\n\t\t\t\tredoStack,\n\t\t\t};\n\t\t}\n\t}\n}\n\ntype UseUndoRedoReturn<T> = {\n\t/** Whether there are actions to undo. */\n\tcanUndo: boolean;\n\t/** Whether there are actions to redo. */\n\tcanRedo: boolean;\n\t/** Push a snapshot onto the undo stack. Clears the redo stack. */\n\tpush: (snapshot: T) => void;\n\t/** Pop the last snapshot from the undo stack. Returns `undefined` if empty. */\n\tundo: (current: T) => T | undefined;\n\t/** Pop the last snapshot from the redo stack. Returns `undefined` if empty. */\n\tredo: (current: T) => T | undefined;\n};\n\n/**\n * Generic undo/redo hook backed by a reducer that maintains two history\n * stacks (undo and redo).\n *\n * The hook does not own your application state — instead it helps you\n * snapshot it. Call `push(snapshot)` *before* mutating state to capture\n * the current value, then call `undo(current)` or `redo(current)` to swap\n * `current` with the previous/next snapshot. Both `undo` and `redo` return\n * the snapshot to apply, or `undefined` if their stack is empty. Pushing a\n * new snapshot clears the redo stack, matching standard editor semantics.\n *\n * @typeParam T - The type of the value being snapshotted (e.g. a list of\n * items, a serialized form value, etc.).\n *\n * @returns An object with the current undo/redo capability flags and\n * actions:\n * - `canUndo`: `true` when there is at least one snapshot on the undo\n * stack.\n * - `canRedo`: `true` when there is at least one snapshot on the redo\n * stack.\n * - `push(snapshot)`: Push a snapshot onto the undo stack and clear the\n * redo stack. Call this *before* mutating state.\n * - `undo(current)`: Pop the latest undo snapshot and return it; returns\n * `undefined` when the undo stack is empty. The supplied `current` is\n * pushed onto the redo stack so you can redo back to it.\n * - `redo(current)`: Pop the latest redo snapshot and return it; returns\n * `undefined` when the redo stack is empty. The supplied `current` is\n * pushed onto the undo stack.\n *\n * @example\n * // Snapshot before mutating, then wire up keyboard shortcuts\n * const [items, setItems] = useState<string[]>([]);\n * const { push, undo, redo, canUndo, canRedo } = useUndoRedo<string[]>();\n *\n * function removeItem(item: string) {\n * push(items); // snapshot before mutation\n * setItems((prev) => prev.filter((entry) => entry !== item));\n * }\n *\n * function handleKeyDown(event: React.KeyboardEvent) {\n * const cmd = event.metaKey || event.ctrlKey;\n * if (cmd && event.key === \"z\" && !event.shiftKey) {\n * const previous = undo(items);\n * if (previous) {\n * setItems(previous);\n * }\n * }\n * if (cmd && ((event.shiftKey && event.key === \"z\") || event.key === \"y\")) {\n * const next = redo(items);\n * if (next) {\n * setItems(next);\n * }\n * }\n * }\n *\n * return (\n * <div tabIndex={0} onKeyDown={handleKeyDown}>\n * <button disabled={!canUndo} onClick={() => { const previous = undo(items); if (previous) setItems(previous); }}>Undo</button>\n * <button disabled={!canRedo} onClick={() => { const next = redo(items); if (next) setItems(next); }}>Redo</button>\n * </div>\n * );\n */\nfunction useUndoRedo<T>(): UseUndoRedoReturn<T> {\n\tconst [state, dispatch] = useReducer(undoRedoReducer<T>, {\n\t\tundoStack: [],\n\t\tredoStack: [],\n\t});\n\n\tconst push = useCallback((snapshot: T) => {\n\t\tdispatch({ type: \"push\", snapshot });\n\t}, []);\n\n\tconst undo = useCallback(\n\t\t(current: T): T | undefined => {\n\t\t\tconst previous = state.undoStack[state.undoStack.length - 1];\n\t\t\tif (previous === undefined) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tdispatch({ type: \"undo\", current });\n\t\t\treturn previous;\n\t\t},\n\t\t[state.undoStack],\n\t);\n\n\tconst redo = useCallback(\n\t\t(current: T): T | undefined => {\n\t\t\tconst next = state.redoStack[state.redoStack.length - 1];\n\t\t\tif (next === undefined) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tdispatch({ type: \"redo\", current });\n\t\t\treturn next;\n\t\t},\n\t\t[state.redoStack],\n\t);\n\n\treturn useMemo(\n\t\t() => ({\n\t\t\tcanUndo: state.undoStack.length > 0,\n\t\t\tcanRedo: state.redoStack.length > 0,\n\t\t\tpush,\n\t\t\tundo,\n\t\t\tredo,\n\t\t}),\n\t\t[state.undoStack.length, state.redoStack.length, push, undo, redo],\n\t);\n}\n\nexport {\n\t//,\n\tuseUndoRedo,\n};\n\nexport type {\n\t//,\n\tUseUndoRedoReturn,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../src/hooks/use-breakpoint.tsx","../src/hooks/use-callback-ref.tsx","../src/hooks/use-debounced-callback.tsx","../src/hooks/use-random-stable-id.tsx","../src/hooks/use-scroll-behavior.tsx","../src/hooks/use-in-view.tsx","../src/hooks/use-undo-redo.tsx"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\n/**\n * Tailwind CSS breakpoints in descending order (largest → smallest).\n *\n * These correspond to Tailwind’s default `theme.screens` config and are used\n * to determine the current viewport size.\n *\n * @see https://tailwindcss.com/docs/screens\n *\n * @example\n * \"2xl\" // ≥96rem (1536px)\n * \"xl\" // ≥80rem (1280px)\n * \"lg\" // ≥64rem (1024px)\n * \"md\" // ≥48rem (768px)\n * \"sm\" // ≥40rem (640px)\n * \"xs\" // ≥30rem (480px)\n * \"2xs\" // ≥22.5rem (360px)\n */\nconst tailwindBreakpoints = [\"2xl\", \"xl\", \"lg\", \"md\", \"sm\", \"xs\", \"2xs\"] as const;\n\n/**\n * A valid Tailwind CSS breakpoint identifier.\n *\n * @example\n * const bp: TailwindBreakpoint = \"md\"; // ≥48rem (768px)\n *\n * @example\n * \"2xl\" // ≥96rem (1536px)\n * \"xl\" // ≥80rem (1280px)\n * \"lg\" // ≥64rem (1024px)\n * \"md\" // ≥48rem (768px)\n * \"sm\" // ≥40rem (640px)\n * \"xs\" // ≥30rem (480px)\n * \"2xs\" // ≥22.5rem (360px)\n */\ntype TailwindBreakpoint = (typeof tailwindBreakpoints)[number];\n\n/**\n * Mantle’s breakpoint set, extending Tailwind’s with `\"default\"`.\n *\n * `\"default\"` represents the base (0px and up) viewport,\n * useful for defining fallbacks or mobile-first styles.\n *\n * @example\n * \"default\" // ≥0rem (0px)\n * \"2xs\" // ≥22.5rem (360px)\n * \"xs\" // ≥30rem (480px)\n * \"sm\" // ≥40rem (640px)\n * \"md\" // ≥48rem (768px)\n * \"lg\" // ≥64rem (1024px)\n * \"xl\" // ≥80rem (1280px)\n * \"2xl\" // ≥96rem (1536px)\n */\nconst breakpoints = [\"default\", ...tailwindBreakpoints] as const;\n\n/**\n * A valid Mantle breakpoint identifier.\n *\n * Includes Tailwind’s standard breakpoints plus `\"default\"` for 0px+.\n *\n * @example\n * const bp: Breakpoint = \"default\"; // ≥0px\n *\n * @example\n * \"default\" // ≥0rem (0px)\n * \"2xs\" // ≥22.5rem (360px)\n * \"xs\" // ≥30rem (480px)\n * \"sm\" // ≥40rem (640px)\n * \"md\" // ≥48rem (768px)\n * \"lg\" // ≥64rem (1024px)\n * \"xl\" // ≥80rem (1280px)\n * \"2xl\" // ≥96rem (1536px)\n */\ntype Breakpoint = (typeof breakpoints)[number];\n\n/**\n * React hook that returns the current breakpoint based on the viewport width.\n *\n * Uses a singleton subscription to a set of min-width media queries and returns\n * the largest matching breakpoint. Designed for React 18+ with\n * `useSyncExternalStore`.\n *\n * @returns {Breakpoint} The current breakpoint that matches the viewport width.\n *\n * @example\n * const breakpoint = useBreakpoint();\n * if (breakpoint === \"lg\") {\n * // Do something for large screens and above\n * }\n */\nfunction useBreakpoint(): Breakpoint {\n\treturn useSyncExternalStore(\n\t\tsubscribeToBreakpointChanges,\n\t\tgetCurrentBreakpointSnapshot,\n\t\t() => \"default\", // SSR fallback\n\t);\n}\n\n/**\n * React hook that returns true if the current viewport width is below the specified breakpoint.\n *\n * This hook uses `window.matchMedia` with a max-width media query and leverages\n * `useSyncExternalStore` to stay compliant with React's concurrent rendering model.\n *\n * @param {TailwindBreakpoint} breakpoint - The breakpoint to check against (e.g., \"md\", \"lg\").\n *\n * @returns {boolean} `true` if the viewport width is below the breakpoint, otherwise `false`.\n *\n * @example\n * // Check if viewport is below medium (768px)\n * const isBelowMd = useIsBelowBreakpoint(\"md\");\n */\nfunction useIsBelowBreakpoint(breakpoint: TailwindBreakpoint): boolean {\n\treturn useSyncExternalStore(\n\t\tcreateBelowBreakpointSubscribe(breakpoint),\n\t\tcreateBelowBreakpointGetSnapshot(breakpoint),\n\t\t() => false, // SSR fallback - assume desktop\n\t);\n}\n\nexport {\n\t//,\n\tbreakpoints,\n\tuseBreakpoint,\n\tuseIsBelowBreakpoint,\n};\n\nexport type {\n\t//,\n\tBreakpoint,\n\tTailwindBreakpoint,\n};\n\n/**\n * A CSS media query string representing a minimum width in `rem` units.\n *\n * @example\n * const query: MinWidthQuery = \"(min-width: 48rem)\";\n *\n * @private\n */\ntype MinWidthQuery = `(min-width: ${number}rem)`;\n\n/**\n * A CSS media query string representing a maximum width in `rem` units.\n *\n * @example\n * const query: MaxWidthQuery = \"(max-width: 47.99rem)\";\n *\n * @private\n */\ntype MaxWidthQuery = `(max-width: ${number}rem)`;\n\n/**\n * Precomputed min-width media query strings for each Tailwind breakpoint.\n *\n * Using constants avoids template string work in hot paths and ensures type\n * safety against the `MinWidthQuery` template literal type.\n *\n * @remarks\n * These are expressed in `rem`. If your CSS breakpoints are in `px`, consider\n * aligning units to avoid JS/CSS drift when `html{font-size}` changes.\n *\n * @private\n */\nconst breakpointQueries = {\n\t\"2xl\": \"(min-width: 96rem)\" as const,\n\txl: \"(min-width: 80rem)\" as const,\n\tlg: \"(min-width: 64rem)\" as const,\n\tmd: \"(min-width: 48rem)\" as const,\n\tsm: \"(min-width: 40rem)\" as const,\n\txs: \"(min-width: 30rem)\" as const,\n\t\"2xs\": \"(min-width: 22.5rem)\" as const,\n} as const satisfies Record<TailwindBreakpoint, MinWidthQuery>;\n\n/**\n * Precomputed max-width media query strings used by `useIsBelowBreakpoint`.\n *\n * The `-0.01rem` offset avoids overlap at exact boundaries.\n *\n * @private\n */\nconst belowBreakpointQueries = {\n\t\"2xl\": \"(max-width: 95.99rem)\" as const, // 96 - 0.01\n\txl: \"(max-width: 79.99rem)\" as const, // 80 - 0.01\n\tlg: \"(max-width: 63.99rem)\" as const, // 64 - 0.01\n\tmd: \"(max-width: 47.99rem)\" as const, // 48 - 0.01\n\tsm: \"(max-width: 39.99rem)\" as const, // 40 - 0.01\n\txs: \"(max-width: 29.99rem)\" as const, // 30 - 0.01\n\t\"2xs\": \"(max-width: 22.49rem)\" as const, // 22.5 - 0.01\n} as const satisfies Record<TailwindBreakpoint, MaxWidthQuery>;\n\n/**\n * Lazily-initialized cache of `MediaQueryList` objects for min-width queries.\n *\n * Initialized on first access to remain SSR-safe (no `window` at import time).\n *\n * @private\n */\nlet minWidthMQLs: Record<TailwindBreakpoint, MediaQueryList> | null = null;\n\n/**\n * Lazily-initialized cache of `MediaQueryList` objects for max-width queries.\n *\n * Used by `useIsBelowBreakpoint`. Also SSR-safe by lazy access.\n *\n * @private\n */\nlet maxWidthMQLs: Record<TailwindBreakpoint, MediaQueryList> | null = null;\n\n/**\n * Get (and lazily create) the cached `MediaQueryList` objects for min-width queries.\n *\n * @returns A record of `MediaQueryList` keyed by Tailwind breakpoint.\n * @private\n */\nfunction getMinWidthMQLs(): Record<TailwindBreakpoint, MediaQueryList> {\n\tif (!minWidthMQLs) {\n\t\tminWidthMQLs = {\n\t\t\t\"2xl\": window.matchMedia(breakpointQueries[\"2xl\"]),\n\t\t\txl: window.matchMedia(breakpointQueries.xl),\n\t\t\tlg: window.matchMedia(breakpointQueries.lg),\n\t\t\tmd: window.matchMedia(breakpointQueries.md),\n\t\t\tsm: window.matchMedia(breakpointQueries.sm),\n\t\t\txs: window.matchMedia(breakpointQueries.xs),\n\t\t\t\"2xs\": window.matchMedia(breakpointQueries[\"2xs\"]),\n\t\t};\n\t}\n\treturn minWidthMQLs;\n}\n\n/**\n * Get (and lazily create) the cached `MediaQueryList` for a specific max-width breakpoint.\n *\n * @param breakpoint - Tailwind breakpoint identifier (e.g., \"md\").\n * @returns The corresponding `MediaQueryList`.\n * @private\n */\nfunction getMaxWidthMQL(breakpoint: TailwindBreakpoint): MediaQueryList {\n\tif (!maxWidthMQLs) {\n\t\tmaxWidthMQLs = {\n\t\t\t\"2xl\": window.matchMedia(belowBreakpointQueries[\"2xl\"]),\n\t\t\txl: window.matchMedia(belowBreakpointQueries.xl),\n\t\t\tlg: window.matchMedia(belowBreakpointQueries.lg),\n\t\t\tmd: window.matchMedia(belowBreakpointQueries.md),\n\t\t\tsm: window.matchMedia(belowBreakpointQueries.sm),\n\t\t\txs: window.matchMedia(belowBreakpointQueries.xs),\n\t\t\t\"2xs\": window.matchMedia(belowBreakpointQueries[\"2xs\"]),\n\t\t};\n\t}\n\treturn maxWidthMQLs[breakpoint];\n}\n\n/**\n * Current breakpoint value used by the singleton store backing `useBreakpoint`.\n *\n * Initialized to `\"default\"` and updated on media-query change events.\n *\n * @private\n */\nlet currentBreakpointValue: Breakpoint = \"default\";\n\n/**\n * Set of component listeners subscribed to the singleton breakpoint store.\n *\n * Each listener is invoked when the current breakpoint value changes.\n *\n * @private\n */\nconst breakpointListeners = new Set<() => void>();\n\n/**\n * Flag indicating whether global media-query listeners are currently attached.\n *\n * Prevents duplicate registrations and enables full teardown when unused.\n *\n * @private\n */\nlet breakpointSubscriptionActive = false;\n\n/**\n * Compute the current breakpoint by checking cached min-width MQLs\n * from largest to smallest.\n *\n * @returns {Breakpoint} The largest matching breakpoint, or `\"default\"`.\n * @private\n */\nfunction getCurrentBreakpoint(): Breakpoint {\n\tconst mqls = getMinWidthMQLs();\n\tfor (const breakpoint of tailwindBreakpoints) {\n\t\tif (mqls[breakpoint].matches) {\n\t\t\treturn breakpoint;\n\t\t}\n\t}\n\treturn \"default\";\n}\n\n/**\n * Update the current breakpoint value and notify all listeners.\n *\n * Uses `requestAnimationFrame` to coalesce rapid resize events and minimize\n * re-renders during active window resizing.\n *\n * @private\n */\nlet breakpointUpdatePending = false;\nfunction updateCurrentBreakpoint() {\n\tif (!breakpointUpdatePending) {\n\t\tbreakpointUpdatePending = true;\n\t\trequestAnimationFrame(() => {\n\t\t\tbreakpointUpdatePending = false;\n\t\t\tconst newBreakpoint = getCurrentBreakpoint();\n\t\t\tif (newBreakpoint !== currentBreakpointValue) {\n\t\t\t\tcurrentBreakpointValue = newBreakpoint;\n\t\t\t\tfor (const listener of breakpointListeners) {\n\t\t\t\t\tlistener();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n}\n\n/**\n * Subscribe a component to breakpoint changes (singleton pattern).\n *\n * Ensures only one set of MQL listeners exists app-wide. Also reconciles the\n * `useSyncExternalStore` initial snapshot/subscribe race by invoking the\n * subscriber once on mount.\n *\n * @param callback - Listener invoked when the breakpoint value may have changed.\n * @returns Cleanup function to unsubscribe the listener.\n * @private\n */\nfunction subscribeToBreakpointChanges(callback: () => void) {\n\tbreakpointListeners.add(callback);\n\n\t// Attach global listeners once\n\tif (!breakpointSubscriptionActive) {\n\t\tbreakpointSubscriptionActive = true;\n\t\tconst mqls = getMinWidthMQLs();\n\n\t\t// Initialize current value synchronously\n\t\tcurrentBreakpointValue = getCurrentBreakpoint();\n\n\t\t// Attach listeners to all breakpoint MQLs\n\t\tfor (const mql of Object.values(mqls)) {\n\t\t\tmql.addEventListener(\"change\", updateCurrentBreakpoint);\n\t\t}\n\t}\n\n\t// Reconcile initial getSnapshot vs subscribe ordering\n\tcallback();\n\n\t// Cleanup\n\treturn () => {\n\t\tbreakpointListeners.delete(callback);\n\n\t\t// Tear down global listeners when no one is listening\n\t\tif (breakpointListeners.size === 0 && breakpointSubscriptionActive) {\n\t\t\tbreakpointSubscriptionActive = false;\n\t\t\tconst mqls = getMinWidthMQLs();\n\t\t\tfor (const mql of Object.values(mqls)) {\n\t\t\t\tmql.removeEventListener(\"change\", updateCurrentBreakpoint);\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Return the current breakpoint value from the singleton store.\n *\n * Used as the `getSnapshot` for `useSyncExternalStore`.\n *\n * @returns {Breakpoint} The latest computed breakpoint.\n * @private\n */\nfunction getCurrentBreakpointSnapshot(): Breakpoint {\n\treturn currentBreakpointValue;\n}\n\n/**\n * Cached `subscribe` functions keyed by breakpoint.\n *\n * Without caching, `useSyncExternalStore` receives a new function reference on\n * every render, causing it to tear down and re-attach the MQL listener each\n * time — the primary source of resize sluggishness.\n *\n * @private\n */\nconst belowBreakpointSubscribeCache = new Map<\n\tTailwindBreakpoint,\n\t(callback: () => void) => () => void\n>();\n\n/**\n * Get (or create and cache) a `subscribe` function for a specific \"below\" breakpoint.\n *\n * Uses a cached `MediaQueryList` and rAF-throttled change handler to avoid\n * bursty updates during resize.\n *\n * @param breakpoint - Tailwind breakpoint identifier (e.g., \"lg\").\n * @returns A stable `subscribe` function suitable for `useSyncExternalStore`.\n * @private\n */\nfunction createBelowBreakpointSubscribe(breakpoint: TailwindBreakpoint) {\n\tlet cached = belowBreakpointSubscribeCache.get(breakpoint);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tcached = (callback: () => void) => {\n\t\tconst mediaQuery = getMaxWidthMQL(breakpoint);\n\n\t\t// rAF throttle the change callback during active resize\n\t\tlet pending = false;\n\t\tconst onChange = () => {\n\t\t\tif (!pending) {\n\t\t\t\tpending = true;\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tpending = false;\n\t\t\t\t\tcallback();\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tmediaQuery.addEventListener(\"change\", onChange);\n\t\treturn () => {\n\t\t\tmediaQuery.removeEventListener(\"change\", onChange);\n\t\t};\n\t};\n\n\tbelowBreakpointSubscribeCache.set(breakpoint, cached);\n\treturn cached;\n}\n\n/**\n * Cached `getSnapshot` functions keyed by breakpoint.\n *\n * Ensures `useSyncExternalStore` receives a referentially stable function,\n * preventing unnecessary subscription churn.\n *\n * @private\n */\nconst belowBreakpointSnapshotCache = new Map<TailwindBreakpoint, () => boolean>();\n\n/**\n * Get (or create and cache) a `getSnapshot` function for a specific \"below\" breakpoint.\n *\n * Uses the cached `MediaQueryList` for the target breakpoint.\n *\n * @param breakpoint - Tailwind breakpoint identifier (e.g., \"lg\").\n * @returns A stable function that returns `true` when the viewport is below the breakpoint.\n * @private\n */\nfunction createBelowBreakpointGetSnapshot(breakpoint: TailwindBreakpoint) {\n\tlet cached = belowBreakpointSnapshotCache.get(breakpoint);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tcached = () => {\n\t\tconst mediaQuery = getMaxWidthMQL(breakpoint);\n\t\treturn mediaQuery.matches;\n\t};\n\n\tbelowBreakpointSnapshotCache.set(breakpoint, cached);\n\treturn cached;\n}\n","import { useEffect, useMemo, useRef } from \"react\";\n\n/**\n * Returns a memoized callback that always invokes the latest version of the\n * provided callback, while preserving a stable function identity across\n * renders.\n *\n * Use this when you need to pass a callback to a child component, an event\n * handler, or a hook dependency array, but the consumer should not re-run /\n * re-render simply because the callback's identity changed. The returned\n * function never changes reference, but internally always calls through to\n * the latest `callback` passed in.\n *\n * Most commonly used as an internal building block for other hooks (for\n * example, {@link useDebouncedCallback}). It is also re-exported publicly\n * for consumers that need the same pattern.\n *\n * @param callback - The callback to wrap. May be `undefined`, in which case\n * invoking the returned function is a no-op until a callback is provided\n * on a subsequent render.\n * @returns A stable function with the same signature as `callback` that\n * forwards its arguments to the most recent `callback` value.\n *\n * @example\n * // Pass a stable handler to a memoized child without re-rendering it\n * const onSelect = useCallbackRef((id: string) => {\n * // reads the latest `props.items` without being in deps\n * props.onSelectItem(id, props.items);\n * });\n *\n * return <MemoizedList onSelect={onSelect} />;\n */\nfunction useCallbackRef<T extends (...args: unknown[]) => unknown>(callback: T | undefined): T {\n\tconst callbackRef = useRef(callback);\n\n\tuseEffect(() => {\n\t\tcallbackRef.current = callback;\n\t});\n\n\treturn useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport {\n\t//,\n\tuseCallbackRef,\n};\n","import { useCallback, useEffect, useRef } from \"react\";\nimport { useCallbackRef } from \"./use-callback-ref.js\";\n\n/**\n * Options for {@link useDebouncedCallback}.\n */\ntype Options = {\n\t/**\n\t * The delay in milliseconds to wait between the last invocation and\n\t * actually running the callback.\n\t */\n\twaitMs: number;\n};\n\n/**\n * Returns a debounced version of the provided callback. Each call resets a\n * timer; the underlying callback only runs after `options.waitMs` of\n * inactivity has elapsed.\n *\n * Useful for limiting rapid invocations such as search-as-you-type inputs,\n * window resize handlers, or expensive button-press handlers. The pending\n * timer is automatically cleared on unmount.\n *\n * The debounced function always invokes the latest version of `callbackFn`,\n * so callers do not need to memoize it. The returned function's identity\n * only changes when `options.waitMs` changes, so it is safe to include in\n * dependency arrays.\n *\n * @param callbackFn - The function to debounce. The latest reference passed\n * on each render is always used when the timer fires.\n * @param options - Debounce options.\n * @param options.waitMs - Milliseconds of inactivity to wait before calling\n * `callbackFn`.\n * @returns A function with the same parameter list as `callbackFn` that\n * schedules (or reschedules) the underlying call.\n *\n * @example\n * // Debounce a search input by 300ms\n * const [query, setQuery] = useState(\"\");\n * const search = useDebouncedCallback((value: string) => {\n * fetchResults(value);\n * }, { waitMs: 300 });\n *\n * return (\n * <input\n * value={query}\n * onChange={(event) => {\n * setQuery(event.target.value);\n * search(event.target.value);\n * }}\n * />\n * );\n */\nfunction useDebouncedCallback<T extends (...args: unknown[]) => unknown>(\n\tcallbackFn: T,\n\toptions: Options,\n) {\n\tconst stableCallbackFn = useCallbackRef(callbackFn);\n\tconst debounceTimerRef = useRef(0);\n\tuseEffect(() => () => window.clearTimeout(debounceTimerRef.current), []);\n\n\treturn useCallback(\n\t\t(...args: Parameters<T>) => {\n\t\t\twindow.clearTimeout(debounceTimerRef.current);\n\t\t\tdebounceTimerRef.current = window.setTimeout(() => stableCallbackFn(...args), options.waitMs);\n\t\t},\n\t\t[stableCallbackFn, options.waitMs],\n\t);\n}\n\nexport {\n\t//,\n\tuseDebouncedCallback,\n};\n","import { useMemo } from \"react\";\n\n/**\n * React hook that returns a random, stable id (e.g. `\"mantle-a3f9k7q\"`)\n * suitable for DOM `id` attributes and `aria-*` references.\n *\n * Unlike React's built-in `useId`, the generated suffix does not contain\n * special characters (`:`). The default id is safe to use directly in CSS\n * selectors and `querySelector` calls; if you provide a custom `prefix`,\n * keep it selector-safe or escape the final id with `CSS.escape()` before\n * querying. The id is generated once for the lifetime of the component and\n * is stable across re-renders, but a new value is produced when `prefix`\n * changes.\n *\n * @param prefix - Optional string prepended to the generated suffix.\n * Whitespace-only or empty values fall back to `\"mantle\"`. Use a\n * selector-safe prefix if you plan to reference the id in CSS selectors\n * without escaping. Defaults to `\"mantle\"`.\n * @returns A string of the form `\"<prefix>-<7-char-random>\"`.\n *\n * @example\n * // Associate a label with a custom input\n * const id = useRandomStableId(\"email-input\");\n *\n * return (\n * <>\n * <label htmlFor={id}>Email</label>\n * <input id={id} type=\"email\" />\n * </>\n * );\n *\n * @example\n * // Use as an aria-controls reference\n * const panelId = useRandomStableId(\"panel\");\n *\n * return (\n * <>\n * <button aria-controls={panelId}>Toggle</button>\n * <div id={panelId}>Panel contents</div>\n * </>\n * );\n */\nconst useRandomStableId = (prefix = \"mantle\") => useMemo(() => randomStableId(prefix), [prefix]);\n\nexport {\n\t//,\n\tuseRandomStableId,\n};\n\nfunction randomStableId(prefix = \"mantle\") {\n\tconst safePrefix = prefix.trim() || \"mantle\";\n\treturn [safePrefix, randomPostfix()].join(\"-\");\n}\n\nfunction randomPostfix() {\n\treturn Math.random().toString(36).substring(2, 9);\n}\n","import { useMemo } from \"react\";\nimport { usePrefersReducedMotion } from \"./use-prefers-reduced-motion.js\";\n\n/**\n * `scroll-behavior` values:\n *\n * - `\"auto\"` — scrolling happens instantly (no animation).\n * - `\"smooth\"` — scrolling animates smoothly using a user-agent–defined easing and duration.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior#values\n */\nexport type ScrollBehavior = \"auto\" | \"smooth\";\n\n/**\n * React hook that returns a {@link ScrollBehavior} value (`\"auto\"` or\n * `\"smooth\"`) that respects the user's motion preference.\n *\n * Internally calls {@link usePrefersReducedMotion}: when reduced motion is\n * preferred, this returns `\"auto\"` (no animated scroll); otherwise it\n * returns `\"smooth\"`. Pair this with `window.scrollTo`,\n * `Element.scrollIntoView`, or any other scroll API that accepts a\n * `behavior` option to avoid forcing animations on users who have opted\n * out of motion. The conservative SSR default also prevents \"first paint\"\n * scroll animations.\n *\n * @returns `\"auto\"` when the user prefers reduced motion, otherwise\n * `\"smooth\"`.\n *\n * @example\n * // Scroll to the top of the page on a button click\n * const behavior = useScrollBehavior();\n *\n * return (\n * <button onClick={() => window.scrollTo({ top: 0, behavior })}>\n * Back to top\n * </button>\n * );\n *\n * @example\n * // Bring a referenced section into view\n * const behavior = useScrollBehavior();\n * const sectionRef = useRef<HTMLElement>(null);\n *\n * function focusSection() {\n * sectionRef.current?.scrollIntoView({ behavior, block: \"start\" });\n * }\n *\n * @see {@link usePrefersReducedMotion}\n * @see CSS `scroll-behavior` property (values: `\"auto\"`, `\"smooth\"`).\n */\nexport function useScrollBehavior(): ScrollBehavior {\n\tconst prefersReducedMotion = usePrefersReducedMotion();\n\n\treturn useMemo(() => (prefersReducedMotion ? \"auto\" : \"smooth\"), [prefersReducedMotion]);\n}\n","\"use client\";\n\nimport { type RefObject, useEffect, useState } from \"react\";\nimport type { InViewOptions, MarginType } from \"../utils/in-view.js\";\nimport { inView } from \"../utils/in-view.js\";\n\n/**\n * Options for the `useInView` hook.\n */\ntype UseInViewOptions = {\n\t/**\n\t * A ref to a scrollable container element to use as the intersection root.\n\t * Defaults to the browser viewport.\n\t */\n\troot?: RefObject<Element | null>;\n\n\t/**\n\t * Expand or contract the detected area from each side of the root's bounding box.\n\t * Uses the same syntax as the CSS `margin` shorthand (e.g. `\"10px\"`, `\"10% 20px\"`).\n\t */\n\tmargin?: MarginType;\n\n\t/**\n\t * How much of the element must be visible before it is considered in view.\n\t * - `\"some\"` (default): Any part of the element is visible.\n\t * - `\"all\"`: The entire element is visible.\n\t * - `number`: An intersection ratio between `0` and `1` (e.g. `0.5` for 50%).\n\t */\n\tamount?: \"some\" | \"all\" | number;\n\n\t/**\n\t * If `true`, stop observing once the element enters the viewport for the\n\t * first time. Useful for one-shot entrance animations.\n\t * Defaults to `false`.\n\t */\n\tonce?: boolean;\n\n\t/**\n\t * The initial visibility state returned before the observer has attached.\n\t * Defaults to `false`.\n\t */\n\tinitial?: boolean;\n};\n\n/**\n * React hook that tracks whether a DOM element is visible within the viewport\n * (or a scrollable container) using the `IntersectionObserver` API.\n *\n * @param ref - A ref attached to the element to observe.\n * @param options - Options controlling the scroll root, margin, threshold,\n * initial state, and one-time detection.\n * @returns `true` if the element is currently in view, otherwise `false`.\n *\n * @example\n * // Basic usage\n * const ref = useRef<HTMLDivElement>(null);\n * const isInView = useInView(ref);\n *\n * return <div ref={ref}>{isInView ? \"Visible!\" : \"Hidden\"}</div>;\n *\n * @example\n * // Trigger once when the element first enters the viewport\n * const ref = useRef<HTMLDivElement>(null);\n * const isInView = useInView(ref, { once: true });\n *\n * return (\n * <div\n * ref={ref}\n * style={{ opacity: isInView ? 1 : 0, transition: \"opacity 0.5s\" }}\n * />\n * );\n *\n * @example\n * // Require 50% of the element to be visible\n * const ref = useRef<HTMLDivElement>(null);\n * const isInView = useInView(ref, { amount: 0.5 });\n */\nfunction useInView(\n\tref: RefObject<Element | null>,\n\t{ root, margin, amount, once = false, initial = false }: UseInViewOptions = {},\n): boolean {\n\tconst [isInView, setInView] = useState(initial);\n\n\tuseEffect(() => {\n\t\tif (!ref.current || (once && isInView)) {\n\t\t\treturn;\n\t\t}\n\n\t\tfunction onEnter() {\n\t\t\tsetInView(true);\n\t\t\treturn once ? undefined : () => setInView(false);\n\t\t}\n\n\t\tconst options: InViewOptions = {\n\t\t\troot: (root && root.current) || undefined,\n\t\t\tmargin,\n\t\t\tamount,\n\t\t};\n\n\t\treturn inView(ref.current, onEnter, options);\n\t\t/**\n\t\t * Intentionally omit `isInView` from deps. The effect must only re-run\n\t\t * when the observation parameters change, not when visibility changes.\n\t\t * Including `isInView` would restart the observer (disconnect + reconnect)\n\t\t * on every enter/leave event, causing wasteful churn for the common\n\t\t * `once=false` case.\n\t\t */\n\t\t// oxlint-disable-next-line react-hooks/exhaustive-deps\n\t}, [root, ref, margin, once, amount]);\n\n\treturn isInView;\n}\n\nexport { useInView };\nexport type { UseInViewOptions };\n","import { useCallback, useMemo, useReducer } from \"react\";\n\ntype UndoRedoState<T> = {\n\tundoStack: T[];\n\tredoStack: T[];\n};\n\ntype UndoRedoAction<T> =\n\t| { type: \"push\"; snapshot: T }\n\t| { type: \"undo\"; current: T }\n\t| { type: \"redo\"; current: T };\n\nfunction undoRedoReducer<T>(state: UndoRedoState<T>, action: UndoRedoAction<T>): UndoRedoState<T> {\n\tswitch (action.type) {\n\t\tcase \"push\": {\n\t\t\treturn {\n\t\t\t\tundoStack: [...state.undoStack, action.snapshot],\n\t\t\t\tredoStack: [],\n\t\t\t};\n\t\t}\n\t\tcase \"undo\": {\n\t\t\tif (state.undoStack.length === 0) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\tconst undoStack = state.undoStack.slice(0, -1);\n\t\t\tconst previous = state.undoStack[state.undoStack.length - 1];\n\t\t\tif (previous === undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tundoStack,\n\t\t\t\tredoStack: [...state.redoStack, action.current],\n\t\t\t};\n\t\t}\n\t\tcase \"redo\": {\n\t\t\tif (state.redoStack.length === 0) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\tconst redoStack = state.redoStack.slice(0, -1);\n\t\t\tconst next = state.redoStack[state.redoStack.length - 1];\n\t\t\tif (next === undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tundoStack: [...state.undoStack, action.current],\n\t\t\t\tredoStack,\n\t\t\t};\n\t\t}\n\t}\n}\n\ntype UseUndoRedoReturn<T> = {\n\t/** Whether there are actions to undo. */\n\tcanUndo: boolean;\n\t/** Whether there are actions to redo. */\n\tcanRedo: boolean;\n\t/** Push a snapshot onto the undo stack. Clears the redo stack. */\n\tpush: (snapshot: T) => void;\n\t/** Pop the last snapshot from the undo stack. Returns `undefined` if empty. */\n\tundo: (current: T) => T | undefined;\n\t/** Pop the last snapshot from the redo stack. Returns `undefined` if empty. */\n\tredo: (current: T) => T | undefined;\n};\n\n/**\n * Generic undo/redo hook backed by a reducer that maintains two history\n * stacks (undo and redo).\n *\n * The hook does not own your application state — instead it helps you\n * snapshot it. Call `push(snapshot)` *before* mutating state to capture\n * the current value, then call `undo(current)` or `redo(current)` to swap\n * `current` with the previous/next snapshot. Both `undo` and `redo` return\n * the snapshot to apply, or `undefined` if their stack is empty. Pushing a\n * new snapshot clears the redo stack, matching standard editor semantics.\n *\n * @typeParam T - The type of the value being snapshotted (e.g. a list of\n * items, a serialized form value, etc.).\n *\n * @returns An object with the current undo/redo capability flags and\n * actions:\n * - `canUndo`: `true` when there is at least one snapshot on the undo\n * stack.\n * - `canRedo`: `true` when there is at least one snapshot on the redo\n * stack.\n * - `push(snapshot)`: Push a snapshot onto the undo stack and clear the\n * redo stack. Call this *before* mutating state.\n * - `undo(current)`: Pop the latest undo snapshot and return it; returns\n * `undefined` when the undo stack is empty. The supplied `current` is\n * pushed onto the redo stack so you can redo back to it.\n * - `redo(current)`: Pop the latest redo snapshot and return it; returns\n * `undefined` when the redo stack is empty. The supplied `current` is\n * pushed onto the undo stack.\n *\n * @example\n * // Snapshot before mutating, then wire up keyboard shortcuts\n * const [items, setItems] = useState<string[]>([]);\n * const { push, undo, redo, canUndo, canRedo } = useUndoRedo<string[]>();\n *\n * function removeItem(item: string) {\n * push(items); // snapshot before mutation\n * setItems((prev) => prev.filter((entry) => entry !== item));\n * }\n *\n * function handleKeyDown(event: React.KeyboardEvent) {\n * const cmd = event.metaKey || event.ctrlKey;\n * if (cmd && event.key === \"z\" && !event.shiftKey) {\n * const previous = undo(items);\n * if (previous) {\n * setItems(previous);\n * }\n * }\n * if (cmd && ((event.shiftKey && event.key === \"z\") || event.key === \"y\")) {\n * const next = redo(items);\n * if (next) {\n * setItems(next);\n * }\n * }\n * }\n *\n * return (\n * <div tabIndex={0} onKeyDown={handleKeyDown}>\n * <button disabled={!canUndo} onClick={() => { const previous = undo(items); if (previous) setItems(previous); }}>Undo</button>\n * <button disabled={!canRedo} onClick={() => { const next = redo(items); if (next) setItems(next); }}>Redo</button>\n * </div>\n * );\n */\nfunction useUndoRedo<T>(): UseUndoRedoReturn<T> {\n\tconst [state, dispatch] = useReducer(undoRedoReducer<T>, {\n\t\tundoStack: [],\n\t\tredoStack: [],\n\t});\n\n\tconst push = useCallback((snapshot: T) => {\n\t\tdispatch({ type: \"push\", snapshot });\n\t}, []);\n\n\tconst undo = useCallback(\n\t\t(current: T): T | undefined => {\n\t\t\tconst previous = state.undoStack[state.undoStack.length - 1];\n\t\t\tif (previous === undefined) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tdispatch({ type: \"undo\", current });\n\t\t\treturn previous;\n\t\t},\n\t\t[state.undoStack],\n\t);\n\n\tconst redo = useCallback(\n\t\t(current: T): T | undefined => {\n\t\t\tconst next = state.redoStack[state.redoStack.length - 1];\n\t\t\tif (next === undefined) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tdispatch({ type: \"redo\", current });\n\t\t\treturn next;\n\t\t},\n\t\t[state.redoStack],\n\t);\n\n\treturn useMemo(\n\t\t() => ({\n\t\t\tcanUndo: state.undoStack.length > 0,\n\t\t\tcanRedo: state.redoStack.length > 0,\n\t\t\tpush,\n\t\t\tundo,\n\t\t\tredo,\n\t\t}),\n\t\t[state.undoStack.length, state.redoStack.length, push, undo, redo],\n\t);\n}\n\nexport {\n\t//,\n\tuseUndoRedo,\n};\n\nexport type {\n\t//,\n\tUseUndoRedoReturn,\n};\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as SvgAttributes } from "./types-
|
|
1
|
+
import { t as SvgAttributes } from "./types-DnghL1WE.js";
|
|
2
2
|
import * as _$react from "react";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
|
|
@@ -24,4 +24,4 @@ type IconProps = Omit<SvgAttributes, "children"> & {
|
|
|
24
24
|
declare const Icon: _$react.ForwardRefExoticComponent<Omit<IconProps, "ref"> & _$react.RefAttributes<SVGSVGElement>>;
|
|
25
25
|
//#endregion
|
|
26
26
|
export { IconProps as n, Icon as t };
|
|
27
|
-
//# sourceMappingURL=icon-
|
|
27
|
+
//# sourceMappingURL=icon-D_BMDi_q.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as WithAsChild } from "./as-child-
|
|
2
|
-
import { t as VariantProps } from "./variant-props-
|
|
1
|
+
import { t as WithAsChild } from "./as-child-C2PttRwz.js";
|
|
2
|
+
import { t as VariantProps } from "./variant-props-B4io4uA_.js";
|
|
3
3
|
import * as _$react from "react";
|
|
4
4
|
import { ButtonHTMLAttributes, ReactNode } from "react";
|
|
5
5
|
import * as _$class_variance_authority_types0 from "class-variance-authority/types";
|
|
@@ -95,4 +95,4 @@ type IconButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & WithAsChild & I
|
|
|
95
95
|
declare const IconButton: _$react.ForwardRefExoticComponent<IconButtonProps & _$react.RefAttributes<HTMLButtonElement>>;
|
|
96
96
|
//#endregion
|
|
97
97
|
export { IconButtonProps as n, IconButton as t };
|
|
98
|
-
//# sourceMappingURL=icon-button-
|
|
98
|
+
//# sourceMappingURL=icon-button-Dty-yfE2.d.ts.map
|
package/dist/icon.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as SvgAttributes } from "./types-
|
|
2
|
-
import { n as IconProps, t as Icon } from "./icon-
|
|
3
|
-
import { n as SvgOnlyProps, t as SvgOnly } from "./svg-only-
|
|
1
|
+
import { t as SvgAttributes } from "./types-DnghL1WE.js";
|
|
2
|
+
import { n as IconProps, t as Icon } from "./icon-D_BMDi_q.js";
|
|
3
|
+
import { n as SvgOnlyProps, t as SvgOnly } from "./svg-only-CLbMy439.js";
|
|
4
4
|
export { Icon, type IconProps, type SvgAttributes, SvgOnly, type SvgOnlyProps };
|
package/dist/icons.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as SvgAttributes } from "./types-
|
|
2
|
-
import { a as AlphanumericSortingDirection, c as TimeSortingDirection, o as SortingDirection, s as SortingMode } from "./direction-
|
|
3
|
-
import { i as Theme } from "./themes-
|
|
1
|
+
import { t as SvgAttributes } from "./types-DnghL1WE.js";
|
|
2
|
+
import { a as AlphanumericSortingDirection, c as TimeSortingDirection, o as SortingDirection, s as SortingMode } from "./direction-CVntIxOS.js";
|
|
3
|
+
import { i as Theme } from "./themes-f2W5S6xS.js";
|
|
4
4
|
import { ComponentProps } from "react";
|
|
5
5
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
6
6
|
|
package/dist/icons.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{s as e}from"./theme-provider-BFcnjeME.js";import{t}from"./traffic-policy-file-
|
|
1
|
+
import{s as e}from"./theme-provider-BFcnjeME.js";import{t}from"./traffic-policy-file-BwHHdhWJ.js";import{t as n}from"./sort-mXo37xN2.js";import{jsx as r}from"react/jsx-runtime";import{DesktopIcon as i}from"@phosphor-icons/react/Desktop";import{MoonIcon as a}from"@phosphor-icons/react/Moon";import{SunIcon as o}from"@phosphor-icons/react/Sun";function s(t){return r(c,{theme:e(),...t})}s.displayName=`AutoThemeIcon`;function c({theme:e,...t}){switch(e){case`system`:return r(i,{...t});case`light`:return r(o,{...t});case`dark`:return r(a,{...t});case`light-high-contrast`:return r(o,{...t,weight:`fill`});case`dark-high-contrast`:return r(a,{...t,weight:`fill`})}}c.displayName=`ThemeIcon`;function l(e){return r(`svg`,{fill:`currentColor`,height:`1em`,viewBox:`0 0 94 36`,width:`2.61em`,...e,children:r(`path`,{d:`M32.272 12.011c-1.298-1.466-2.904-2.205-4.812-2.205-1.176 0-2.26.233-3.255.7a7.995 7.995 0 0 0-2.581 1.906 9.205 9.205 0 0 0-1.715 2.853 9.773 9.773 0 0 0-.628 3.546c0 1.25.194 2.39.58 3.419.362.98.918 1.877 1.635 2.636A7.543 7.543 0 0 0 24 26.584c.965.41 2.025.617 3.176.617.522 0 1.005-.041 1.445-.116.439-.075.858-.2 1.26-.37.4-.175.79-.398 1.18-.664.385-.27.792-.612 1.21-1.018v4.353h-.005v.421h-5.33l-4.005 4.64v.798h15.037v-24.98h-5.697v1.746Zm-.014 7.979a4.25 4.25 0 0 1-.786 1.215 3.555 3.555 0 0 1-2.592 1.1 3.627 3.627 0 0 1-1.464-.292 3.508 3.508 0 0 1-1.166-.808 3.93 3.93 0 0 1-1.054-2.72c0-.519.097-1.006.298-1.457a3.77 3.77 0 0 1 .804-1.181 4.114 4.114 0 0 1 1.162-.808 3.484 3.484 0 0 1 2.817-.016c.448.19.844.463 1.181.81.336.347.6.743.804 1.194.202.452.298.95.298 1.493 0 .505-.104 1.005-.302 1.47m-16.261-7.708a6.173 6.173 0 0 0-2.06-1.602 4.875 4.875 0 0 0-.57-.22 6.383 6.383 0 0 0-.923-.216H8.383L5.697 13.39v-3.082H.002v16.61h5.695V15.712h5.35l.444-.01v11.214h5.697V16.528c0-.885-.084-1.674-.25-2.366a4.655 4.655 0 0 0-.941-1.877zm38.367-2.018h-6.213l-2.47 2.863v-2.864h-5.7v16.61h5.71l.004-11.117h4.144l4.526-5.26zm31.051 7.672 7.79-7.392v-.281H85.7l-5.975 5.991V0h-5.696v26.87h5.696v-6.766l6.262 6.763h7.663v-.316l-8.233-8.617zm-16.11-5.78a9.436 9.436 0 0 0-3.085-1.842 10.953 10.953 0 0 0-3.855-.664c-1.407 0-2.705.226-3.884.678a9.611 9.611 0 0 0-3.072 1.858 8.488 8.488 0 0 0-2.016 2.788 8.281 8.281 0 0 0-.722 3.449c0 1.362.24 2.596.722 3.707a8.52 8.52 0 0 0 2.002 2.862c.85.798 1.86 1.415 3.036 1.847 1.177.432 2.455.647 3.842.647 1.406 0 2.707-.215 3.919-.647 1.204-.431 2.24-1.04 3.098-1.833a8.583 8.583 0 0 0 2.031-2.816c.493-1.09.742-2.29.742-3.611 0-1.316-.244-2.52-.722-3.612a8.424 8.424 0 0 0-2.035-2.81Zm-3.558 7.864c-.2.461-.463.869-.786 1.215a3.573 3.573 0 0 1-2.592 1.1c-.502 0-.981-.096-1.434-.291a3.44 3.44 0 0 1-1.16-.809 4.155 4.155 0 0 1-.788-1.215 3.825 3.825 0 0 1-.297-1.537c0-.517.098-1.004.297-1.456.201-.451.46-.849.787-1.194a3.579 3.579 0 0 1 2.597-1.1c.502 0 .98.096 1.43.29.448.19.839.461 1.16.81.328.345.586.752.786 1.214.2.461.297.954.297 1.471 0 .538-.096 1.04-.297 1.502`})})}function u(e){return r(`svg`,{fill:`currentColor`,height:`1em`,viewBox:`0 0 32 32`,width:`1em`,...e,children:r(`path`,{d:`M27.2 6.18a9.47 9.47 0 0 0-3.12-2.5A9.42 9.42 0 0 0 21.82 3h-6.14l-4.06 4.9V3.1H3V29h8.62V11.53h8.09l.67-.02v17.48H29V12.8c0-1.37-.13-2.6-.38-3.68a7.35 7.35 0 0 0-1.42-2.93Z`})})}export{s as AutoThemeIcon,u as NgrokLettermarkIcon,l as NgrokWordmarkIcon,n as SortIcon,c as ThemeIcon,t as TrafficPolicyFileIcon};
|
|
2
2
|
//# sourceMappingURL=icons.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
const e={some:0,all:1};function t(t,n,{root:r,margin:i,amount:a=`some`}={}){let o=new WeakMap,s=new IntersectionObserver(e=>{e.forEach(e=>{let t=o.get(e.target);if(e.isIntersecting!==!!t)if(e.isIntersecting){let t=n(e.target,e);typeof t==`function`?o.set(e.target,t):s.unobserve(e.target)}else typeof t==`function`&&(t(e),o.delete(e.target))})},{root:r,rootMargin:i,threshold:typeof a==`number`?a:e[a]});return s.observe(t),()=>{s.unobserve(t),s.disconnect()}}export{t};
|
|
2
|
-
//# sourceMappingURL=in-view-
|
|
2
|
+
//# sourceMappingURL=in-view-BLZVEGFC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-view-
|
|
1
|
+
{"version":3,"file":"in-view-BLZVEGFC.js","names":[],"sources":["../src/utils/in-view.ts"],"sourcesContent":["type MarginValue = `${number}${\"px\" | \"%\"}`;\n\n/**\n * Margin string used to expand or contract the intersection root's bounding box.\n * Follows the same syntax as the CSS `margin` shorthand (1–4 values).\n *\n * @example \"10px\"\n * @example \"10% 20px\"\n * @example \"10px 20px 10px 20px\"\n */\ntype MarginType =\n\t| MarginValue\n\t| `${MarginValue} ${MarginValue}`\n\t| `${MarginValue} ${MarginValue} ${MarginValue}`\n\t| `${MarginValue} ${MarginValue} ${MarginValue} ${MarginValue}`;\n\n/**\n * Options for the `inView` helper.\n */\ntype InViewOptions = {\n\t/**\n\t * The scrollable container element (or `Document`) to use as the intersection root.\n\t * Defaults to the browser viewport.\n\t */\n\troot?: Element | Document;\n\n\t/**\n\t * Expand or contract the detected area from each side of the root's bounding box.\n\t * Uses the same syntax as the CSS `margin` shorthand (e.g. `\"10px\"`, `\"10% 20px\"`).\n\t */\n\tmargin?: MarginType;\n\n\t/**\n\t * How much of the element must be visible before it is considered in view.\n\t * - `\"some\"` (default): Any part of the element is visible.\n\t * - `\"all\"`: The entire element is visible.\n\t * - `number`: An intersection ratio between `0` and `1` (e.g. `0.5` for 50%).\n\t */\n\tamount?: \"some\" | \"all\" | number;\n};\n\n/**\n * Callback invoked when an observed element leaves the viewport.\n * Receives the `IntersectionObserverEntry` for the departing element.\n */\ntype ViewChangeHandler = (entry: IntersectionObserverEntry) => void;\n\nconst thresholds = {\n\tsome: 0,\n\tall: 1,\n};\n\n/**\n * Observe when a DOM element enters or leaves the viewport (or a scrollable\n * container) using the `IntersectionObserver` API.\n *\n * When the element enters the viewport, `onStart` is called. If `onStart`\n * returns a function, that function is called when the element leaves the\n * viewport. If `onStart` returns nothing, the element is unobserved after\n * the first entry.\n *\n * @param element - The DOM element to observe.\n * @param onStart - Called when the element enters the viewport. Optionally\n * returns a cleanup function called when the element leaves.\n * @param options - Options for the intersection root, margin, and threshold.\n * @returns A cleanup function that disconnects the observer.\n *\n * @example\n * const stop = inView(element, (el) => {\n * el.classList.add(\"visible\");\n * return () => el.classList.remove(\"visible\");\n * });\n *\n * // Later, stop observing:\n * stop();\n */\nfunction inView(\n\telement: Element,\n\tonStart: (element: Element, entry: IntersectionObserverEntry) => void | ViewChangeHandler,\n\t{ root, margin: rootMargin, amount = \"some\" }: InViewOptions = {},\n): VoidFunction {\n\tconst activeIntersections = new WeakMap<Element, ViewChangeHandler>();\n\n\tconst onIntersectionChange: IntersectionObserverCallback = (entries) => {\n\t\tentries.forEach((entry) => {\n\t\t\tconst onEnd = activeIntersections.get(entry.target);\n\n\t\t\t/**\n\t\t\t * If there's no change to the intersection, we don't need to\n\t\t\t * do anything here.\n\t\t\t */\n\t\t\tif (entry.isIntersecting === Boolean(onEnd)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (entry.isIntersecting) {\n\t\t\t\tconst newOnEnd = onStart(entry.target, entry);\n\t\t\t\tif (typeof newOnEnd === \"function\") {\n\t\t\t\t\tactiveIntersections.set(entry.target, newOnEnd);\n\t\t\t\t} else {\n\t\t\t\t\tobserver.unobserve(entry.target);\n\t\t\t\t}\n\t\t\t} else if (typeof onEnd === \"function\") {\n\t\t\t\tonEnd(entry);\n\t\t\t\tactiveIntersections.delete(entry.target);\n\t\t\t}\n\t\t});\n\t};\n\n\tconst observer = new IntersectionObserver(onIntersectionChange, {\n\t\troot,\n\t\trootMargin,\n\t\tthreshold: typeof amount === \"number\" ? amount : thresholds[amount],\n\t});\n\n\tobserver.observe(element);\n\n\treturn () => {\n\t\tobserver.unobserve(element);\n\t\tobserver.disconnect();\n\t};\n}\n\nexport { inView };\nexport type { InViewOptions, MarginType, ViewChangeHandler };\n"],"mappings":"AA+CA,MAAM,EAAa,CAClB,KAAM,EACN,IAAK,EACL,CA0BD,SAAS,EACR,EACA,EACA,CAAE,OAAM,OAAQ,EAAY,SAAS,QAA0B,EAAE,CAClD,CACf,IAAM,EAAsB,IAAI,QA4B1B,EAAW,IAAI,qBA1BuC,GAAY,CACvE,EAAQ,QAAS,GAAU,CAC1B,IAAM,EAAQ,EAAoB,IAAI,EAAM,OAAO,CAM/C,KAAM,iBAAmB,EAAQ,EAIrC,GAAI,EAAM,eAAgB,CACzB,IAAM,EAAW,EAAQ,EAAM,OAAQ,EAAM,CACzC,OAAO,GAAa,WACvB,EAAoB,IAAI,EAAM,OAAQ,EAAS,CAE/C,EAAS,UAAU,EAAM,OAAO,MAEvB,OAAO,GAAU,aAC3B,EAAM,EAAM,CACZ,EAAoB,OAAO,EAAM,OAAO,GAExC,EAG6D,CAC/D,OACA,aACA,UAAW,OAAO,GAAW,SAAW,EAAS,EAAW,GAC5D,CAAC,CAIF,OAFA,EAAS,QAAQ,EAAQ,KAEZ,CACZ,EAAS,UAAU,EAAQ,CAC3B,EAAS,YAAY"}
|
|
@@ -93,4 +93,4 @@ declare function inView(element: Element, onStart: (element: Element, entry: Int
|
|
|
93
93
|
}?: InViewOptions): VoidFunction;
|
|
94
94
|
//#endregion
|
|
95
95
|
export { composeRefs as a, inView as i, MarginType as n, useComposedRefs as o, ViewChangeHandler as r, copyToClipboard as s, InViewOptions as t };
|
|
96
|
-
//# sourceMappingURL=in-view-
|
|
96
|
+
//# sourceMappingURL=in-view-DdIrfU4u.d.ts.map
|
|
@@ -37,4 +37,4 @@ type Color = (typeof colors)[number];
|
|
|
37
37
|
declare const isColor: (value: unknown) => value is Color;
|
|
38
38
|
//#endregion
|
|
39
39
|
export { functionalColors as a, isNamedColor as c, colors as i, namedColors as l, FunctionalColor as n, isColor as o, NamedColor as r, isFunctionalColor as s, Color as t };
|
|
40
|
-
//# sourceMappingURL=index-
|
|
40
|
+
//# sourceMappingURL=index-CVk4t5hk.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as WithAsChild } from "./as-child-
|
|
2
|
-
import { t as VariantProps } from "./variant-props-
|
|
1
|
+
import { t as WithAsChild } from "./as-child-C2PttRwz.js";
|
|
2
|
+
import { t as VariantProps } from "./variant-props-B4io4uA_.js";
|
|
3
3
|
import * as _$react from "react";
|
|
4
4
|
import { ComponentProps } from "react";
|
|
5
5
|
import * as _$class_variance_authority_types0 from "class-variance-authority/types";
|
|
@@ -27,4 +27,4 @@ type ButtonGroupProps = ComponentProps<"div"> & ButtonGroupVariants & WithAsChil
|
|
|
27
27
|
declare const ButtonGroup: _$react.ForwardRefExoticComponent<Omit<ButtonGroupProps, "ref"> & _$react.RefAttributes<HTMLDivElement>>;
|
|
28
28
|
//#endregion
|
|
29
29
|
export { ButtonGroupProps as n, ButtonGroup as t };
|
|
30
|
-
//# sourceMappingURL=index-
|
|
30
|
+
//# sourceMappingURL=index-DIBURJqf.d.ts.map
|
|
@@ -44,4 +44,4 @@ type CssProperties = CSSProperties & Record<CssVariableName, string | number>;
|
|
|
44
44
|
declare const $cssProperties: <T extends CssProperties = CssProperties>(input: T) => CSSProperties;
|
|
45
45
|
//#endregion
|
|
46
46
|
export { parseBooleanish as i, CssProperties as n, Booleanish as r, $cssProperties as t };
|
|
47
|
-
//# sourceMappingURL=index-
|
|
47
|
+
//# sourceMappingURL=index-TI92Xpg5.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as _$react from "react";
|
|
2
|
+
import * as _$_radix_ui_react_slot0 from "@radix-ui/react-slot";
|
|
3
|
+
|
|
4
|
+
//#region src/components/slot/slot.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Merges its props onto its immediate child. This is useful for creating
|
|
7
|
+
* components that can be rendered as different elements. Automatically merges
|
|
8
|
+
* className props using `cx` for proper Tailwind class handling.
|
|
9
|
+
*
|
|
10
|
+
* @see https://mantle.ngrok.com/components/slot
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Slot className="custom-class">
|
|
15
|
+
* <a href="/">Home</a>
|
|
16
|
+
* </Slot>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare const Slot$1: _$react.ForwardRefExoticComponent<Omit<_$_radix_ui_react_slot0.SlotProps & _$react.RefAttributes<HTMLElement>, "ref"> & _$react.RefAttributes<HTMLElement>>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Slot$1 as t };
|
|
22
|
+
//# sourceMappingURL=index-j46YISoN.d.ts.map
|