@planningcenter/tapestry 4.12.0 → 4.13.0-rc.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.
Files changed (134) hide show
  1. package/dist/componentRegistrars.d.ts +1 -0
  2. package/dist/componentRegistrars.d.ts.map +1 -1
  3. package/dist/componentRegistrars.js +6 -2
  4. package/dist/componentRegistrars.js.map +1 -1
  5. package/dist/components/DataTable/ActiveFilters.d.ts +23 -1
  6. package/dist/components/DataTable/ActiveFilters.d.ts.map +1 -1
  7. package/dist/components/DataTable/ActiveFilters.js +49 -4
  8. package/dist/components/DataTable/ActiveFilters.js.map +1 -1
  9. package/dist/components/DataTable/CollectionEditorsLazy.d.ts.map +1 -1
  10. package/dist/components/DataTable/CollectionEditorsLazy.js +20 -18
  11. package/dist/components/DataTable/CollectionEditorsLazy.js.map +1 -1
  12. package/dist/components/DataTable/DataTable.d.ts +66 -50
  13. package/dist/components/DataTable/DataTable.d.ts.map +1 -1
  14. package/dist/components/DataTable/DataTable.js +78 -17
  15. package/dist/components/DataTable/DataTable.js.map +1 -1
  16. package/dist/components/DataTable/ManageSavedViewsModal.d.ts +18 -7
  17. package/dist/components/DataTable/ManageSavedViewsModal.d.ts.map +1 -1
  18. package/dist/components/DataTable/ManageSavedViewsModal.js +120 -104
  19. package/dist/components/DataTable/ManageSavedViewsModal.js.map +1 -1
  20. package/dist/components/DataTable/ManageSavedViewsModal.module.css.js +1 -1
  21. package/dist/components/DataTable/NewSavedViewButton.d.ts.map +1 -1
  22. package/dist/components/DataTable/NewSavedViewButton.js +1 -1
  23. package/dist/components/DataTable/NewSavedViewButton.js.map +1 -1
  24. package/dist/components/DataTable/SavedViewActions.d.ts +19 -0
  25. package/dist/components/DataTable/SavedViewActions.d.ts.map +1 -0
  26. package/dist/components/DataTable/SavedViewActions.js +34 -0
  27. package/dist/components/DataTable/SavedViewActions.js.map +1 -0
  28. package/dist/components/DataTable/SavedViewMenu.d.ts +18 -0
  29. package/dist/components/DataTable/SavedViewMenu.d.ts.map +1 -0
  30. package/dist/components/DataTable/SavedViewMenu.js +25 -0
  31. package/dist/components/DataTable/SavedViewMenu.js.map +1 -0
  32. package/dist/components/DataTable/SavedViewsBar.d.ts +25 -13
  33. package/dist/components/DataTable/SavedViewsBar.d.ts.map +1 -1
  34. package/dist/components/DataTable/SavedViewsBar.js +43 -83
  35. package/dist/components/DataTable/SavedViewsBar.js.map +1 -1
  36. package/dist/components/DataTable/TableActions.d.ts +4 -2
  37. package/dist/components/DataTable/TableActions.d.ts.map +1 -1
  38. package/dist/components/DataTable/TableActions.js +11 -14
  39. package/dist/components/DataTable/TableActions.js.map +1 -1
  40. package/dist/components/DataTable/index.d.ts +2 -0
  41. package/dist/components/DataTable/index.d.ts.map +1 -1
  42. package/dist/components/DataTable/saveErrorMessage.d.ts +3 -0
  43. package/dist/components/DataTable/saveErrorMessage.d.ts.map +1 -0
  44. package/dist/components/DataTable/saveErrorMessage.js +9 -0
  45. package/dist/components/DataTable/saveErrorMessage.js.map +1 -0
  46. package/dist/components/DataTable/savedViews.d.ts +15 -30
  47. package/dist/components/DataTable/savedViews.d.ts.map +1 -1
  48. package/dist/components/DataTable/savedViews.js +51 -98
  49. package/dist/components/DataTable/savedViews.js.map +1 -1
  50. package/dist/components/DataTable/useActiveSavedView.d.ts +1 -1
  51. package/dist/components/DataTable/useActiveSavedView.d.ts.map +1 -1
  52. package/dist/components/DataTable/useActiveSavedView.js +5 -4
  53. package/dist/components/DataTable/useActiveSavedView.js.map +1 -1
  54. package/dist/components/DataTable/useColumnVisibility.d.ts +7 -7
  55. package/dist/components/DataTable/useColumnVisibility.d.ts.map +1 -1
  56. package/dist/components/DataTable/useColumnVisibility.js +14 -14
  57. package/dist/components/DataTable/useColumnVisibility.js.map +1 -1
  58. package/dist/components/DataTable/useResultsAnnouncement.d.ts +26 -0
  59. package/dist/components/DataTable/useResultsAnnouncement.d.ts.map +1 -0
  60. package/dist/components/DataTable/{useFilterAnnouncement.js → useResultsAnnouncement.js} +16 -12
  61. package/dist/components/DataTable/useResultsAnnouncement.js.map +1 -0
  62. package/dist/components/DataTable/useSaveStatus.d.ts +32 -0
  63. package/dist/components/DataTable/useSaveStatus.d.ts.map +1 -0
  64. package/dist/components/DataTable/useSaveStatus.js +80 -0
  65. package/dist/components/DataTable/useSaveStatus.js.map +1 -0
  66. package/dist/components/DataTable/useSavedViewNameModal.d.ts +14 -0
  67. package/dist/components/DataTable/useSavedViewNameModal.d.ts.map +1 -0
  68. package/dist/components/DataTable/useSavedViewNameModal.js +43 -0
  69. package/dist/components/DataTable/useSavedViewNameModal.js.map +1 -0
  70. package/dist/components/DataTable/useSavedViewsClient.d.ts +58 -0
  71. package/dist/components/DataTable/useSavedViewsClient.d.ts.map +1 -0
  72. package/dist/components/DataTable/useSavedViewsClient.js +87 -0
  73. package/dist/components/DataTable/useSavedViewsClient.js.map +1 -0
  74. package/dist/components/DataTable/useSavedViewsState.d.ts +35 -0
  75. package/dist/components/DataTable/useSavedViewsState.d.ts.map +1 -0
  76. package/dist/components/DataTable/useSavedViewsState.js +192 -0
  77. package/dist/components/DataTable/useSavedViewsState.js.map +1 -0
  78. package/dist/components/DataTable/useUpdateSavedViewName.d.ts +2 -3
  79. package/dist/components/DataTable/useUpdateSavedViewName.d.ts.map +1 -1
  80. package/dist/components/DataTable/useUpdateSavedViewName.js +1 -2
  81. package/dist/components/DataTable/useUpdateSavedViewName.js.map +1 -1
  82. package/dist/components/button/BaseButton.d.ts +2 -0
  83. package/dist/components/button/BaseButton.d.ts.map +1 -1
  84. package/dist/components/button/BaseButton.js +8 -17
  85. package/dist/components/button/BaseButton.js.map +1 -1
  86. package/dist/components/internal/LoadingAnnouncement.d.ts +6 -0
  87. package/dist/components/internal/LoadingAnnouncement.d.ts.map +1 -1
  88. package/dist/components/internal/LoadingAnnouncement.js +12 -3
  89. package/dist/components/internal/LoadingAnnouncement.js.map +1 -1
  90. package/dist/components/internal/index.d.ts +1 -1
  91. package/dist/components/internal/index.d.ts.map +1 -1
  92. package/dist/components/internal/tooltip/useTooltipTriggerProps.d.ts +13 -0
  93. package/dist/components/internal/tooltip/useTooltipTriggerProps.d.ts.map +1 -0
  94. package/dist/components/internal/tooltip/useTooltipTriggerProps.js +24 -0
  95. package/dist/components/internal/tooltip/useTooltipTriggerProps.js.map +1 -0
  96. package/dist/components/link/BaseLink.d.ts.map +1 -1
  97. package/dist/components/link/BaseLink.js +3 -13
  98. package/dist/components/link/BaseLink.js.map +1 -1
  99. package/dist/components/segmented-control/IconSegment.d.ts +30 -8
  100. package/dist/components/segmented-control/IconSegment.d.ts.map +1 -1
  101. package/dist/components/segmented-control/IconSegment.js +10 -3
  102. package/dist/components/segmented-control/IconSegment.js.map +1 -1
  103. package/dist/components/segmented-control/SegmentBase.d.ts.map +1 -1
  104. package/dist/components/segmented-control/SegmentBase.js +4 -2
  105. package/dist/components/segmented-control/SegmentBase.js.map +1 -1
  106. package/dist/components/segmented-control/SegmentedControl.d.ts.map +1 -1
  107. package/dist/components/segmented-control/SegmentedControl.js +10 -3
  108. package/dist/components/segmented-control/SegmentedControl.js.map +1 -1
  109. package/dist/index.css +20 -5
  110. package/dist/index.css.map +1 -1
  111. package/dist/reactRender.css +1409 -1319
  112. package/dist/reactRender.css.map +1 -1
  113. package/dist/reactRender.js +1 -1
  114. package/dist/reactRenderLegacy.css +1409 -1319
  115. package/dist/reactRenderLegacy.css.map +1 -1
  116. package/dist/reactRenderLegacy.js +1 -1
  117. package/dist/tapestryMount.css +1409 -1319
  118. package/dist/tapestryMount.css.map +1 -1
  119. package/dist/tapestryMount.js +1 -1
  120. package/dist/tapestryMountLegacy.css +1409 -1319
  121. package/dist/tapestryMountLegacy.css.map +1 -1
  122. package/dist/tapestryMountLegacy.js +1 -1
  123. package/dist/tokens-dark.css +2 -2
  124. package/dist/tokens-dark.css.map +1 -1
  125. package/dist/tokens.css +3 -3
  126. package/dist/tokens.css.map +1 -1
  127. package/dist/unstable.css +385 -295
  128. package/dist/unstable.css.map +1 -1
  129. package/dist/unstable.js +1 -0
  130. package/dist/unstable.js.map +1 -1
  131. package/package.json +6 -7
  132. package/dist/components/DataTable/useFilterAnnouncement.d.ts +0 -24
  133. package/dist/components/DataTable/useFilterAnnouncement.d.ts.map +0 -1
  134. package/dist/components/DataTable/useFilterAnnouncement.js.map +0 -1
@@ -1,7 +1,6 @@
1
1
  import type { SavedView } from "./savedViews";
2
2
  /**
3
- * Saves a view's new name, trimmed. Callers skip blank and unchanged names,
4
- * since `view.name` is out of date while a save is pending.
3
+ * Saves a view's new name, trimmed. Callers skip blank and unchanged names.
5
4
  */
6
- export declare function useUpdateSavedViewName(onUpdate?: (savedView: SavedView) => Promise<SavedView> | SavedView): (view: SavedView, name: string) => Promise<void>;
5
+ export declare function useUpdateSavedViewName(onUpdate?: (savedView: SavedView) => Promise<SavedView | undefined> | SavedView): (view: SavedView, name: string) => Promise<void>;
7
6
  //# sourceMappingURL=useUpdateSavedViewName.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUpdateSavedViewName.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,IAErD,MAAM,SAAS,EAAE,MAAM,MAAM,mBAG5C"}
1
+ {"version":3,"file":"useUpdateSavedViewName.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,CAAC,EAAE,CACT,SAAS,EAAE,SAAS,KACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,SAAS,IAEjC,MAAM,SAAS,EAAE,MAAM,MAAM,mBAG5C"}
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Saves a view's new name, trimmed. Callers skip blank and unchanged names,
3
- * since `view.name` is out of date while a save is pending.
2
+ * Saves a view's new name, trimmed. Callers skip blank and unchanged names.
4
3
  */
5
4
  function useUpdateSavedViewName(onUpdate) {
6
5
  return async (view, name) => {
@@ -1 +1 @@
1
- {"version":3,"file":"useUpdateSavedViewName.js","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"sourcesContent":["import type { SavedView } from \"./savedViews\"\n\n/**\n * Saves a view's new name, trimmed. Callers skip blank and unchanged names,\n * since `view.name` is out of date while a save is pending.\n */\nexport function useUpdateSavedViewName(\n onUpdate?: (savedView: SavedView) => Promise<SavedView> | SavedView\n) {\n return async (view: SavedView, name: string) => {\n await onUpdate?.({ ...view, name: name.trim() })\n }\n}\n"],"names":[],"mappings":"AAEA;;;AAGG;AACG,SAAU,sBAAsB,CACpC,QAAmE,EAAA;AAEnE,IAAA,OAAO,OAAO,IAAe,EAAE,IAAY,KAAI;AAC7C,QAAA,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AAClD,IAAA,CAAC;AACH;;;;"}
1
+ {"version":3,"file":"useUpdateSavedViewName.js","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"sourcesContent":["import type { SavedView } from \"./savedViews\"\n\n/**\n * Saves a view's new name, trimmed. Callers skip blank and unchanged names.\n */\nexport function useUpdateSavedViewName(\n onUpdate?: (\n savedView: SavedView\n ) => Promise<SavedView | undefined> | SavedView\n) {\n return async (view: SavedView, name: string) => {\n await onUpdate?.({ ...view, name: name.trim() })\n }\n}\n"],"names":[],"mappings":"AAEA;;AAEG;AACG,SAAU,sBAAsB,CACpC,QAE+C,EAAA;AAE/C,IAAA,OAAO,OAAO,IAAe,EAAE,IAAY,KAAI;AAC7C,QAAA,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AAClD,IAAA,CAAC;AACH;;;;"}
@@ -2,6 +2,8 @@ import "./btn.css";
2
2
  import { BaseComponentProps, ComponentKind } from "../../utilities/buttonLinkShared";
3
3
  import React, { ButtonHTMLAttributes } from "react";
4
4
  export interface BaseButtonProps extends Omit<BaseComponentProps, "kind"> {
5
+ /** Keeps a disabled button focusable, using `aria-disabled` without the native `disabled` attribute. Use when focus would otherwise be lost, such as a button that disables itself while focused. */
6
+ focusableWhenDisabled?: boolean;
5
7
  /** The visual style of the button. */
6
8
  kind?: ComponentKind;
7
9
  /** The text content of the button. */
@@ -1 +1 @@
1
- {"version":3,"file":"BaseButton.d.ts","sourceRoot":"","sources":["../../../src/components/button/BaseButton.tsx"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAIlB,OAAO,EACL,kBAAkB,EAGlB,aAAa,EAEd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,KAAK,EAAE,EACZ,oBAAoB,EAIrB,MAAM,OAAO,CAAA;AAId,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACvE,sCAAsC;IACtC,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,sCAAsC;IACtC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,+GAA+G;IAC/G,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,eAAe,GAAG,UAAU,CACnC,GACC,eAAe,CAAA;AAEjB,eAAO,MAAM,UAAU,qLAkEtB,CAAA"}
1
+ {"version":3,"file":"BaseButton.d.ts","sourceRoot":"","sources":["../../../src/components/button/BaseButton.tsx"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAIlB,OAAO,EACL,kBAAkB,EAGlB,aAAa,EAEd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,KAAK,EAAE,EAAE,oBAAoB,EAAc,MAAM,OAAO,CAAA;AAE/D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACvE,qMAAqM;IACrM,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,sCAAsC;IACtC,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,sCAAsC;IACtC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,+GAA+G;IAC/G,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,eAAe,GAAG,UAAU,CACnC,GACC,eAAe,CAAA;AAEjB,eAAO,MAAM,UAAU,qLA4DtB,CAAA"}
@@ -1,26 +1,17 @@
1
1
  import LoadingSpinner from '../internal/LoadingSpinner.js';
2
- import React, { forwardRef, useContext } from 'react';
3
- import 'react-aria/private/live-announcer/LiveAnnouncer';
2
+ import '../internal/LoadingAnnouncement.js';
4
3
  import '../internal/tooltip/Tooltip.js';
5
- import { TooltipTriggerContext } from '../internal/tooltip/TooltipTriggerContext.js';
4
+ import { useTooltipTriggerProps } from '../internal/tooltip/useTooltipTriggerProps.js';
6
5
  import { COMPONENT_SIZE_CLASS_MAP, COMPONENT_KIND_CLASS_MAP, wrapStringWithSpan } from '../../utilities/buttonLinkShared.js';
7
6
  import classNames from 'classnames';
8
- import { mergeProps } from 'react-aria/mergeProps';
9
- import { mergeRefs } from 'react-aria/mergeRefs';
7
+ import React, { forwardRef } from 'react';
10
8
 
11
- const BaseButton = forwardRef(({ "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, className, disabled = false, fullWidth, kind = "secondary", label, loading = false, loadingAriaLabel = "Loading...", prefix, size, suffix, ...restProps }, ref) => {
12
- const tooltip = useContext(TooltipTriggerContext);
13
- const { "aria-describedby": tooltipId, ...tooltipTriggerProps } = tooltip?.triggerProps ?? {};
14
- const mergedProps = tooltip
15
- ? mergeProps(restProps, tooltipTriggerProps)
16
- : restProps;
17
- const mergedRef = tooltip
18
- ? mergeRefs(ref, tooltip.ref)
19
- : ref;
20
- const describedBy = [ariaDescribedBy, tooltipId].filter(Boolean).join(" ") || undefined;
21
- const combinedClassName = classNames(kind && "tds-btn", size && size !== "md" && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], fullWidth && "tds-btn--full-width", loading && "tds-btn--loading", className, { "tds-btn--prefix": prefix, "tds-btn--suffix": suffix });
9
+ const BaseButton = forwardRef(({ "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, className, disabled = false, focusableWhenDisabled = false, fullWidth, kind = "secondary", label, loading = false, loadingAriaLabel = "Loading...", prefix, size, suffix, ...restProps }, ref) => {
10
+ const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps(restProps, ref, ariaDescribedBy);
22
11
  const isDisabled = disabled || loading;
23
- return (React.createElement("button", { type: "button", className: combinedClassName, ref: mergedRef, ...mergedProps, "aria-busy": loading || undefined, "aria-describedby": describedBy, "aria-disabled": isDisabled || undefined, disabled: isDisabled, "aria-label": loading ? loadingAriaLabel : ariaLabel },
12
+ const isFocusableDisabled = isDisabled && focusableWhenDisabled;
13
+ const combinedClassName = classNames(kind && "tds-btn", size && size !== "md" && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], fullWidth && "tds-btn--full-width", loading && "tds-btn--loading", isFocusableDisabled && "disabled", className, { "tds-btn--prefix": prefix, "tds-btn--suffix": suffix });
14
+ return (React.createElement("button", { type: "button", className: combinedClassName, ref: mergedRef, ...mergedProps, "aria-busy": loading || undefined, "aria-describedby": describedBy, "aria-disabled": isDisabled || undefined, disabled: isDisabled && !focusableWhenDisabled, onClick: isFocusableDisabled ? undefined : mergedProps.onClick, "aria-label": loading ? loadingAriaLabel : ariaLabel },
24
15
  loading && React.createElement(LoadingSpinner, null),
25
16
  wrapStringWithSpan(prefix),
26
17
  loading ? React.createElement("span", null, label) : label,
@@ -1 +1 @@
1
- {"version":3,"file":"BaseButton.js","sources":["../../../src/components/button/BaseButton.tsx"],"sourcesContent":["import \"./btn.css\"\n\nimport { LoadingSpinner } from \"@components/internal\"\nimport { TooltipTriggerContext } from \"@components/internal/tooltip/TooltipTriggerContext\"\nimport {\n BaseComponentProps,\n COMPONENT_KIND_CLASS_MAP,\n COMPONENT_SIZE_CLASS_MAP,\n ComponentKind,\n wrapStringWithSpan,\n} from \"@utilities/buttonLinkShared\"\nimport classNames from \"classnames\"\nimport React, {\n ButtonHTMLAttributes,\n forwardRef,\n RefObject,\n useContext,\n} from \"react\"\nimport { mergeProps } from \"react-aria/mergeProps\"\nimport { mergeRefs } from \"react-aria/mergeRefs\"\n\nexport interface BaseButtonProps extends Omit<BaseComponentProps, \"kind\"> {\n /** The visual style of the button. */\n kind?: ComponentKind\n /** The text content of the button. */\n label: React.ReactNode\n /** Whether the button is in a loading state. When true, the button is disabled and shows a loading spinner. */\n loading?: boolean\n /** Accessible label to use when the button is in loading state. */\n loadingAriaLabel?: string\n}\n\nexport type BaseButtonElementProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof BaseButtonProps | \"children\"\n> &\n BaseButtonProps\n\nexport const BaseButton = forwardRef<HTMLButtonElement, BaseButtonElementProps>(\n (\n {\n \"aria-describedby\": ariaDescribedBy,\n \"aria-label\": ariaLabel,\n className,\n disabled = false,\n fullWidth,\n kind = \"secondary\",\n label,\n loading = false,\n loadingAriaLabel = \"Loading...\",\n prefix,\n size,\n suffix,\n ...restProps\n }: BaseButtonElementProps,\n ref\n ) => {\n const tooltip = useContext(TooltipTriggerContext)\n const { \"aria-describedby\": tooltipId, ...tooltipTriggerProps } =\n tooltip?.triggerProps ?? {}\n\n const mergedProps = tooltip\n ? mergeProps(restProps, tooltipTriggerProps)\n : restProps\n const mergedRef = tooltip\n ? mergeRefs<HTMLButtonElement>(\n ref,\n tooltip.ref as RefObject<HTMLButtonElement>\n )\n : ref\n const describedBy =\n [ariaDescribedBy, tooltipId].filter(Boolean).join(\" \") || undefined\n\n const combinedClassName = classNames(\n kind && \"tds-btn\",\n size && size !== \"md\" && COMPONENT_SIZE_CLASS_MAP[size],\n kind && COMPONENT_KIND_CLASS_MAP[kind],\n fullWidth && \"tds-btn--full-width\",\n loading && \"tds-btn--loading\",\n className,\n { \"tds-btn--prefix\": prefix, \"tds-btn--suffix\": suffix }\n )\n\n const isDisabled = disabled || loading\n\n return (\n <button\n type=\"button\"\n className={combinedClassName}\n ref={mergedRef}\n {...mergedProps}\n aria-busy={loading || undefined}\n aria-describedby={describedBy}\n aria-disabled={isDisabled || undefined}\n disabled={isDisabled}\n aria-label={loading ? loadingAriaLabel : ariaLabel}\n >\n {loading && <LoadingSpinner />}\n {wrapStringWithSpan(prefix)}\n {loading ? <span>{label}</span> : label}\n {wrapStringWithSpan(suffix)}\n </button>\n )\n }\n)\n\nBaseButton.displayName = \"BaseButton\"\n"],"names":[],"mappings":";;;;;;;;;;MAsCa,UAAU,GAAG,UAAU,CAClC,CACE,EACE,kBAAkB,EAAE,eAAe,EACnC,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,SAAS,EACT,IAAI,GAAG,WAAW,EAClB,KAAK,EACL,OAAO,GAAG,KAAK,EACf,gBAAgB,GAAG,YAAY,EAC/B,MAAM,EACN,IAAI,EACJ,MAAM,EACN,GAAG,SAAS,EACW,EACzB,GAAG,KACD;AACF,IAAA,MAAM,OAAO,GAAG,UAAU,CAAC,qBAAqB,CAAC;AACjD,IAAA,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,GAAG,mBAAmB,EAAE,GAC7D,OAAO,EAAE,YAAY,IAAI,EAAE;IAE7B,MAAM,WAAW,GAAG;AAClB,UAAE,UAAU,CAAC,SAAS,EAAE,mBAAmB;UACzC,SAAS;IACb,MAAM,SAAS,GAAG;UACd,SAAS,CACP,GAAG,EACH,OAAO,CAAC,GAAmC;UAE7C,GAAG;AACP,IAAA,MAAM,WAAW,GACf,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS;IAErE,MAAM,iBAAiB,GAAG,UAAU,CAClC,IAAI,IAAI,SAAS,EACjB,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACvD,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACtC,SAAS,IAAI,qBAAqB,EAClC,OAAO,IAAI,kBAAkB,EAC7B,SAAS,EACT,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,CACzD;AAED,IAAA,MAAM,UAAU,GAAG,QAAQ,IAAI,OAAO;AAEtC,IAAA,QACE,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,SAAS,KACV,WAAW,EAAA,WAAA,EACJ,OAAO,IAAI,SAAS,EAAA,kBAAA,EACb,WAAW,mBACd,UAAU,IAAI,SAAS,EACtC,QAAQ,EAAE,UAAU,EAAA,YAAA,EACR,OAAO,GAAG,gBAAgB,GAAG,SAAS,EAAA;QAEjD,OAAO,IAAI,KAAA,CAAA,aAAA,CAAC,cAAc,EAAA,IAAA,CAAG;QAC7B,kBAAkB,CAAC,MAAM,CAAC;QAC1B,OAAO,GAAG,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,EAAO,KAAK,CAAQ,GAAG,KAAK;AACtC,QAAA,kBAAkB,CAAC,MAAM,CAAC,CACpB;AAEb,CAAC;AAGH,UAAU,CAAC,WAAW,GAAG,YAAY;;;;"}
1
+ {"version":3,"file":"BaseButton.js","sources":["../../../src/components/button/BaseButton.tsx"],"sourcesContent":["import \"./btn.css\"\n\nimport { LoadingSpinner } from \"@components/internal\"\nimport { useTooltipTriggerProps } from \"@components/internal/tooltip/useTooltipTriggerProps\"\nimport {\n BaseComponentProps,\n COMPONENT_KIND_CLASS_MAP,\n COMPONENT_SIZE_CLASS_MAP,\n ComponentKind,\n wrapStringWithSpan,\n} from \"@utilities/buttonLinkShared\"\nimport classNames from \"classnames\"\nimport React, { ButtonHTMLAttributes, forwardRef } from \"react\"\n\nexport interface BaseButtonProps extends Omit<BaseComponentProps, \"kind\"> {\n /** Keeps a disabled button focusable, using `aria-disabled` without the native `disabled` attribute. Use when focus would otherwise be lost, such as a button that disables itself while focused. */\n focusableWhenDisabled?: boolean\n /** The visual style of the button. */\n kind?: ComponentKind\n /** The text content of the button. */\n label: React.ReactNode\n /** Whether the button is in a loading state. When true, the button is disabled and shows a loading spinner. */\n loading?: boolean\n /** Accessible label to use when the button is in loading state. */\n loadingAriaLabel?: string\n}\n\nexport type BaseButtonElementProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof BaseButtonProps | \"children\"\n> &\n BaseButtonProps\n\nexport const BaseButton = forwardRef<HTMLButtonElement, BaseButtonElementProps>(\n (\n {\n \"aria-describedby\": ariaDescribedBy,\n \"aria-label\": ariaLabel,\n className,\n disabled = false,\n focusableWhenDisabled = false,\n fullWidth,\n kind = \"secondary\",\n label,\n loading = false,\n loadingAriaLabel = \"Loading...\",\n prefix,\n size,\n suffix,\n ...restProps\n }: BaseButtonElementProps,\n ref\n ) => {\n const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps(\n restProps,\n ref,\n ariaDescribedBy\n )\n\n const isDisabled = disabled || loading\n const isFocusableDisabled = isDisabled && focusableWhenDisabled\n\n const combinedClassName = classNames(\n kind && \"tds-btn\",\n size && size !== \"md\" && COMPONENT_SIZE_CLASS_MAP[size],\n kind && COMPONENT_KIND_CLASS_MAP[kind],\n fullWidth && \"tds-btn--full-width\",\n loading && \"tds-btn--loading\",\n isFocusableDisabled && \"disabled\",\n className,\n { \"tds-btn--prefix\": prefix, \"tds-btn--suffix\": suffix }\n )\n\n return (\n <button\n type=\"button\"\n className={combinedClassName}\n ref={mergedRef}\n {...mergedProps}\n aria-busy={loading || undefined}\n aria-describedby={describedBy}\n aria-disabled={isDisabled || undefined}\n disabled={isDisabled && !focusableWhenDisabled}\n onClick={isFocusableDisabled ? undefined : mergedProps.onClick}\n aria-label={loading ? loadingAriaLabel : ariaLabel}\n >\n {loading && <LoadingSpinner />}\n {wrapStringWithSpan(prefix)}\n {loading ? <span>{label}</span> : label}\n {wrapStringWithSpan(suffix)}\n </button>\n )\n }\n)\n\nBaseButton.displayName = \"BaseButton\"\n"],"names":[],"mappings":";;;;;;;;AAiCO,MAAM,UAAU,GAAG,UAAU,CAClC,CACE,EACE,kBAAkB,EAAE,eAAe,EACnC,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,qBAAqB,GAAG,KAAK,EAC7B,SAAS,EACT,IAAI,GAAG,WAAW,EAClB,KAAK,EACL,OAAO,GAAG,KAAK,EACf,gBAAgB,GAAG,YAAY,EAC/B,MAAM,EACN,IAAI,EACJ,MAAM,EACN,GAAG,SAAS,EACW,EACzB,GAAG,KACD;AACF,IAAA,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,sBAAsB,CACpE,SAAS,EACT,GAAG,EACH,eAAe,CAChB;AAED,IAAA,MAAM,UAAU,GAAG,QAAQ,IAAI,OAAO;AACtC,IAAA,MAAM,mBAAmB,GAAG,UAAU,IAAI,qBAAqB;IAE/D,MAAM,iBAAiB,GAAG,UAAU,CAClC,IAAI,IAAI,SAAS,EACjB,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACvD,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACtC,SAAS,IAAI,qBAAqB,EAClC,OAAO,IAAI,kBAAkB,EAC7B,mBAAmB,IAAI,UAAU,EACjC,SAAS,EACT,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,CACzD;IAED,QACE,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,SAAS,EAAA,GACV,WAAW,EAAA,WAAA,EACJ,OAAO,IAAI,SAAS,EAAA,kBAAA,EACb,WAAW,EAAA,eAAA,EACd,UAAU,IAAI,SAAS,EACtC,QAAQ,EAAE,UAAU,IAAI,CAAC,qBAAqB,EAC9C,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,WAAW,CAAC,OAAO,EAAA,YAAA,EAClD,OAAO,GAAG,gBAAgB,GAAG,SAAS,EAAA;QAEjD,OAAO,IAAI,KAAA,CAAA,aAAA,CAAC,cAAc,EAAA,IAAA,CAAG;QAC7B,kBAAkB,CAAC,MAAM,CAAC;QAC1B,OAAO,GAAG,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,EAAO,KAAK,CAAQ,GAAG,KAAK;AACtC,QAAA,kBAAkB,CAAC,MAAM,CAAC,CACpB;AAEb,CAAC;AAGH,UAAU,CAAC,WAAW,GAAG,YAAY;;;;"}
@@ -1,4 +1,10 @@
1
1
  export declare const LOADING_ANNOUNCEMENT_DELAY_MS = 500;
2
+ /**
3
+ * Turns off every {@link LoadingAnnouncement} inside it while `true`. Use it
4
+ * when focus moves into a field during the load, because the screen reader
5
+ * then speaks the message only after the results appear.
6
+ */
7
+ export declare const SuppressLoadingAnnouncementContext: import("react").Context<boolean>;
2
8
  /**
3
9
  * Announces `message` to screen reader users while a loading indicator is
4
10
  * shown. Render it next to the spinner, only while loading. It waits
@@ -1 +1 @@
1
- {"version":3,"file":"LoadingAnnouncement.d.ts","sourceRoot":"","sources":["../../../src/components/internal/LoadingAnnouncement.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,6BAA6B,MAAM,CAAA;AAEhD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,QAYnE"}
1
+ {"version":3,"file":"LoadingAnnouncement.d.ts","sourceRoot":"","sources":["../../../src/components/internal/LoadingAnnouncement.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,6BAA6B,MAAM,CAAA;AAEhD;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,kCAAuB,CAAA;AAEtE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,QAcnE"}
@@ -1,7 +1,13 @@
1
1
  import { useAnnounce } from '../../utilities/useAnnounce.js';
2
- import { useEffect } from 'react';
2
+ import { createContext, useContext, useEffect } from 'react';
3
3
 
4
4
  const LOADING_ANNOUNCEMENT_DELAY_MS = 500;
5
+ /**
6
+ * Turns off every {@link LoadingAnnouncement} inside it while `true`. Use it
7
+ * when focus moves into a field during the load, because the screen reader
8
+ * then speaks the message only after the results appear.
9
+ */
10
+ const SuppressLoadingAnnouncementContext = createContext(false);
5
11
  /**
6
12
  * Announces `message` to screen reader users while a loading indicator is
7
13
  * shown. Render it next to the spinner, only while loading. It waits
@@ -10,12 +16,15 @@ const LOADING_ANNOUNCEMENT_DELAY_MS = 500;
10
16
  */
11
17
  function LoadingAnnouncement({ message }) {
12
18
  const announce = useAnnounce();
19
+ const isSuppressed = useContext(SuppressLoadingAnnouncementContext);
13
20
  useEffect(() => {
21
+ if (isSuppressed)
22
+ return;
14
23
  const timeout = setTimeout(() => announce(message), LOADING_ANNOUNCEMENT_DELAY_MS);
15
24
  return () => clearTimeout(timeout);
16
- }, [announce, message]);
25
+ }, [announce, isSuppressed, message]);
17
26
  return null;
18
27
  }
19
28
 
20
- export { LOADING_ANNOUNCEMENT_DELAY_MS, LoadingAnnouncement };
29
+ export { LOADING_ANNOUNCEMENT_DELAY_MS, LoadingAnnouncement, SuppressLoadingAnnouncementContext };
21
30
  //# sourceMappingURL=LoadingAnnouncement.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LoadingAnnouncement.js","sources":["../../../src/components/internal/LoadingAnnouncement.tsx"],"sourcesContent":["import { useAnnounce } from \"@utilities/useAnnounce\"\nimport { useEffect } from \"react\"\n\nexport const LOADING_ANNOUNCEMENT_DELAY_MS = 500\n\n/**\n * Announces `message` to screen reader users while a loading indicator is\n * shown. Render it next to the spinner, only while loading. It waits\n * {@link LOADING_ANNOUNCEMENT_DELAY_MS} after mount, so a fast load says\n * nothing, and it cancels if it unmounts first.\n */\nexport function LoadingAnnouncement({ message }: { message: string }) {\n const announce = useAnnounce()\n\n useEffect(() => {\n const timeout = setTimeout(\n () => announce(message),\n LOADING_ANNOUNCEMENT_DELAY_MS\n )\n return () => clearTimeout(timeout)\n }, [announce, message])\n\n return null\n}\n"],"names":[],"mappings":";;;AAGO,MAAM,6BAA6B,GAAG;AAE7C;;;;;AAKG;AACG,SAAU,mBAAmB,CAAC,EAAE,OAAO,EAAuB,EAAA;AAClE,IAAA,MAAM,QAAQ,GAAG,WAAW,EAAE;IAE9B,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,OAAO,GAAG,UAAU,CACxB,MAAM,QAAQ,CAAC,OAAO,CAAC,EACvB,6BAA6B,CAC9B;AACD,QAAA,OAAO,MAAM,YAAY,CAAC,OAAO,CAAC;AACpC,IAAA,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAEvB,IAAA,OAAO,IAAI;AACb;;;;"}
1
+ {"version":3,"file":"LoadingAnnouncement.js","sources":["../../../src/components/internal/LoadingAnnouncement.tsx"],"sourcesContent":["import { useAnnounce } from \"@utilities/useAnnounce\"\nimport { createContext, useContext, useEffect } from \"react\"\n\nexport const LOADING_ANNOUNCEMENT_DELAY_MS = 500\n\n/**\n * Turns off every {@link LoadingAnnouncement} inside it while `true`. Use it\n * when focus moves into a field during the load, because the screen reader\n * then speaks the message only after the results appear.\n */\nexport const SuppressLoadingAnnouncementContext = createContext(false)\n\n/**\n * Announces `message` to screen reader users while a loading indicator is\n * shown. Render it next to the spinner, only while loading. It waits\n * {@link LOADING_ANNOUNCEMENT_DELAY_MS} after mount, so a fast load says\n * nothing, and it cancels if it unmounts first.\n */\nexport function LoadingAnnouncement({ message }: { message: string }) {\n const announce = useAnnounce()\n const isSuppressed = useContext(SuppressLoadingAnnouncementContext)\n\n useEffect(() => {\n if (isSuppressed) return\n const timeout = setTimeout(\n () => announce(message),\n LOADING_ANNOUNCEMENT_DELAY_MS\n )\n return () => clearTimeout(timeout)\n }, [announce, isSuppressed, message])\n\n return null\n}\n"],"names":[],"mappings":";;;AAGO,MAAM,6BAA6B,GAAG;AAE7C;;;;AAIG;MACU,kCAAkC,GAAG,aAAa,CAAC,KAAK;AAErE;;;;;AAKG;AACG,SAAU,mBAAmB,CAAC,EAAE,OAAO,EAAuB,EAAA;AAClE,IAAA,MAAM,QAAQ,GAAG,WAAW,EAAE;AAC9B,IAAA,MAAM,YAAY,GAAG,UAAU,CAAC,kCAAkC,CAAC;IAEnE,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,YAAY;YAAE;AAClB,QAAA,MAAM,OAAO,GAAG,UAAU,CACxB,MAAM,QAAQ,CAAC,OAAO,CAAC,EACvB,6BAA6B,CAC9B;AACD,QAAA,OAAO,MAAM,YAAY,CAAC,OAAO,CAAC;IACpC,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;AAErC,IAAA,OAAO,IAAI;AACb;;;;"}
@@ -1,5 +1,5 @@
1
1
  import LoadingSpinner from "./LoadingSpinner";
2
- export { LoadingAnnouncement } from "./LoadingAnnouncement";
2
+ export { LoadingAnnouncement, SuppressLoadingAnnouncementContext, } from "./LoadingAnnouncement";
3
3
  export type { TooltipProps } from "./tooltip";
4
4
  export { Tooltip } from "./tooltip";
5
5
  export { LoadingSpinner };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/internal/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/internal/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EACL,mBAAmB,EACnB,kCAAkC,GACnC,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,cAAc,EAAE,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { type ForwardedRef, type HTMLAttributes, type Ref } from "react";
2
+ /**
3
+ * Wires an element to a surrounding `<Tooltip>`. Merges the tooltip's
4
+ * trigger props and ref into the element's own, and joins the tooltip's
5
+ * `aria-describedby` with the caller's. Outside a `<Tooltip>`, returns the
6
+ * element's own props and ref unchanged.
7
+ */
8
+ export declare function useTooltipTriggerProps<E extends HTMLElement>(props: HTMLAttributes<E>, ref: ForwardedRef<E>, ariaDescribedBy?: string): {
9
+ describedBy: string | undefined;
10
+ mergedProps: HTMLAttributes<E>;
11
+ mergedRef: Ref<E>;
12
+ };
13
+ //# sourceMappingURL=useTooltipTriggerProps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTooltipTriggerProps.d.ts","sourceRoot":"","sources":["../../../../src/components/internal/tooltip/useTooltipTriggerProps.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,GAAG,EAGT,MAAM,OAAO,CAAA;AAMd;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,WAAW,EAC1D,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EACxB,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB;IACD,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC,CAAA;IAC9B,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;CAClB,CAaA"}
@@ -0,0 +1,24 @@
1
+ import { useContext } from 'react';
2
+ import { mergeProps } from 'react-aria/mergeProps';
3
+ import { mergeRefs } from 'react-aria/mergeRefs';
4
+ import { TooltipTriggerContext } from './TooltipTriggerContext.js';
5
+
6
+ /**
7
+ * Wires an element to a surrounding `<Tooltip>`. Merges the tooltip's
8
+ * trigger props and ref into the element's own, and joins the tooltip's
9
+ * `aria-describedby` with the caller's. Outside a `<Tooltip>`, returns the
10
+ * element's own props and ref unchanged.
11
+ */
12
+ function useTooltipTriggerProps(props, ref, ariaDescribedBy) {
13
+ const tooltip = useContext(TooltipTriggerContext);
14
+ const { "aria-describedby": tooltipId, ...tooltipTriggerProps } = tooltip?.triggerProps ?? {};
15
+ const mergedProps = tooltip ? mergeProps(props, tooltipTriggerProps) : props;
16
+ const mergedRef = tooltip
17
+ ? mergeRefs(ref, tooltip.ref)
18
+ : ref;
19
+ const describedBy = [ariaDescribedBy, tooltipId].filter(Boolean).join(" ") || undefined;
20
+ return { describedBy, mergedProps, mergedRef };
21
+ }
22
+
23
+ export { useTooltipTriggerProps };
24
+ //# sourceMappingURL=useTooltipTriggerProps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTooltipTriggerProps.js","sources":["../../../../src/components/internal/tooltip/useTooltipTriggerProps.ts"],"sourcesContent":["import {\n type ForwardedRef,\n type HTMLAttributes,\n type Ref,\n type RefObject,\n useContext,\n} from \"react\"\nimport { mergeProps } from \"react-aria/mergeProps\"\nimport { mergeRefs } from \"react-aria/mergeRefs\"\n\nimport { TooltipTriggerContext } from \"./TooltipTriggerContext\"\n\n/**\n * Wires an element to a surrounding `<Tooltip>`. Merges the tooltip's\n * trigger props and ref into the element's own, and joins the tooltip's\n * `aria-describedby` with the caller's. Outside a `<Tooltip>`, returns the\n * element's own props and ref unchanged.\n */\nexport function useTooltipTriggerProps<E extends HTMLElement>(\n props: HTMLAttributes<E>,\n ref: ForwardedRef<E>,\n ariaDescribedBy?: string\n): {\n describedBy: string | undefined\n mergedProps: HTMLAttributes<E>\n mergedRef: Ref<E>\n} {\n const tooltip = useContext(TooltipTriggerContext)\n const { \"aria-describedby\": tooltipId, ...tooltipTriggerProps } =\n tooltip?.triggerProps ?? {}\n\n const mergedProps = tooltip ? mergeProps(props, tooltipTriggerProps) : props\n const mergedRef = tooltip\n ? mergeRefs<E>(ref, tooltip.ref as RefObject<E>)\n : ref\n const describedBy =\n [ariaDescribedBy, tooltipId].filter(Boolean).join(\" \") || undefined\n\n return { describedBy, mergedProps, mergedRef }\n}\n"],"names":[],"mappings":";;;;;AAYA;;;;;AAKG;SACa,sBAAsB,CACpC,KAAwB,EACxB,GAAoB,EACpB,eAAwB,EAAA;AAMxB,IAAA,MAAM,OAAO,GAAG,UAAU,CAAC,qBAAqB,CAAC;AACjD,IAAA,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,GAAG,mBAAmB,EAAE,GAC7D,OAAO,EAAE,YAAY,IAAI,EAAE;AAE7B,IAAA,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,mBAAmB,CAAC,GAAG,KAAK;IAC5E,MAAM,SAAS,GAAG;UACd,SAAS,CAAI,GAAG,EAAE,OAAO,CAAC,GAAmB;UAC7C,GAAG;AACP,IAAA,MAAM,WAAW,GACf,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS;AAErE,IAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE;AAChD;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"BaseLink.d.ts","sourceRoot":"","sources":["../../../src/components/link/BaseLink.tsx"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAA;AAI1B,OAAO,EACL,kBAAkB,EAGlB,aAAa,EAEd,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,KAAK,EAAE,EACZ,oBAAoB,EAIrB,MAAM,OAAO,CAAA;AAId,KAAK,iBAAiB,GAAG,kBAAkB,GACzC,IAAI,CACF,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,kBAAkB,GAAG,UAAU,GAAG,MAAM,CAC/C,GAAG;IACF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB,CAAA;AAEH,MAAM,MAAM,oBAAoB,GAC5B,QAAQ,CACN,iBAAiB,GAAG;IAClB;;;;;OAKG;IACH,OAAO,EAAE,IAAI,CAAA;IACb,IAAI,CAAC,EAAE,KAAK,CAAA;CACb,CACF,GACD,QAAQ,CAAC,iBAAiB,GAAG;IAAE,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEnE,eAAO,MAAM,QAAQ,gGAiEpB,CAAA"}
1
+ {"version":3,"file":"BaseLink.d.ts","sourceRoot":"","sources":["../../../src/components/link/BaseLink.tsx"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAA;AAI1B,OAAO,EACL,kBAAkB,EAGlB,aAAa,EAEd,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,KAAK,EAAE,EAAE,oBAAoB,EAAc,MAAM,OAAO,CAAA;AAE/D,KAAK,iBAAiB,GAAG,kBAAkB,GACzC,IAAI,CACF,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,kBAAkB,GAAG,UAAU,GAAG,MAAM,CAC/C,GAAG;IACF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB,CAAA;AAEH,MAAM,MAAM,oBAAoB,GAC5B,QAAQ,CACN,iBAAiB,GAAG;IAClB;;;;;OAKG;IACH,OAAO,EAAE,IAAI,CAAA;IACb,IAAI,CAAC,EAAE,KAAK,CAAA;CACb,CACF,GACD,QAAQ,CAAC,iBAAiB,GAAG;IAAE,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEnE,eAAO,MAAM,QAAQ,gGAuDpB,CAAA"}
@@ -1,21 +1,11 @@
1
- import { TooltipTriggerContext } from '../internal/tooltip/TooltipTriggerContext.js';
1
+ import { useTooltipTriggerProps } from '../internal/tooltip/useTooltipTriggerProps.js';
2
2
  import { Slot, Slottable } from '@radix-ui/react-slot';
3
3
  import { COMPONENT_SIZE_CLASS_MAP, COMPONENT_KIND_CLASS_MAP, wrapStringWithSpan } from '../../utilities/buttonLinkShared.js';
4
4
  import classNames from 'classnames';
5
- import React__default, { forwardRef, useContext } from 'react';
6
- import { mergeProps } from 'react-aria/mergeProps';
7
- import { mergeRefs } from 'react-aria/mergeRefs';
5
+ import React__default, { forwardRef } from 'react';
8
6
 
9
7
  const BaseLink = forwardRef(({ asChild = false, "aria-describedby": ariaDescribedBy, children, className, external = false, fullWidth, href, kind, label, prefix, size, suffix, ...restProps }, ref) => {
10
- const tooltip = useContext(TooltipTriggerContext);
11
- const { "aria-describedby": tooltipId, ...tooltipTriggerProps } = tooltip?.triggerProps ?? {};
12
- const mergedProps = tooltip
13
- ? mergeProps(restProps, tooltipTriggerProps)
14
- : restProps;
15
- const mergedRef = tooltip
16
- ? mergeRefs(ref, tooltip.ref)
17
- : ref;
18
- const describedBy = [ariaDescribedBy, tooltipId].filter(Boolean).join(" ") || undefined;
8
+ const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps(restProps, ref, ariaDescribedBy);
19
9
  const combinedClassName = classNames(kind && "tds-btn", size && size !== "md" && kind && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], fullWidth && "tds-btn--full-width", className, { "tds-btn--prefix": prefix, "tds-btn--suffix": suffix });
20
10
  const externalProps = external
21
11
  ? { rel: "noopener noreferrer", target: "_blank" }
@@ -1 +1 @@
1
- {"version":3,"file":"BaseLink.js","sources":["../../../src/components/link/BaseLink.tsx"],"sourcesContent":["import \"../button/btn.css\"\n\nimport { TooltipTriggerContext } from \"@components/internal/tooltip/TooltipTriggerContext\"\nimport { Slot, Slottable } from \"@radix-ui/react-slot\"\nimport {\n BaseComponentProps,\n COMPONENT_KIND_CLASS_MAP,\n COMPONENT_SIZE_CLASS_MAP,\n ComponentKind,\n wrapStringWithSpan,\n} from \"@utilities/buttonLinkShared\"\nimport { Simplify } from \"@utilities/typeUtils\"\nimport classNames from \"classnames\"\nimport React, {\n AnchorHTMLAttributes,\n forwardRef,\n RefObject,\n useContext,\n} from \"react\"\nimport { mergeProps } from \"react-aria/mergeProps\"\nimport { mergeRefs } from \"react-aria/mergeRefs\"\n\ntype BaseLinkBaseProps = BaseComponentProps &\n Omit<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n keyof BaseComponentProps | \"children\" | \"href\"\n > & {\n children?: React.ReactNode\n external?: boolean\n kind?: ComponentKind\n label?: React.ReactNode\n }\n\nexport type BaseLinkElementProps =\n | Simplify<\n BaseLinkBaseProps & {\n /**\n * When true, merges the link's styling onto the child element instead\n * of rendering an `<a>`. Use to wrap a router link (e.g. react-router,\n * Next.js) or any link-like component. The `external` prop still\n * applies — it spreads `rel`/`target` onto the child element.\n */\n asChild: true\n href?: never\n }\n >\n | Simplify<BaseLinkBaseProps & { asChild?: false; href: string }>\n\nexport const BaseLink = forwardRef<HTMLAnchorElement, BaseLinkElementProps>(\n (\n {\n asChild = false,\n \"aria-describedby\": ariaDescribedBy,\n children,\n className,\n external = false,\n fullWidth,\n href,\n kind,\n label,\n prefix,\n size,\n suffix,\n ...restProps\n }: BaseLinkElementProps,\n ref\n ) => {\n const tooltip = useContext(TooltipTriggerContext)\n const { \"aria-describedby\": tooltipId, ...tooltipTriggerProps } =\n tooltip?.triggerProps ?? {}\n\n const mergedProps = tooltip\n ? mergeProps(restProps, tooltipTriggerProps)\n : restProps\n const mergedRef = tooltip\n ? mergeRefs<HTMLAnchorElement>(\n ref,\n tooltip.ref as RefObject<HTMLAnchorElement>\n )\n : ref\n const describedBy =\n [ariaDescribedBy, tooltipId].filter(Boolean).join(\" \") || undefined\n\n const combinedClassName = classNames(\n kind && \"tds-btn\",\n size && size !== \"md\" && kind && COMPONENT_SIZE_CLASS_MAP[size],\n kind && COMPONENT_KIND_CLASS_MAP[kind],\n fullWidth && \"tds-btn--full-width\",\n className,\n { \"tds-btn--prefix\": prefix, \"tds-btn--suffix\": suffix }\n )\n\n const externalProps = external\n ? { rel: \"noopener noreferrer\", target: \"_blank\" }\n : {}\n\n const Comp = asChild ? Slot : \"a\"\n\n return (\n <Comp\n {...(!asChild && { href })}\n className={combinedClassName}\n ref={mergedRef}\n {...mergedProps}\n aria-describedby={describedBy}\n {...externalProps}\n >\n {wrapStringWithSpan(prefix)}\n <Slottable>{label || children}</Slottable>\n {wrapStringWithSpan(suffix)}\n </Comp>\n )\n }\n)\n\nBaseLink.displayName = \"BaseLink\"\n"],"names":["React"],"mappings":";;;;;;;;MAgDa,QAAQ,GAAG,UAAU,CAChC,CACE,EACE,OAAO,GAAG,KAAK,EACf,kBAAkB,EAAE,eAAe,EACnC,QAAQ,EACR,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,SAAS,EACT,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,MAAM,EACN,IAAI,EACJ,MAAM,EACN,GAAG,SAAS,EACS,EACvB,GAAG,KACD;AACF,IAAA,MAAM,OAAO,GAAG,UAAU,CAAC,qBAAqB,CAAC;AACjD,IAAA,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,GAAG,mBAAmB,EAAE,GAC7D,OAAO,EAAE,YAAY,IAAI,EAAE;IAE7B,MAAM,WAAW,GAAG;AAClB,UAAE,UAAU,CAAC,SAAS,EAAE,mBAAmB;UACzC,SAAS;IACb,MAAM,SAAS,GAAG;UACd,SAAS,CACP,GAAG,EACH,OAAO,CAAC,GAAmC;UAE7C,GAAG;AACP,IAAA,MAAM,WAAW,GACf,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS;IAErE,MAAM,iBAAiB,GAAG,UAAU,CAClC,IAAI,IAAI,SAAS,EACjB,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EAC/D,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACtC,SAAS,IAAI,qBAAqB,EAClC,SAAS,EACT,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,CACzD;IAED,MAAM,aAAa,GAAG;UAClB,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ;UAC9C,EAAE;IAEN,MAAM,IAAI,GAAG,OAAO,GAAG,IAAI,GAAG,GAAG;IAEjC,QACEA,cAAA,CAAA,aAAA,CAAC,IAAI,EAAA,EAAA,IACE,CAAC,OAAO,IAAI,EAAE,IAAI,EAAE,CAAC,EAC1B,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,SAAS,EAAA,GACV,WAAW,EAAA,kBAAA,EACG,WAAW,EAAA,GACzB,aAAa,EAAA;QAEhB,kBAAkB,CAAC,MAAM,CAAC;AAC3B,QAAAA,cAAA,CAAA,aAAA,CAAC,SAAS,EAAA,IAAA,EAAE,KAAK,IAAI,QAAQ,CAAa;AACzC,QAAA,kBAAkB,CAAC,MAAM,CAAC,CACtB;AAEX,CAAC;AAGH,QAAQ,CAAC,WAAW,GAAG,UAAU;;;;"}
1
+ {"version":3,"file":"BaseLink.js","sources":["../../../src/components/link/BaseLink.tsx"],"sourcesContent":["import \"../button/btn.css\"\n\nimport { useTooltipTriggerProps } from \"@components/internal/tooltip/useTooltipTriggerProps\"\nimport { Slot, Slottable } from \"@radix-ui/react-slot\"\nimport {\n BaseComponentProps,\n COMPONENT_KIND_CLASS_MAP,\n COMPONENT_SIZE_CLASS_MAP,\n ComponentKind,\n wrapStringWithSpan,\n} from \"@utilities/buttonLinkShared\"\nimport { Simplify } from \"@utilities/typeUtils\"\nimport classNames from \"classnames\"\nimport React, { AnchorHTMLAttributes, forwardRef } from \"react\"\n\ntype BaseLinkBaseProps = BaseComponentProps &\n Omit<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n keyof BaseComponentProps | \"children\" | \"href\"\n > & {\n children?: React.ReactNode\n external?: boolean\n kind?: ComponentKind\n label?: React.ReactNode\n }\n\nexport type BaseLinkElementProps =\n | Simplify<\n BaseLinkBaseProps & {\n /**\n * When true, merges the link's styling onto the child element instead\n * of rendering an `<a>`. Use to wrap a router link (e.g. react-router,\n * Next.js) or any link-like component. The `external` prop still\n * applies — it spreads `rel`/`target` onto the child element.\n */\n asChild: true\n href?: never\n }\n >\n | Simplify<BaseLinkBaseProps & { asChild?: false; href: string }>\n\nexport const BaseLink = forwardRef<HTMLAnchorElement, BaseLinkElementProps>(\n (\n {\n asChild = false,\n \"aria-describedby\": ariaDescribedBy,\n children,\n className,\n external = false,\n fullWidth,\n href,\n kind,\n label,\n prefix,\n size,\n suffix,\n ...restProps\n }: BaseLinkElementProps,\n ref\n ) => {\n const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps(\n restProps,\n ref,\n ariaDescribedBy\n )\n\n const combinedClassName = classNames(\n kind && \"tds-btn\",\n size && size !== \"md\" && kind && COMPONENT_SIZE_CLASS_MAP[size],\n kind && COMPONENT_KIND_CLASS_MAP[kind],\n fullWidth && \"tds-btn--full-width\",\n className,\n { \"tds-btn--prefix\": prefix, \"tds-btn--suffix\": suffix }\n )\n\n const externalProps = external\n ? { rel: \"noopener noreferrer\", target: \"_blank\" }\n : {}\n\n const Comp = asChild ? Slot : \"a\"\n\n return (\n <Comp\n {...(!asChild && { href })}\n className={combinedClassName}\n ref={mergedRef}\n {...mergedProps}\n aria-describedby={describedBy}\n {...externalProps}\n >\n {wrapStringWithSpan(prefix)}\n <Slottable>{label || children}</Slottable>\n {wrapStringWithSpan(suffix)}\n </Comp>\n )\n }\n)\n\nBaseLink.displayName = \"BaseLink\"\n"],"names":["React"],"mappings":";;;;;;MAyCa,QAAQ,GAAG,UAAU,CAChC,CACE,EACE,OAAO,GAAG,KAAK,EACf,kBAAkB,EAAE,eAAe,EACnC,QAAQ,EACR,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,SAAS,EACT,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,MAAM,EACN,IAAI,EACJ,MAAM,EACN,GAAG,SAAS,EACS,EACvB,GAAG,KACD;AACF,IAAA,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,sBAAsB,CACpE,SAAS,EACT,GAAG,EACH,eAAe,CAChB;IAED,MAAM,iBAAiB,GAAG,UAAU,CAClC,IAAI,IAAI,SAAS,EACjB,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EAC/D,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACtC,SAAS,IAAI,qBAAqB,EAClC,SAAS,EACT,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,CACzD;IAED,MAAM,aAAa,GAAG;UAClB,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ;UAC9C,EAAE;IAEN,MAAM,IAAI,GAAG,OAAO,GAAG,IAAI,GAAG,GAAG;IAEjC,QACEA,cAAA,CAAA,aAAA,CAAC,IAAI,EAAA,EAAA,IACE,CAAC,OAAO,IAAI,EAAE,IAAI,EAAE,CAAC,EAC1B,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,SAAS,EAAA,GACV,WAAW,EAAA,kBAAA,EACG,WAAW,EAAA,GACzB,aAAa,EAAA;QAEhB,kBAAkB,CAAC,MAAM,CAAC;AAC3B,QAAAA,cAAA,CAAA,aAAA,CAAC,SAAS,EAAA,IAAA,EAAE,KAAK,IAAI,QAAQ,CAAa;AACzC,QAAA,kBAAkB,CAAC,MAAM,CAAC,CACtB;AAEX,CAAC;AAGH,QAAQ,CAAC,WAAW,GAAG,UAAU;;;;"}
@@ -1,9 +1,8 @@
1
1
  import "./index.css";
2
+ import { type TooltipPlacement } from "../internal/tooltip";
2
3
  import React, { type ButtonHTMLAttributes } from "react";
3
4
  import { type SegmentKind } from "./SegmentBase";
4
- export interface IconSegmentProps {
5
- /** Accessible label — required since there's no visible text. */
6
- "aria-label": string;
5
+ type IconSegmentSharedProps = {
7
6
  /** Disables this item only. */
8
7
  disabled?: boolean;
9
8
  /** The icon to display. */
@@ -12,17 +11,40 @@ export interface IconSegmentProps {
12
11
  * This segment's color when selected. Default `'interaction-subtle'`.
13
12
  */
14
13
  kind?: SegmentKind;
14
+ /** Where the tooltip renders relative to the segment. Default `"bottom"`. Has no effect without `tooltip`. */
15
+ tooltipPlacement?: TooltipPlacement;
15
16
  /** Unique identifier for this item. */
16
17
  value: string;
17
- }
18
- export type IconSegmentElementProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof IconSegmentProps | "aria-checked" | "children" | "onClick" | "onKeyDown" | "role" | "tabIndex" | "type"> & IconSegmentProps;
18
+ };
19
+ type IconSegmentTooltipProps = {
20
+ /** Accessible label. Required for icon-only segments to provide context about the segment's purpose. Not required when `tooltip` is set. When both are set, this is the name and the tooltip is visual only. */
21
+ "aria-label"?: string;
22
+ /** Text shown on hover and focus. Without `aria-label`, it is also used as the `aria-label`. With `aria-label`, start the `aria-label` with the same text (e.g. tooltip `"Grid"`, aria-label `"Grid view"`). The tooltip itself is hidden from screen readers. Ignores `aria-labelledby`. */
23
+ tooltip: string;
24
+ };
25
+ type IconSegmentAriaLabelProps = {
26
+ /** Accessible label. Required for icon-only segments to provide context about the segment's purpose. Not required when `tooltip` is set. When both are set, this is the name and the tooltip is visual only. */
27
+ "aria-label": string;
28
+ /** Text shown on hover and focus. Without `aria-label`, it is also used as the `aria-label`. With `aria-label`, start the `aria-label` with the same text (e.g. tooltip `"Grid"`, aria-label `"Grid view"`). The tooltip itself is hidden from screen readers. Ignores `aria-labelledby`. */
29
+ tooltip?: string;
30
+ };
31
+ /**
32
+ * An icon-only segment needs a name: `aria-label`, `tooltip`, or both.
33
+ * `IconSegmentAriaLabelProps` is declared last so that a missing name
34
+ * reports "aria-label is missing", not "tooltip is missing".
35
+ */
36
+ type IconSegmentNameProps = IconSegmentAriaLabelProps | IconSegmentTooltipProps;
37
+ export type IconSegmentProps = IconSegmentSharedProps & IconSegmentNameProps;
38
+ export type IconSegmentElementProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof IconSegmentSharedProps | "aria-checked" | "aria-label" | "children" | "onClick" | "onKeyDown" | "role" | "tabIndex" | "tooltip" | "type"> & IconSegmentProps;
19
39
  /**
20
40
  * An icon-only item within a `SegmentedControl`, mirroring `IconButton`'s
21
- * shape. Must be rendered as a direct child of `SegmentedControl` — it reads
22
- * selection state, group `disabled`, and roving tabindex from that context.
41
+ * shape. Requires an `aria-label` or `tooltip` for accessibility. Must be
42
+ * rendered as a direct child of `SegmentedControl` — it reads selection
43
+ * state, group `disabled`, and roving tabindex from that context.
23
44
  *
24
45
  * @component
25
46
  * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/forms-segmentedcontrol--docs | Storybook Documentation}
26
47
  */
27
- export declare const IconSegment: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children" | "type" | "tabIndex" | "role" | "aria-checked" | "onKeyDown" | "onClick" | keyof IconSegmentProps> & IconSegmentProps & React.RefAttributes<HTMLButtonElement>>;
48
+ export declare const IconSegment: React.ForwardRefExoticComponent<IconSegmentElementProps & React.RefAttributes<HTMLButtonElement>>;
49
+ export {};
28
50
  //# sourceMappingURL=IconSegment.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IconSegment.d.ts","sourceRoot":"","sources":["../../../src/components/segmented-control/IconSegment.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,KAAK,EAAE,EAAE,KAAK,oBAAoB,EAAc,MAAM,OAAO,CAAA;AAEpE,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAA;IACpB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,2BAA2B;IAC3B,IAAI,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB;;OAEG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,oBAAoB,CAAC,iBAAiB,CAAC,EACrC,MAAM,gBAAgB,GACtB,cAAc,GACd,UAAU,GACV,SAAS,GACT,WAAW,GACX,MAAM,GACN,UAAU,GACV,MAAM,CACT,GACC,gBAAgB,CAAA;AAElB;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,iQAKtB,CAAA"}
1
+ {"version":3,"file":"IconSegment.d.ts","sourceRoot":"","sources":["../../../src/components/segmented-control/IconSegment.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,EAAW,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC7E,OAAO,KAAK,EAAE,EAAE,KAAK,oBAAoB,EAAc,MAAM,OAAO,CAAA;AAEpE,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAE7D,KAAK,sBAAsB,GAAG;IAC5B,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,2BAA2B;IAC3B,IAAI,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB;;OAEG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,8GAA8G;IAC9G,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,KAAK,uBAAuB,GAAG;IAC7B,gNAAgN;IAChN,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6RAA6R;IAC7R,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,yBAAyB,GAAG;IAC/B,gNAAgN;IAChN,YAAY,EAAE,MAAM,CAAA;IACpB,6RAA6R;IAC7R,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;GAIG;AACH,KAAK,oBAAoB,GAAG,yBAAyB,GAAG,uBAAuB,CAAA;AAE/E,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG,oBAAoB,CAAA;AAE5E,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,oBAAoB,CAAC,iBAAiB,CAAC,EACrC,MAAM,sBAAsB,GAC5B,cAAc,GACd,YAAY,GACZ,UAAU,GACV,SAAS,GACT,WAAW,GACX,MAAM,GACN,UAAU,GACV,SAAS,GACT,MAAM,CACT,GACC,gBAAgB,CAAA;AAElB;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,mGAiCtB,CAAA"}
@@ -1,15 +1,22 @@
1
+ import { Tooltip } from '../internal/tooltip/Tooltip.js';
1
2
  import React__default, { forwardRef } from 'react';
2
3
  import { SegmentBase } from './SegmentBase.js';
3
4
 
4
5
  /**
5
6
  * An icon-only item within a `SegmentedControl`, mirroring `IconButton`'s
6
- * shape. Must be rendered as a direct child of `SegmentedControl` — it reads
7
- * selection state, group `disabled`, and roving tabindex from that context.
7
+ * shape. Requires an `aria-label` or `tooltip` for accessibility. Must be
8
+ * rendered as a direct child of `SegmentedControl` — it reads selection
9
+ * state, group `disabled`, and roving tabindex from that context.
8
10
  *
9
11
  * @component
10
12
  * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/forms-segmentedcontrol--docs | Storybook Documentation}
11
13
  */
12
- const IconSegment = forwardRef(function IconSegment({ icon, ...restProps }, ref) {
14
+ const IconSegment = forwardRef(function IconSegment({ icon, tooltip, tooltipPlacement = "bottom", ...restProps }, ref) {
15
+ if (tooltip) {
16
+ const { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...segmentProps } = restProps;
17
+ return (React__default.createElement(Tooltip, { content: tooltip, placement: tooltipPlacement, visualOnly: true },
18
+ React__default.createElement(SegmentBase, { ...segmentProps, "aria-label": ariaLabel || tooltip, content: icon, iconOnly: true, ref: ref })));
19
+ }
13
20
  return React__default.createElement(SegmentBase, { ...restProps, content: icon, iconOnly: true, ref: ref });
14
21
  });
15
22
  IconSegment.displayName = "IconSegment";
@@ -1 +1 @@
1
- {"version":3,"file":"IconSegment.js","sources":["../../../src/components/segmented-control/IconSegment.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport React, { type ButtonHTMLAttributes, forwardRef } from \"react\"\n\nimport { SegmentBase, type SegmentKind } from \"./SegmentBase\"\n\nexport interface IconSegmentProps {\n /** Accessible label — required since there's no visible text. */\n \"aria-label\": string\n /** Disables this item only. */\n disabled?: boolean\n /** The icon to display. */\n icon: React.ReactNode\n /**\n * This segment's color when selected. Default `'interaction-subtle'`.\n */\n kind?: SegmentKind\n /** Unique identifier for this item. */\n value: string\n}\n\nexport type IconSegmentElementProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n | keyof IconSegmentProps\n | \"aria-checked\"\n | \"children\"\n | \"onClick\"\n | \"onKeyDown\"\n | \"role\"\n | \"tabIndex\"\n | \"type\"\n> &\n IconSegmentProps\n\n/**\n * An icon-only item within a `SegmentedControl`, mirroring `IconButton`'s\n * shape. Must be rendered as a direct child of `SegmentedControl` — it reads\n * selection state, group `disabled`, and roving tabindex from that context.\n *\n * @component\n * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/forms-segmentedcontrol--docs | Storybook Documentation}\n */\nexport const IconSegment = forwardRef<\n HTMLButtonElement,\n IconSegmentElementProps\n>(function IconSegment({ icon, ...restProps }: IconSegmentElementProps, ref) {\n return <SegmentBase {...restProps} content={icon} iconOnly ref={ref} />\n})\n\nIconSegment.displayName = \"IconSegment\"\n"],"names":["React"],"mappings":";;;AAkCA;;;;;;;AAOG;AACI,MAAM,WAAW,GAAG,UAAU,CAGnC,SAAS,WAAW,CAAC,EAAE,IAAI,EAAE,GAAG,SAAS,EAA2B,EAAE,GAAG,EAAA;AACzE,IAAA,OAAOA,cAAA,CAAA,aAAA,CAAC,WAAW,EAAA,EAAA,GAAK,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAA,IAAA,EAAC,GAAG,EAAE,GAAG,GAAI;AACzE,CAAC;AAED,WAAW,CAAC,WAAW,GAAG,aAAa;;;;"}
1
+ {"version":3,"file":"IconSegment.js","sources":["../../../src/components/segmented-control/IconSegment.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport { Tooltip, type TooltipPlacement } from \"@components/internal/tooltip\"\nimport React, { type ButtonHTMLAttributes, forwardRef } from \"react\"\n\nimport { SegmentBase, type SegmentKind } from \"./SegmentBase\"\n\ntype IconSegmentSharedProps = {\n /** Disables this item only. */\n disabled?: boolean\n /** The icon to display. */\n icon: React.ReactNode\n /**\n * This segment's color when selected. Default `'interaction-subtle'`.\n */\n kind?: SegmentKind\n /** Where the tooltip renders relative to the segment. Default `\"bottom\"`. Has no effect without `tooltip`. */\n tooltipPlacement?: TooltipPlacement\n /** Unique identifier for this item. */\n value: string\n}\n\ntype IconSegmentTooltipProps = {\n /** Accessible label. Required for icon-only segments to provide context about the segment's purpose. Not required when `tooltip` is set. When both are set, this is the name and the tooltip is visual only. */\n \"aria-label\"?: string\n /** Text shown on hover and focus. Without `aria-label`, it is also used as the `aria-label`. With `aria-label`, start the `aria-label` with the same text (e.g. tooltip `\"Grid\"`, aria-label `\"Grid view\"`). The tooltip itself is hidden from screen readers. Ignores `aria-labelledby`. */\n tooltip: string\n}\n\ntype IconSegmentAriaLabelProps = {\n /** Accessible label. Required for icon-only segments to provide context about the segment's purpose. Not required when `tooltip` is set. When both are set, this is the name and the tooltip is visual only. */\n \"aria-label\": string\n /** Text shown on hover and focus. Without `aria-label`, it is also used as the `aria-label`. With `aria-label`, start the `aria-label` with the same text (e.g. tooltip `\"Grid\"`, aria-label `\"Grid view\"`). The tooltip itself is hidden from screen readers. Ignores `aria-labelledby`. */\n tooltip?: string\n}\n\n/**\n * An icon-only segment needs a name: `aria-label`, `tooltip`, or both.\n * `IconSegmentAriaLabelProps` is declared last so that a missing name\n * reports \"aria-label is missing\", not \"tooltip is missing\".\n */\ntype IconSegmentNameProps = IconSegmentAriaLabelProps | IconSegmentTooltipProps\n\nexport type IconSegmentProps = IconSegmentSharedProps & IconSegmentNameProps\n\nexport type IconSegmentElementProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n | keyof IconSegmentSharedProps\n | \"aria-checked\"\n | \"aria-label\"\n | \"children\"\n | \"onClick\"\n | \"onKeyDown\"\n | \"role\"\n | \"tabIndex\"\n | \"tooltip\"\n | \"type\"\n> &\n IconSegmentProps\n\n/**\n * An icon-only item within a `SegmentedControl`, mirroring `IconButton`'s\n * shape. Requires an `aria-label` or `tooltip` for accessibility. Must be\n * rendered as a direct child of `SegmentedControl` — it reads selection\n * state, group `disabled`, and roving tabindex from that context.\n *\n * @component\n * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/forms-segmentedcontrol--docs | Storybook Documentation}\n */\nexport const IconSegment = forwardRef<\n HTMLButtonElement,\n IconSegmentElementProps\n>(function IconSegment(\n {\n icon,\n tooltip,\n tooltipPlacement = \"bottom\",\n ...restProps\n }: IconSegmentElementProps,\n ref\n) {\n if (tooltip) {\n const {\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...segmentProps\n } = restProps\n\n return (\n <Tooltip content={tooltip} placement={tooltipPlacement} visualOnly>\n <SegmentBase\n {...segmentProps}\n aria-label={ariaLabel || tooltip}\n content={icon}\n iconOnly\n ref={ref}\n />\n </Tooltip>\n )\n }\n\n return <SegmentBase {...restProps} content={icon} iconOnly ref={ref} />\n})\n\nIconSegment.displayName = \"IconSegment\"\n"],"names":["React"],"mappings":";;;;AA4DA;;;;;;;;AAQG;MACU,WAAW,GAAG,UAAU,CAGnC,SAAS,WAAW,CACpB,EACE,IAAI,EACJ,OAAO,EACP,gBAAgB,GAAG,QAAQ,EAC3B,GAAG,SAAS,EACY,EAC1B,GAAG,EAAA;IAEH,IAAI,OAAO,EAAE;AACX,QAAA,MAAM,EACJ,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,GAAG,YAAY,EAChB,GAAG,SAAS;AAEb,QAAA,QACEA,cAAA,CAAA,aAAA,CAAC,OAAO,EAAA,EAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,UAAU,EAAA,IAAA,EAAA;YAChEA,cAAA,CAAA,aAAA,CAAC,WAAW,OACN,YAAY,EAAA,YAAA,EACJ,SAAS,IAAI,OAAO,EAChC,OAAO,EAAE,IAAI,EACb,QAAQ,QACR,GAAG,EAAE,GAAG,EAAA,CACR,CACM;IAEd;AAEA,IAAA,OAAOA,cAAA,CAAA,aAAA,CAAC,WAAW,EAAA,EAAA,GAAK,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAA,IAAA,EAAC,GAAG,EAAE,GAAG,GAAI;AACzE,CAAC;AAED,WAAW,CAAC,WAAW,GAAG,aAAa;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentBase.d.ts","sourceRoot":"","sources":["../../../src/components/segmented-control/SegmentBase.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,KAAK,oBAAoB,EAA0B,MAAM,OAAO,CAAA;AAIhF,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,kBAAkB,GAClB,oBAAoB,GACpB,gBAAgB,GAChB,SAAS,GACT,SAAS,CAAA;AAWb,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,oBAAoB,CAAC,iBAAiB,CAAC,EACrC,MAAM,gBAAgB,GACtB,cAAc,GACd,UAAU,GACV,SAAS,GACT,WAAW,GACX,MAAM,GACN,UAAU,GACV,MAAM,CACT,GACC,gBAAgB,CAAA;AAElB;;;;;GAKG;AACH,eAAO,MAAM,WAAW,iQAuFtB,CAAA"}
1
+ {"version":3,"file":"SegmentBase.d.ts","sourceRoot":"","sources":["../../../src/components/segmented-control/SegmentBase.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,KAAK,oBAAoB,EAA0B,MAAM,OAAO,CAAA;AAIhF,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,kBAAkB,GAClB,oBAAoB,GACpB,gBAAgB,GAChB,SAAS,GACT,SAAS,CAAA;AAWb,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,oBAAoB,CAAC,iBAAiB,CAAC,EACrC,MAAM,gBAAgB,GACtB,cAAc,GACd,UAAU,GACV,SAAS,GACT,WAAW,GACX,MAAM,GACN,UAAU,GACV,MAAM,CACT,GACC,gBAAgB,CAAA;AAElB;;;;;GAKG;AACH,eAAO,MAAM,WAAW,iQA6FtB,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { useTooltipTriggerProps } from '../internal/tooltip/useTooltipTriggerProps.js';
1
2
  import classNames from 'classnames';
2
3
  import React__default, { forwardRef, useContext } from 'react';
3
4
  import { SegmentedControlContext } from './SegmentedControlContext.js';
@@ -16,7 +17,7 @@ const ARROW_KEYS = [
16
17
  * and disabled propagation all live here so the two public components only
17
18
  * differ in what they render inside the button.
18
19
  */
19
- const SegmentBase = forwardRef(function SegmentBase({ className, content, disabled = false, iconOnly = false, kind = "interaction-subtle", value, ...restProps }, ref) {
20
+ const SegmentBase = forwardRef(function SegmentBase({ "aria-describedby": ariaDescribedBy, className, content, disabled = false, iconOnly = false, kind = "interaction-subtle", value, ...restProps }, ref) {
20
21
  const context = useContext(SegmentedControlContext);
21
22
  const isDisabled = disabled || (context?.disabled ?? false);
22
23
  const isChecked = context?.value === value;
@@ -62,7 +63,8 @@ const SegmentBase = forwardRef(function SegmentBase({ className, content, disabl
62
63
  if (nextValue)
63
64
  context?.onSelect(nextValue);
64
65
  };
65
- return (React__default.createElement("button", { ...restProps, "aria-checked": isChecked ? "true" : "false", className: combinedClassName, "data-segment-value": value, disabled: isDisabled, onClick: handleClick, onKeyDown: handleKeyDown, ref: ref, role: "radio", tabIndex: value === context?.tabbableValue ? 0 : -1, type: "button" }, content));
66
+ const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps({ ...restProps, onClick: handleClick, onKeyDown: handleKeyDown }, ref, ariaDescribedBy);
67
+ return (React__default.createElement("button", { ...mergedProps, "aria-checked": isChecked ? "true" : "false", "aria-describedby": describedBy, className: combinedClassName, "data-segment-value": value, disabled: isDisabled, ref: mergedRef, role: "radio", tabIndex: value === context?.tabbableValue ? 0 : -1, type: "button" }, content));
66
68
  });
67
69
  SegmentBase.displayName = "SegmentBase";
68
70
 
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentBase.js","sources":["../../../src/components/segmented-control/SegmentBase.tsx"],"sourcesContent":["import classNames from \"classnames\"\nimport React, { type ButtonHTMLAttributes, forwardRef, useContext } from \"react\"\n\nimport { SegmentedControlContext } from \"./SegmentedControlContext\"\n\nexport type SegmentKind =\n | \"error\"\n | \"interaction-bold\"\n | \"interaction-subtle\"\n | \"neutral-subtle\"\n | \"success\"\n | \"warning\"\n\nconst ARROW_KEYS = [\n \"ArrowLeft\",\n \"ArrowRight\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"Home\",\n \"End\",\n]\n\nexport interface SegmentBaseProps {\n content: React.ReactNode\n disabled?: boolean\n iconOnly?: boolean\n kind?: SegmentKind\n value: string\n}\n\nexport type SegmentBaseElementProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n | keyof SegmentBaseProps\n | \"aria-checked\"\n | \"children\"\n | \"onClick\"\n | \"onKeyDown\"\n | \"role\"\n | \"tabIndex\"\n | \"type\"\n> &\n SegmentBaseProps\n\n/**\n * Shared `role=\"radio\"` button implementation for `Segment` and\n * `IconSegment` — selection state, roving tabindex, arrow-key navigation,\n * and disabled propagation all live here so the two public components only\n * differ in what they render inside the button.\n */\nexport const SegmentBase = forwardRef<\n HTMLButtonElement,\n SegmentBaseElementProps\n>(function SegmentBase(\n {\n className,\n content,\n disabled = false,\n iconOnly = false,\n kind = \"interaction-subtle\",\n value,\n ...restProps\n }: SegmentBaseElementProps,\n ref\n) {\n const context = useContext(SegmentedControlContext)\n const isDisabled = disabled || (context?.disabled ?? false)\n const isChecked = context?.value === value\n\n const combinedClassName = classNames(\n \"tds-segmented-control-segment\",\n iconOnly && \"tds-segmented-control-segment--icon-only\",\n `tds-segmented-control-segment--${kind}`,\n className\n )\n\n const handleClick = () => {\n if (isDisabled) return\n context?.onSelect(value)\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (!ARROW_KEYS.includes(event.key)) return\n\n const track = event.currentTarget.closest('[role=\"radiogroup\"]')\n if (!track) return\n\n const segments = Array.from(\n track.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]:not(:disabled)')\n )\n const currentIndex = segments.indexOf(event.currentTarget)\n if (currentIndex === -1) return\n\n let nextIndex = currentIndex\n switch (event.key) {\n case \"ArrowLeft\":\n case \"ArrowUp\":\n nextIndex = currentIndex === 0 ? segments.length - 1 : currentIndex - 1\n break\n case \"ArrowRight\":\n case \"ArrowDown\":\n nextIndex = currentIndex === segments.length - 1 ? 0 : currentIndex + 1\n break\n case \"Home\":\n nextIndex = 0\n break\n case \"End\":\n nextIndex = segments.length - 1\n break\n }\n\n const nextSegment = segments[nextIndex]\n if (!nextSegment || nextSegment === event.currentTarget) return\n\n event.preventDefault()\n nextSegment.focus()\n const nextValue = nextSegment.dataset.segmentValue\n if (nextValue) context?.onSelect(nextValue)\n }\n\n return (\n <button\n {...restProps}\n aria-checked={isChecked ? \"true\" : \"false\"}\n className={combinedClassName}\n data-segment-value={value}\n disabled={isDisabled}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n ref={ref}\n role=\"radio\"\n tabIndex={value === context?.tabbableValue ? 0 : -1}\n type=\"button\"\n >\n {content}\n </button>\n )\n})\n\nSegmentBase.displayName = \"SegmentBase\"\n"],"names":["React"],"mappings":"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"}
1
+ {"version":3,"file":"SegmentBase.js","sources":["../../../src/components/segmented-control/SegmentBase.tsx"],"sourcesContent":["import { useTooltipTriggerProps } from \"@components/internal/tooltip/useTooltipTriggerProps\"\nimport classNames from \"classnames\"\nimport React, { type ButtonHTMLAttributes, forwardRef, useContext } from \"react\"\n\nimport { SegmentedControlContext } from \"./SegmentedControlContext\"\n\nexport type SegmentKind =\n | \"error\"\n | \"interaction-bold\"\n | \"interaction-subtle\"\n | \"neutral-subtle\"\n | \"success\"\n | \"warning\"\n\nconst ARROW_KEYS = [\n \"ArrowLeft\",\n \"ArrowRight\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"Home\",\n \"End\",\n]\n\nexport interface SegmentBaseProps {\n content: React.ReactNode\n disabled?: boolean\n iconOnly?: boolean\n kind?: SegmentKind\n value: string\n}\n\nexport type SegmentBaseElementProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n | keyof SegmentBaseProps\n | \"aria-checked\"\n | \"children\"\n | \"onClick\"\n | \"onKeyDown\"\n | \"role\"\n | \"tabIndex\"\n | \"type\"\n> &\n SegmentBaseProps\n\n/**\n * Shared `role=\"radio\"` button implementation for `Segment` and\n * `IconSegment` — selection state, roving tabindex, arrow-key navigation,\n * and disabled propagation all live here so the two public components only\n * differ in what they render inside the button.\n */\nexport const SegmentBase = forwardRef<\n HTMLButtonElement,\n SegmentBaseElementProps\n>(function SegmentBase(\n {\n \"aria-describedby\": ariaDescribedBy,\n className,\n content,\n disabled = false,\n iconOnly = false,\n kind = \"interaction-subtle\",\n value,\n ...restProps\n }: SegmentBaseElementProps,\n ref\n) {\n const context = useContext(SegmentedControlContext)\n const isDisabled = disabled || (context?.disabled ?? false)\n const isChecked = context?.value === value\n\n const combinedClassName = classNames(\n \"tds-segmented-control-segment\",\n iconOnly && \"tds-segmented-control-segment--icon-only\",\n `tds-segmented-control-segment--${kind}`,\n className\n )\n\n const handleClick = () => {\n if (isDisabled) return\n context?.onSelect(value)\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (!ARROW_KEYS.includes(event.key)) return\n\n const track = event.currentTarget.closest('[role=\"radiogroup\"]')\n if (!track) return\n\n const segments = Array.from(\n track.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]:not(:disabled)')\n )\n const currentIndex = segments.indexOf(event.currentTarget)\n if (currentIndex === -1) return\n\n let nextIndex = currentIndex\n switch (event.key) {\n case \"ArrowLeft\":\n case \"ArrowUp\":\n nextIndex = currentIndex === 0 ? segments.length - 1 : currentIndex - 1\n break\n case \"ArrowRight\":\n case \"ArrowDown\":\n nextIndex = currentIndex === segments.length - 1 ? 0 : currentIndex + 1\n break\n case \"Home\":\n nextIndex = 0\n break\n case \"End\":\n nextIndex = segments.length - 1\n break\n }\n\n const nextSegment = segments[nextIndex]\n if (!nextSegment || nextSegment === event.currentTarget) return\n\n event.preventDefault()\n nextSegment.focus()\n const nextValue = nextSegment.dataset.segmentValue\n if (nextValue) context?.onSelect(nextValue)\n }\n\n const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps(\n { ...restProps, onClick: handleClick, onKeyDown: handleKeyDown },\n ref,\n ariaDescribedBy\n )\n\n return (\n <button\n {...mergedProps}\n aria-checked={isChecked ? \"true\" : \"false\"}\n aria-describedby={describedBy}\n className={combinedClassName}\n data-segment-value={value}\n disabled={isDisabled}\n ref={mergedRef}\n role=\"radio\"\n tabIndex={value === context?.tabbableValue ? 0 : -1}\n type=\"button\"\n >\n {content}\n </button>\n )\n})\n\nSegmentBase.displayName = \"SegmentBase\"\n"],"names":["React"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../../src/components/segmented-control/SegmentedControl.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,EAAE,KAAK,cAAc,EAAY,MAAM,OAAO,CAAA;AAI5D,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE9C,UAAU,2BAA2B;IACnC,wCAAwC;IACxC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,qDAAqD;IACrD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,sBAAsB;IACtB,IAAI,CAAC,EAAE,oBAAoB,CAAA;CAC5B;AAED,UAAU,+BAAgC,SAAQ,2BAA2B;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAA;CACd;AAED,UAAU,iCACR,SAAQ,2BAA2B;IACnC;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,MAAM,qBAAqB,GAC7B,+BAA+B,GAC/B,iCAAiC,CAAA;AAErC,MAAM,MAAM,4BAA4B,GAAG,IAAI,CAC7C,cAAc,CAAC,cAAc,CAAC,EAC9B,MAAM,qBAAqB,GAAG,UAAU,GAAG,MAAM,CAClD,GACC,qBAAqB,CAAA;AAEvB;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB;4HAa1B,4BAA4B;;CAqD9B,CAAA"}
1
+ {"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../../src/components/segmented-control/SegmentedControl.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,EACZ,KAAK,cAAc,EAIpB,MAAM,OAAO,CAAA;AAId,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE9C,UAAU,2BAA2B;IACnC,wCAAwC;IACxC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,qDAAqD;IACrD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,sBAAsB;IACtB,IAAI,CAAC,EAAE,oBAAoB,CAAA;CAC5B;AAED,UAAU,+BAAgC,SAAQ,2BAA2B;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAA;CACd;AAED,UAAU,iCACR,SAAQ,2BAA2B;IACnC;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,MAAM,qBAAqB,GAC7B,+BAA+B,GAC/B,iCAAiC,CAAA;AAErC,MAAM,MAAM,4BAA4B,GAAG,IAAI,CAC7C,cAAc,CAAC,cAAc,CAAC,EAC9B,MAAM,qBAAqB,GAAG,UAAU,GAAG,MAAM,CAClD,GACC,qBAAqB,CAAA;AAEvB;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB;4HAa1B,4BAA4B;;CAyE9B,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { useId } from '../../utilities/useId.js';
2
2
  import classNames from 'classnames';
3
- import React__default, { useState } from 'react';
3
+ import React__default, { useState, useRef, useLayoutEffect } from 'react';
4
4
  import { SegmentedControlContext } from './SegmentedControlContext.js';
5
5
 
6
6
  /**
@@ -22,10 +22,17 @@ const SegmentedControl = ({ children, className, defaultValue, disabled = false,
22
22
  setUncontrolledValue(nextValue);
23
23
  onChange?.(nextValue);
24
24
  };
25
+ const trackRef = useRef(null);
26
+ const [fallbackTabbableValue, setFallbackTabbableValue] = useState();
27
+ useLayoutEffect(() => {
28
+ const enabledSegments = Array.from(trackRef.current?.querySelectorAll('[role="radio"]:not(:disabled)') ?? []);
29
+ const selectedIsEnabled = enabledSegments.some((segment) => segment.dataset.segmentValue === selectedValue);
30
+ setFallbackTabbableValue(selectedIsEnabled ? undefined : enabledSegments[0]?.dataset.segmentValue);
31
+ }, [children, disabled, selectedValue]);
25
32
  const contextValue = {
26
33
  disabled,
27
34
  onSelect,
28
- tabbableValue: selectedValue,
35
+ tabbableValue: fallbackTabbableValue ?? selectedValue,
29
36
  value: selectedValue,
30
37
  };
31
38
  const stableId = useId();
@@ -37,7 +44,7 @@ const SegmentedControl = ({ children, className, defaultValue, disabled = false,
37
44
  }, className);
38
45
  return (React__default.createElement("div", { ...restProps, className: combinedClassName, id: componentId },
39
46
  React__default.createElement("div", { className: classNames("tds-segmented-control-label", hideLabel && "tds-segmented-control-label--hidden"), id: labelId }, label),
40
- React__default.createElement("div", { "aria-labelledby": labelId, className: "tds-segmented-control-track", role: "radiogroup" },
47
+ React__default.createElement("div", { "aria-labelledby": labelId, className: "tds-segmented-control-track", ref: trackRef, role: "radiogroup" },
41
48
  React__default.createElement(SegmentedControlContext.Provider, { value: contextValue }, children))));
42
49
  };
43
50
  SegmentedControl.displayName = "SegmentedControl";
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentedControl.js","sources":["../../../src/components/segmented-control/SegmentedControl.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport { useId } from \"@utilities/useId\"\nimport classNames from \"classnames\"\nimport React, { type HTMLAttributes, useState } from \"react\"\n\nimport { SegmentedControlContext } from \"./SegmentedControlContext\"\n\nexport type SegmentedControlSize = \"md\" | \"sm\"\n\ninterface SegmentedControlSharedProps {\n /** `Segment`/`IconSegment` elements. */\n children: React.ReactNode\n /** Disables the whole group. */\n disabled?: boolean\n /**\n * If `true`, the group spans 100% of its container and items split the\n * width evenly. Intended for a primary mode switch at the top of a\n * section/modal/pane — not for a control nested deep in a form.\n */\n fullWidth?: boolean\n /**\n * If `true`, renders `label` visually hidden instead of a visible\n * element.\n */\n hideLabel?: boolean\n /**\n * Accessible label for the group. Rendered as a visible element by\n * default.\n */\n label: string\n /** Called with the newly selected item's `value`. */\n onChange?: (value: string) => void\n /** Default `'md'`. */\n size?: SegmentedControlSize\n}\n\ninterface SegmentedControlControlledProps extends SegmentedControlSharedProps {\n defaultValue?: never\n /** Controlled selected item's `value`. */\n value: string\n}\n\ninterface SegmentedControlUncontrolledProps\n extends SegmentedControlSharedProps {\n /**\n * Uncontrolled initial selected item's `value` — a segmented control\n * always reflects a current mode, so one item must start selected.\n */\n defaultValue: string\n value?: never\n}\n\nexport type SegmentedControlProps =\n | SegmentedControlControlledProps\n | SegmentedControlUncontrolledProps\n\nexport type SegmentedControlElementProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n keyof SegmentedControlProps | \"children\" | \"role\"\n> &\n SegmentedControlProps\n\n/**\n * A single-select group of items — Table/Card, Yes/No, Tag/Tag group —\n * where exactly one `Segment`/`IconSegment` is selected at a time. Arrow keys\n * move the selection, and the group is one tab stop.\n *\n * @component\n * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/forms-segmentedcontrol--docs | Storybook Documentation}\n */\nexport const SegmentedControl = ({\n children,\n className,\n defaultValue,\n disabled = false,\n fullWidth = false,\n hideLabel = false,\n id,\n label,\n onChange,\n size = \"md\",\n value,\n ...restProps\n}: SegmentedControlElementProps) => {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue)\n const isControlled = value !== undefined\n const selectedValue = isControlled ? value : uncontrolledValue\n\n const onSelect = (nextValue: string) => {\n if (nextValue === selectedValue) return\n if (!isControlled) setUncontrolledValue(nextValue)\n onChange?.(nextValue)\n }\n\n const contextValue = {\n disabled,\n onSelect,\n tabbableValue: selectedValue,\n value: selectedValue,\n }\n\n const stableId = useId()\n const componentId = id || `tds-segmented-control-${stableId}`\n const labelId = `${componentId}-label`\n\n const combinedClassName = classNames(\n \"tds-segmented-control\",\n {\n \"tds-segmented-control--full-width\": fullWidth,\n \"tds-segmented-control--sm\": size === \"sm\",\n },\n className\n )\n\n return (\n <div {...restProps} className={combinedClassName} id={componentId}>\n <div\n className={classNames(\n \"tds-segmented-control-label\",\n hideLabel && \"tds-segmented-control-label--hidden\"\n )}\n id={labelId}\n >\n {label}\n </div>\n <div\n aria-labelledby={labelId}\n className=\"tds-segmented-control-track\"\n role=\"radiogroup\"\n >\n <SegmentedControlContext.Provider value={contextValue}>\n {children}\n </SegmentedControlContext.Provider>\n </div>\n </div>\n )\n}\n\nSegmentedControl.displayName = \"SegmentedControl\"\n"],"names":["React"],"mappings":";;;;;AA+DA;;;;;;;AAOG;AACI,MAAM,gBAAgB,GAAG,CAAC,EAC/B,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,QAAQ,GAAG,KAAK,EAChB,SAAS,GAAG,KAAK,EACjB,SAAS,GAAG,KAAK,EACjB,EAAE,EACF,KAAK,EACL,QAAQ,EACR,IAAI,GAAG,IAAI,EACX,KAAK,EACL,GAAG,SAAS,EACiB,KAAI;IACjC,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC;AACxE,IAAA,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS;IACxC,MAAM,aAAa,GAAG,YAAY,GAAG,KAAK,GAAG,iBAAiB;AAE9D,IAAA,MAAM,QAAQ,GAAG,CAAC,SAAiB,KAAI;QACrC,IAAI,SAAS,KAAK,aAAa;YAAE;AACjC,QAAA,IAAI,CAAC,YAAY;YAAE,oBAAoB,CAAC,SAAS,CAAC;AAClD,QAAA,QAAQ,GAAG,SAAS,CAAC;AACvB,IAAA,CAAC;AAED,IAAA,MAAM,YAAY,GAAG;QACnB,QAAQ;QACR,QAAQ;AACR,QAAA,aAAa,EAAE,aAAa;AAC5B,QAAA,KAAK,EAAE,aAAa;KACrB;AAED,IAAA,MAAM,QAAQ,GAAG,KAAK,EAAE;AACxB,IAAA,MAAM,WAAW,GAAG,EAAE,IAAI,CAAA,sBAAA,EAAyB,QAAQ,EAAE;AAC7D,IAAA,MAAM,OAAO,GAAG,CAAA,EAAG,WAAW,QAAQ;AAEtC,IAAA,MAAM,iBAAiB,GAAG,UAAU,CAClC,uBAAuB,EACvB;AACE,QAAA,mCAAmC,EAAE,SAAS;QAC9C,2BAA2B,EAAE,IAAI,KAAK,IAAI;KAC3C,EACD,SAAS,CACV;IAED,QACEA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAA,GAAS,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,EAAE,EAAE,WAAW,EAAA;AAC/D,QAAAA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAE,UAAU,CACnB,6BAA6B,EAC7B,SAAS,IAAI,qCAAqC,CACnD,EACD,EAAE,EAAE,OAAO,EAAA,EAEV,KAAK,CACF;QACNA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAA,iBAAA,EACmB,OAAO,EACxB,SAAS,EAAC,6BAA6B,EACvC,IAAI,EAAC,YAAY,EAAA;AAEjB,YAAAA,cAAA,CAAA,aAAA,CAAC,uBAAuB,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,YAAY,EAAA,EAClD,QAAQ,CACwB,CAC/B,CACF;AAEV;AAEA,gBAAgB,CAAC,WAAW,GAAG,kBAAkB;;;;"}
1
+ {"version":3,"file":"SegmentedControl.js","sources":["../../../src/components/segmented-control/SegmentedControl.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport { useId } from \"@utilities/useId\"\nimport classNames from \"classnames\"\nimport React, {\n type HTMLAttributes,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\"\n\nimport { SegmentedControlContext } from \"./SegmentedControlContext\"\n\nexport type SegmentedControlSize = \"md\" | \"sm\"\n\ninterface SegmentedControlSharedProps {\n /** `Segment`/`IconSegment` elements. */\n children: React.ReactNode\n /** Disables the whole group. */\n disabled?: boolean\n /**\n * If `true`, the group spans 100% of its container and items split the\n * width evenly. Intended for a primary mode switch at the top of a\n * section/modal/pane — not for a control nested deep in a form.\n */\n fullWidth?: boolean\n /**\n * If `true`, renders `label` visually hidden instead of a visible\n * element.\n */\n hideLabel?: boolean\n /**\n * Accessible label for the group. Rendered as a visible element by\n * default.\n */\n label: string\n /** Called with the newly selected item's `value`. */\n onChange?: (value: string) => void\n /** Default `'md'`. */\n size?: SegmentedControlSize\n}\n\ninterface SegmentedControlControlledProps extends SegmentedControlSharedProps {\n defaultValue?: never\n /** Controlled selected item's `value`. */\n value: string\n}\n\ninterface SegmentedControlUncontrolledProps\n extends SegmentedControlSharedProps {\n /**\n * Uncontrolled initial selected item's `value` — a segmented control\n * always reflects a current mode, so one item must start selected.\n */\n defaultValue: string\n value?: never\n}\n\nexport type SegmentedControlProps =\n | SegmentedControlControlledProps\n | SegmentedControlUncontrolledProps\n\nexport type SegmentedControlElementProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n keyof SegmentedControlProps | \"children\" | \"role\"\n> &\n SegmentedControlProps\n\n/**\n * A single-select group of items — Table/Card, Yes/No, Tag/Tag group —\n * where exactly one `Segment`/`IconSegment` is selected at a time. Arrow keys\n * move the selection, and the group is one tab stop.\n *\n * @component\n * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/forms-segmentedcontrol--docs | Storybook Documentation}\n */\nexport const SegmentedControl = ({\n children,\n className,\n defaultValue,\n disabled = false,\n fullWidth = false,\n hideLabel = false,\n id,\n label,\n onChange,\n size = \"md\",\n value,\n ...restProps\n}: SegmentedControlElementProps) => {\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue)\n const isControlled = value !== undefined\n const selectedValue = isControlled ? value : uncontrolledValue\n\n const onSelect = (nextValue: string) => {\n if (nextValue === selectedValue) return\n if (!isControlled) setUncontrolledValue(nextValue)\n onChange?.(nextValue)\n }\n\n const trackRef = useRef<HTMLDivElement>(null)\n const [fallbackTabbableValue, setFallbackTabbableValue] = useState<\n string | undefined\n >()\n\n useLayoutEffect(() => {\n const enabledSegments = Array.from(\n trackRef.current?.querySelectorAll<HTMLButtonElement>(\n '[role=\"radio\"]:not(:disabled)'\n ) ?? []\n )\n const selectedIsEnabled = enabledSegments.some(\n (segment) => segment.dataset.segmentValue === selectedValue\n )\n setFallbackTabbableValue(\n selectedIsEnabled ? undefined : enabledSegments[0]?.dataset.segmentValue\n )\n }, [children, disabled, selectedValue])\n\n const contextValue = {\n disabled,\n onSelect,\n tabbableValue: fallbackTabbableValue ?? selectedValue,\n value: selectedValue,\n }\n\n const stableId = useId()\n const componentId = id || `tds-segmented-control-${stableId}`\n const labelId = `${componentId}-label`\n\n const combinedClassName = classNames(\n \"tds-segmented-control\",\n {\n \"tds-segmented-control--full-width\": fullWidth,\n \"tds-segmented-control--sm\": size === \"sm\",\n },\n className\n )\n\n return (\n <div {...restProps} className={combinedClassName} id={componentId}>\n <div\n className={classNames(\n \"tds-segmented-control-label\",\n hideLabel && \"tds-segmented-control-label--hidden\"\n )}\n id={labelId}\n >\n {label}\n </div>\n <div\n aria-labelledby={labelId}\n className=\"tds-segmented-control-track\"\n ref={trackRef}\n role=\"radiogroup\"\n >\n <SegmentedControlContext.Provider value={contextValue}>\n {children}\n </SegmentedControlContext.Provider>\n </div>\n </div>\n )\n}\n\nSegmentedControl.displayName = \"SegmentedControl\"\n"],"names":["React"],"mappings":";;;;;AAoEA;;;;;;;AAOG;AACI,MAAM,gBAAgB,GAAG,CAAC,EAC/B,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,QAAQ,GAAG,KAAK,EAChB,SAAS,GAAG,KAAK,EACjB,SAAS,GAAG,KAAK,EACjB,EAAE,EACF,KAAK,EACL,QAAQ,EACR,IAAI,GAAG,IAAI,EACX,KAAK,EACL,GAAG,SAAS,EACiB,KAAI;IACjC,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC;AACxE,IAAA,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS;IACxC,MAAM,aAAa,GAAG,YAAY,GAAG,KAAK,GAAG,iBAAiB;AAE9D,IAAA,MAAM,QAAQ,GAAG,CAAC,SAAiB,KAAI;QACrC,IAAI,SAAS,KAAK,aAAa;YAAE;AACjC,QAAA,IAAI,CAAC,YAAY;YAAE,oBAAoB,CAAC,SAAS,CAAC;AAClD,QAAA,QAAQ,GAAG,SAAS,CAAC;AACvB,IAAA,CAAC;AAED,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAiB,IAAI,CAAC;IAC7C,MAAM,CAAC,qBAAqB,EAAE,wBAAwB,CAAC,GAAG,QAAQ,EAE/D;IAEH,eAAe,CAAC,MAAK;AACnB,QAAA,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAChC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAChC,+BAA+B,CAChC,IAAI,EAAE,CACR;AACD,QAAA,MAAM,iBAAiB,GAAG,eAAe,CAAC,IAAI,CAC5C,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,YAAY,KAAK,aAAa,CAC5D;AACD,QAAA,wBAAwB,CACtB,iBAAiB,GAAG,SAAS,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CACzE;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAEvC,IAAA,MAAM,YAAY,GAAG;QACnB,QAAQ;QACR,QAAQ;QACR,aAAa,EAAE,qBAAqB,IAAI,aAAa;AACrD,QAAA,KAAK,EAAE,aAAa;KACrB;AAED,IAAA,MAAM,QAAQ,GAAG,KAAK,EAAE;AACxB,IAAA,MAAM,WAAW,GAAG,EAAE,IAAI,CAAA,sBAAA,EAAyB,QAAQ,EAAE;AAC7D,IAAA,MAAM,OAAO,GAAG,CAAA,EAAG,WAAW,QAAQ;AAEtC,IAAA,MAAM,iBAAiB,GAAG,UAAU,CAClC,uBAAuB,EACvB;AACE,QAAA,mCAAmC,EAAE,SAAS;QAC9C,2BAA2B,EAAE,IAAI,KAAK,IAAI;KAC3C,EACD,SAAS,CACV;IAED,QACEA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAA,GAAS,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,EAAE,EAAE,WAAW,EAAA;AAC/D,QAAAA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAE,UAAU,CACnB,6BAA6B,EAC7B,SAAS,IAAI,qCAAqC,CACnD,EACD,EAAE,EAAE,OAAO,EAAA,EAEV,KAAK,CACF;AACN,QAAAA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAA,iBAAA,EACmB,OAAO,EACxB,SAAS,EAAC,6BAA6B,EACvC,GAAG,EAAE,QAAQ,EACb,IAAI,EAAC,YAAY,EAAA;AAEjB,YAAAA,cAAA,CAAA,aAAA,CAAC,uBAAuB,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,YAAY,EAAA,EAClD,QAAQ,CACwB,CAC/B,CACF;AAEV;AAEA,gBAAgB,CAAC,WAAW,GAAG,kBAAkB;;;;"}