@planningcenter/tapestry 4.8.0-rc.0 → 4.8.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/dist/components/DataTable/ActiveFilterInstance.d.ts +2 -2
- package/dist/components/DataTable/ActiveFilterInstance.d.ts.map +1 -1
- package/dist/components/DataTable/ActiveFilterInstance.js +4 -1
- package/dist/components/DataTable/ActiveFilterInstance.js.map +1 -1
- package/dist/components/DataTable/ActiveFilters.d.ts +7 -4
- package/dist/components/DataTable/ActiveFilters.d.ts.map +1 -1
- package/dist/components/DataTable/ActiveFilters.js +43 -15
- package/dist/components/DataTable/ActiveFilters.js.map +1 -1
- package/dist/components/DataTable/CollectionEditorsShared.d.ts +33 -0
- package/dist/components/DataTable/CollectionEditorsShared.d.ts.map +1 -0
- package/dist/components/DataTable/CollectionEditorsShared.js +16 -0
- package/dist/components/DataTable/CollectionEditorsShared.js.map +1 -0
- package/dist/components/DataTable/CollectionEditorsStatic.d.ts +14 -0
- package/dist/components/DataTable/CollectionEditorsStatic.d.ts.map +1 -0
- package/dist/components/DataTable/CollectionEditorsStatic.js +67 -0
- package/dist/components/DataTable/CollectionEditorsStatic.js.map +1 -0
- package/dist/components/DataTable/DataTable.d.ts +1 -1
- package/dist/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/components/DataTable/DataTable.js +59 -56
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/FilterChip.d.ts.map +1 -1
- package/dist/components/DataTable/FilterChip.js +25 -1
- package/dist/components/DataTable/FilterChip.js.map +1 -1
- package/dist/components/DataTable/FilterContext.d.ts +10 -1
- package/dist/components/DataTable/FilterContext.d.ts.map +1 -1
- package/dist/components/DataTable/FilterContext.js +52 -9
- package/dist/components/DataTable/FilterContext.js.map +1 -1
- package/dist/components/DataTable/FilterControls.d.ts.map +1 -1
- package/dist/components/DataTable/FilterControls.js +85 -23
- package/dist/components/DataTable/FilterControls.js.map +1 -1
- package/dist/components/DataTable/FilterControls.module.css.js +4 -0
- package/dist/components/DataTable/FilterControls.module.css.js.map +1 -0
- package/dist/components/DataTable/FilterTypes.d.ts +34 -0
- package/dist/components/DataTable/FilterTypes.d.ts.map +1 -1
- package/dist/components/DataTable/FilterTypes.js +7 -0
- package/dist/components/DataTable/FilterTypes.js.map +1 -0
- package/dist/components/DataTable/PendingFilterContext.d.ts +12 -0
- package/dist/components/DataTable/PendingFilterContext.d.ts.map +1 -0
- package/dist/components/DataTable/PendingFilterContext.js +33 -0
- package/dist/components/DataTable/PendingFilterContext.js.map +1 -0
- package/dist/components/DataTable/PendingFilterQueue.d.ts +13 -0
- package/dist/components/DataTable/PendingFilterQueue.d.ts.map +1 -0
- package/dist/components/DataTable/PendingFilterQueue.js +42 -0
- package/dist/components/DataTable/PendingFilterQueue.js.map +1 -0
- package/dist/components/DataTable/SearchableFilters.d.ts +4 -0
- package/dist/components/DataTable/SearchableFilters.d.ts.map +1 -1
- package/dist/components/DataTable/SearchableFilters.js +26 -0
- package/dist/components/DataTable/SearchableFilters.js.map +1 -1
- package/dist/components/DataTable/collectionFilter.d.ts +0 -2
- package/dist/components/DataTable/collectionFilter.d.ts.map +1 -1
- package/dist/components/DataTable/collectionFilter.js +3 -45
- package/dist/components/DataTable/collectionFilter.js.map +1 -1
- package/dist/components/DataTable/dateFilter.d.ts.map +1 -1
- package/dist/components/DataTable/dateFilter.js +23 -7
- package/dist/components/DataTable/dateFilter.js.map +1 -1
- package/dist/components/DataTable/filters.d.ts +1 -0
- package/dist/components/DataTable/filters.d.ts.map +1 -1
- package/dist/components/DataTable/index.d.ts +2 -1
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/numberFilter.d.ts.map +1 -1
- package/dist/components/DataTable/numberFilter.js +15 -4
- package/dist/components/DataTable/numberFilter.js.map +1 -1
- package/dist/components/DataTable/scopedFilter.d.ts +13 -0
- package/dist/components/DataTable/scopedFilter.d.ts.map +1 -0
- package/dist/components/DataTable/scopedFilter.js +114 -0
- package/dist/components/DataTable/scopedFilter.js.map +1 -0
- package/dist/components/DataTable/textFilter.d.ts.map +1 -1
- package/dist/components/DataTable/textFilter.js +8 -3
- package/dist/components/DataTable/textFilter.js.map +1 -1
- package/dist/components/DataTable/useShelfExpanded.d.ts +4 -3
- package/dist/components/DataTable/useShelfExpanded.d.ts.map +1 -1
- package/dist/components/DataTable/useShelfExpanded.js +16 -8
- package/dist/components/DataTable/useShelfExpanded.js.map +1 -1
- package/dist/components/DataTable/utils.d.ts +4 -0
- package/dist/components/DataTable/utils.d.ts.map +1 -0
- package/dist/components/DataTable/utils.js +13 -0
- package/dist/components/DataTable/utils.js.map +1 -0
- package/dist/components/currency-input/CurrencyInput.d.ts +51 -0
- package/dist/components/currency-input/CurrencyInput.d.ts.map +1 -0
- package/dist/components/currency-input/CurrencyInput.js +64 -0
- package/dist/components/currency-input/CurrencyInput.js.map +1 -0
- package/dist/components/currency-input/index.d.ts +4 -0
- package/dist/components/currency-input/index.d.ts.map +1 -0
- package/dist/components/popover/Popover.d.ts +12 -7
- package/dist/components/popover/Popover.d.ts.map +1 -1
- package/dist/components/popover/Popover.js +26 -4
- package/dist/components/popover/Popover.js.map +1 -1
- package/dist/reactRender.css +1305 -1090
- package/dist/reactRender.css.map +1 -1
- package/dist/reactRenderLegacy.css +1305 -1090
- package/dist/reactRenderLegacy.css.map +1 -1
- package/dist/unstable.css +442 -227
- package/dist/unstable.css.map +1 -1
- package/dist/unstable.d.ts +1 -0
- package/dist/unstable.d.ts.map +1 -1
- package/dist/unstable.js +2 -0
- package/dist/unstable.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useShelfExpanded.js","sources":["../../../src/components/DataTable/useShelfExpanded.ts"],"sourcesContent":["import { useCallback, useState } from \"react\"\n\n/**\n * Expands the filters shelf when the
|
|
1
|
+
{"version":3,"file":"useShelfExpanded.js","sources":["../../../src/components/DataTable/useShelfExpanded.ts"],"sourcesContent":["import { useCallback, useState } from \"react\"\n\n/**\n * Expands the filters shelf when the filter count leaves zero or a pending\n * filter is added, and collapses it when the count returns to zero. Returns a\n * toggle to control it.\n */\nexport function useShelfExpanded(\n filterCount: number,\n pendingCount: number\n): [boolean, () => void] {\n const [isShelfExpanded, setIsShelfExpanded] = useState(() => filterCount > 0)\n const [previousCount, setPreviousCount] = useState(filterCount)\n const [previousPendingCount, setPreviousPendingCount] = useState(pendingCount)\n\n if (filterCount !== previousCount) {\n setPreviousCount(filterCount)\n\n if (filterCount === 0) {\n setIsShelfExpanded(false)\n } else if (previousCount === 0) {\n setIsShelfExpanded(true)\n }\n }\n\n if (pendingCount !== previousPendingCount) {\n setPreviousPendingCount(pendingCount)\n\n if (pendingCount > previousPendingCount) {\n setIsShelfExpanded(true)\n }\n }\n\n const toggleShelf = useCallback(() => {\n setIsShelfExpanded((expanded) => !expanded)\n }, [])\n\n return [isShelfExpanded, toggleShelf]\n}\n"],"names":[],"mappings":";;AAEA;;;;AAIG;AACG,SAAU,gBAAgB,CAC9B,WAAmB,EACnB,YAAoB,EAAA;AAEpB,IAAA,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;IAC7E,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC;IAC/D,MAAM,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC;AAE9E,IAAA,IAAI,WAAW,KAAK,aAAa,EAAE;QACjC,gBAAgB,CAAC,WAAW,CAAC;AAE7B,QAAA,IAAI,WAAW,KAAK,CAAC,EAAE;YACrB,kBAAkB,CAAC,KAAK,CAAC;QAC3B;AAAO,aAAA,IAAI,aAAa,KAAK,CAAC,EAAE;YAC9B,kBAAkB,CAAC,IAAI,CAAC;QAC1B;IACF;AAEA,IAAA,IAAI,YAAY,KAAK,oBAAoB,EAAE;QACzC,uBAAuB,CAAC,YAAY,CAAC;AAErC,QAAA,IAAI,YAAY,GAAG,oBAAoB,EAAE;YACvC,kBAAkB,CAAC,IAAI,CAAC;QAC1B;IACF;AAEA,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,MAAK;QACnC,kBAAkB,CAAC,CAAC,QAAQ,KAAK,CAAC,QAAQ,CAAC;IAC7C,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,OAAO,CAAC,eAAe,EAAE,WAAW,CAAC;AACvC;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAGnD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAGnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGpD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// TODO: Do we want to allow a specified "default" operator, rather than always falling back to the first one?
|
|
2
|
+
function getDefaultOperatorName(node) {
|
|
3
|
+
const operatorName = node.operators?.[0]?.name;
|
|
4
|
+
return typeof operatorName === "string" ? operatorName : "default";
|
|
5
|
+
}
|
|
6
|
+
function isEmptyValue(value) {
|
|
7
|
+
if (value === undefined || value === null || value === "")
|
|
8
|
+
return true;
|
|
9
|
+
return Array.isArray(value) && value.length === 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { getDefaultOperatorName, isEmptyValue };
|
|
13
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/components/DataTable/utils.ts"],"sourcesContent":["import type { FilterNodeItem } from \"./FilterTypes\"\n\n// TODO: Do we want to allow a specified \"default\" operator, rather than always falling back to the first one?\nexport function getDefaultOperatorName(node: FilterNodeItem): string {\n const operatorName = node.operators?.[0]?.name\n return typeof operatorName === \"string\" ? operatorName : \"default\"\n}\n\nexport function isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null || value === \"\") return true\n return Array.isArray(value) && value.length === 0\n}\n"],"names":[],"mappings":"AAEA;AACM,SAAU,sBAAsB,CAAC,IAAoB,EAAA;IACzD,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,IAAI;AAC9C,IAAA,OAAO,OAAO,YAAY,KAAK,QAAQ,GAAG,YAAY,GAAG,SAAS;AACpE;AAEM,SAAU,YAAY,CAAC,KAAc,EAAA;IACzC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,IAAI;AACtE,IAAA,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;AACnD;;;;"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import "./index.css";
|
|
2
|
+
import React, { type InputHTMLAttributes } from "react";
|
|
3
|
+
export type CurrencyInputSize = "lg" | "md";
|
|
4
|
+
export type CurrencyInputDisplay = "code" | "narrowSymbol" | "symbol";
|
|
5
|
+
interface CurrencyInputBaseProps {
|
|
6
|
+
/** ISO 4217 currency code (e.g. `"USD"`, `"CAD"`, `"EUR"`). Defaults to `"USD"`. Drives both the visual prefix and the screen-reader-only currency name. Unrecognized codes degrade the visual prefix to the code itself rather than blanking the field; the screen-reader-only currency name instead falls back to a generic "Unknown currency." */
|
|
7
|
+
currency?: string;
|
|
8
|
+
/** How to render the visual prefix. Defaults to `"narrowSymbol"`. Passed straight to `Intl.NumberFormat`. */
|
|
9
|
+
currencyDisplay?: CurrencyInputDisplay;
|
|
10
|
+
/** Helper text below the input. Styled as an error when `invalid`. */
|
|
11
|
+
description?: React.ReactNode;
|
|
12
|
+
/** If true, sets `data-flex-expand` so the field grows/shrinks to fill a `Flex` row. */
|
|
13
|
+
expand?: boolean;
|
|
14
|
+
/** Triggers invalid state (error border, error description, aria-invalid). */
|
|
15
|
+
invalid?: boolean;
|
|
16
|
+
/** BCP 47 locale used to derive the prefix and the screen-reader currency name. Defaults to the runtime's resolved locale. Only visibly changes anything under `currencyDisplay="symbol"` — under the default `narrowSymbol` a bad locale silently falls back rather than throwing. */
|
|
17
|
+
locale?: string;
|
|
18
|
+
/** Visual size — cascades to the prefix and input via Field's font-size custom property. Defaults to 'md'. */
|
|
19
|
+
size?: CurrencyInputSize;
|
|
20
|
+
}
|
|
21
|
+
interface CurrencyInputWithLabel extends CurrencyInputBaseProps {
|
|
22
|
+
"aria-labelledby"?: never;
|
|
23
|
+
/** If true, the label is visually hidden but remains in the DOM/AT tree. */
|
|
24
|
+
hideLabel?: boolean;
|
|
25
|
+
/** The visible label. */
|
|
26
|
+
label: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
interface CurrencyInputWithAriaLabelledBy extends CurrencyInputBaseProps {
|
|
29
|
+
"aria-labelledby": string;
|
|
30
|
+
hideLabel?: never;
|
|
31
|
+
label?: never;
|
|
32
|
+
}
|
|
33
|
+
export type CurrencyInputProps = CurrencyInputWithAriaLabelledBy | CurrencyInputWithLabel;
|
|
34
|
+
/**
|
|
35
|
+
* Combines standard HTML input attributes with CurrencyInput's own props.
|
|
36
|
+
* `type` and `inputMode` are fixed and not exposed; `size` is redefined above.
|
|
37
|
+
*/
|
|
38
|
+
export type CurrencyInputElementProps = Omit<InputHTMLAttributes<HTMLInputElement>, keyof CurrencyInputProps | "inputMode" | "size" | "type"> & CurrencyInputProps;
|
|
39
|
+
/**
|
|
40
|
+
* A currency-prefixed text input — a plain `<input>` with a leading currency
|
|
41
|
+
* symbol (`$`, `€`, `CA$`, …) derived from an ISO 4217 currency code. It is
|
|
42
|
+
* presentational only: no masking, parsing, or rounding. Value in, value
|
|
43
|
+
* out — callers keep whatever number handling they already have. Composes
|
|
44
|
+
* `Field` directly for label, description, and state layout.
|
|
45
|
+
*
|
|
46
|
+
* @component
|
|
47
|
+
* @see {@link https://planningcenter.github.io/tapestry/?path=/docs/unstable-currencyinput--docs | Storybook Documentation}
|
|
48
|
+
*/
|
|
49
|
+
export declare const CurrencyInput: React.ForwardRefExoticComponent<CurrencyInputElementProps & React.RefAttributes<HTMLInputElement>>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=CurrencyInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CurrencyInput.d.ts","sourceRoot":"","sources":["../../../src/components/currency-input/CurrencyInput.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAKpB,OAAO,KAAK,EAAE,EAAc,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAA;AAEnE,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAA;AAuCrE,UAAU,sBAAsB;IAC9B,qVAAqV;IACrV,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6GAA6G;IAC7G,eAAe,CAAC,EAAE,oBAAoB,CAAA;IACtC,sEAAsE;IACtE,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,wFAAwF;IACxF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uRAAuR;IACvR,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8GAA8G;IAC9G,IAAI,CAAC,EAAE,iBAAiB,CAAA;CACzB;AAED,UAAU,sBAAuB,SAAQ,sBAAsB;IAC7D,iBAAiB,CAAC,EAAE,KAAK,CAAA;IACzB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,yBAAyB;IACzB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;CACvB;AAED,UAAU,+BAAgC,SAAQ,sBAAsB;IACtE,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,MAAM,kBAAkB,GAC1B,+BAA+B,GAC/B,sBAAsB,CAAA;AAE1B;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,MAAM,kBAAkB,GAAG,WAAW,GAAG,MAAM,GAAG,MAAM,CACzD,GACC,kBAAkB,CAAA;AAEpB;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,oGAsExB,CAAA"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Field } from '../field/Field.js';
|
|
2
|
+
import { useId } from '../../utilities/useId.js';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import React__default, { forwardRef } from 'react';
|
|
5
|
+
|
|
6
|
+
function symbolFor(currency, currencyDisplay, locale) {
|
|
7
|
+
try {
|
|
8
|
+
return (new Intl.NumberFormat(locale, {
|
|
9
|
+
currency,
|
|
10
|
+
currencyDisplay,
|
|
11
|
+
style: "currency",
|
|
12
|
+
})
|
|
13
|
+
.formatToParts(0)
|
|
14
|
+
.find((part) => part.type === "currency")?.value ?? currency);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return currency;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const UNKNOWN_CURRENCY_NAME = "Unknown currency.";
|
|
21
|
+
function currencyNameFor(currency, locale) {
|
|
22
|
+
if (!Intl.supportedValuesOf("currency").includes(currency.toUpperCase())) {
|
|
23
|
+
return UNKNOWN_CURRENCY_NAME;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const name = new Intl.DisplayNames(locale, { type: "currency" }).of(currency);
|
|
27
|
+
return name ? `${name}.` : UNKNOWN_CURRENCY_NAME;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return UNKNOWN_CURRENCY_NAME;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A currency-prefixed text input — a plain `<input>` with a leading currency
|
|
35
|
+
* symbol (`$`, `€`, `CA$`, …) derived from an ISO 4217 currency code. It is
|
|
36
|
+
* presentational only: no masking, parsing, or rounding. Value in, value
|
|
37
|
+
* out — callers keep whatever number handling they already have. Composes
|
|
38
|
+
* `Field` directly for label, description, and state layout.
|
|
39
|
+
*
|
|
40
|
+
* @component
|
|
41
|
+
* @see {@link https://planningcenter.github.io/tapestry/?path=/docs/unstable-currencyinput--docs | Storybook Documentation}
|
|
42
|
+
*/
|
|
43
|
+
const CurrencyInput = forwardRef(function CurrencyInput({ "aria-labelledby": ariaLabelledBy, className, currency = "USD", currencyDisplay = "narrowSymbol", description, expand, hideLabel, id, invalid, label, locale, size = "md", ...restProps }, ref) {
|
|
44
|
+
const stableId = useId();
|
|
45
|
+
const inputId = id || `tds-currency-input-${stableId}`;
|
|
46
|
+
const descriptionId = description ? `${inputId}-description` : undefined;
|
|
47
|
+
const currencyNameId = `${inputId}-currency-name`;
|
|
48
|
+
const describedBy = [currencyNameId, descriptionId].filter(Boolean).join(" ");
|
|
49
|
+
const symbol = symbolFor(currency, currencyDisplay, locale);
|
|
50
|
+
const currencyName = currencyNameFor(currency, locale);
|
|
51
|
+
const fieldClassName = classNames({ "tds-field--lg": size === "lg" }, className);
|
|
52
|
+
const fieldLabelProps = ariaLabelledBy
|
|
53
|
+
? { "aria-labelledby": ariaLabelledBy }
|
|
54
|
+
: { hideLabel, label };
|
|
55
|
+
return (React__default.createElement(Field, { ...fieldLabelProps, className: fieldClassName, description: description, expand: expand, id: inputId },
|
|
56
|
+
React__default.createElement("div", { className: "tds-currency-input-control" },
|
|
57
|
+
React__default.createElement("div", { "aria-hidden": "true", className: "tds-currency-input-prefix" }, symbol),
|
|
58
|
+
React__default.createElement("input", { ...restProps, "aria-describedby": describedBy, "aria-invalid": invalid || undefined, "aria-labelledby": ariaLabelledBy, id: inputId, inputMode: "decimal", ref: ref, type: "text" })),
|
|
59
|
+
React__default.createElement("div", { className: "tds-currency-input-currency-name--hidden", id: currencyNameId }, currencyName)));
|
|
60
|
+
});
|
|
61
|
+
CurrencyInput.displayName = "CurrencyInput";
|
|
62
|
+
|
|
63
|
+
export { CurrencyInput };
|
|
64
|
+
//# sourceMappingURL=CurrencyInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CurrencyInput.js","sources":["../../../src/components/currency-input/CurrencyInput.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport { Field } from \"@components/field\"\nimport { useId } from \"@utilities/useId\"\nimport classNames from \"classnames\"\nimport React, { forwardRef, type InputHTMLAttributes } from \"react\"\n\nexport type CurrencyInputSize = \"lg\" | \"md\"\n\nexport type CurrencyInputDisplay = \"code\" | \"narrowSymbol\" | \"symbol\"\n\nfunction symbolFor(\n currency: string,\n currencyDisplay: CurrencyInputDisplay,\n locale?: string\n): string {\n try {\n return (\n new Intl.NumberFormat(locale, {\n currency,\n currencyDisplay,\n style: \"currency\",\n })\n .formatToParts(0)\n .find((part) => part.type === \"currency\")?.value ?? currency\n )\n } catch {\n return currency\n }\n}\n\nconst UNKNOWN_CURRENCY_NAME = \"Unknown currency.\"\n\nfunction currencyNameFor(currency: string, locale?: string): string {\n if (!Intl.supportedValuesOf(\"currency\").includes(currency.toUpperCase())) {\n return UNKNOWN_CURRENCY_NAME\n }\n\n try {\n const name = new Intl.DisplayNames(locale, { type: \"currency\" }).of(\n currency\n )\n return name ? `${name}.` : UNKNOWN_CURRENCY_NAME\n } catch {\n return UNKNOWN_CURRENCY_NAME\n }\n}\n\ninterface CurrencyInputBaseProps {\n /** ISO 4217 currency code (e.g. `\"USD\"`, `\"CAD\"`, `\"EUR\"`). Defaults to `\"USD\"`. Drives both the visual prefix and the screen-reader-only currency name. Unrecognized codes degrade the visual prefix to the code itself rather than blanking the field; the screen-reader-only currency name instead falls back to a generic \"Unknown currency.\" */\n currency?: string\n /** How to render the visual prefix. Defaults to `\"narrowSymbol\"`. Passed straight to `Intl.NumberFormat`. */\n currencyDisplay?: CurrencyInputDisplay\n /** Helper text below the input. Styled as an error when `invalid`. */\n description?: React.ReactNode\n /** If true, sets `data-flex-expand` so the field grows/shrinks to fill a `Flex` row. */\n expand?: boolean\n /** Triggers invalid state (error border, error description, aria-invalid). */\n invalid?: boolean\n /** BCP 47 locale used to derive the prefix and the screen-reader currency name. Defaults to the runtime's resolved locale. Only visibly changes anything under `currencyDisplay=\"symbol\"` — under the default `narrowSymbol` a bad locale silently falls back rather than throwing. */\n locale?: string\n /** Visual size — cascades to the prefix and input via Field's font-size custom property. Defaults to 'md'. */\n size?: CurrencyInputSize\n}\n\ninterface CurrencyInputWithLabel extends CurrencyInputBaseProps {\n \"aria-labelledby\"?: never\n /** If true, the label is visually hidden but remains in the DOM/AT tree. */\n hideLabel?: boolean\n /** The visible label. */\n label: React.ReactNode\n}\n\ninterface CurrencyInputWithAriaLabelledBy extends CurrencyInputBaseProps {\n \"aria-labelledby\": string\n hideLabel?: never\n label?: never\n}\n\nexport type CurrencyInputProps =\n | CurrencyInputWithAriaLabelledBy\n | CurrencyInputWithLabel\n\n/**\n * Combines standard HTML input attributes with CurrencyInput's own props.\n * `type` and `inputMode` are fixed and not exposed; `size` is redefined above.\n */\nexport type CurrencyInputElementProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n keyof CurrencyInputProps | \"inputMode\" | \"size\" | \"type\"\n> &\n CurrencyInputProps\n\n/**\n * A currency-prefixed text input — a plain `<input>` with a leading currency\n * symbol (`$`, `€`, `CA$`, …) derived from an ISO 4217 currency code. It is\n * presentational only: no masking, parsing, or rounding. Value in, value\n * out — callers keep whatever number handling they already have. Composes\n * `Field` directly for label, description, and state layout.\n *\n * @component\n * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/unstable-currencyinput--docs | Storybook Documentation}\n */\nexport const CurrencyInput = forwardRef<\n HTMLInputElement,\n CurrencyInputElementProps\n>(function CurrencyInput(\n {\n \"aria-labelledby\": ariaLabelledBy,\n className,\n currency = \"USD\",\n currencyDisplay = \"narrowSymbol\",\n description,\n expand,\n hideLabel,\n id,\n invalid,\n label,\n locale,\n size = \"md\",\n ...restProps\n }: CurrencyInputElementProps,\n ref\n) {\n const stableId = useId()\n const inputId = id || `tds-currency-input-${stableId}`\n const descriptionId = description ? `${inputId}-description` : undefined\n const currencyNameId = `${inputId}-currency-name`\n const describedBy = [currencyNameId, descriptionId].filter(Boolean).join(\" \")\n\n const symbol = symbolFor(currency, currencyDisplay, locale)\n const currencyName = currencyNameFor(currency, locale)\n\n const fieldClassName = classNames(\n { \"tds-field--lg\": size === \"lg\" },\n className\n )\n\n const fieldLabelProps = ariaLabelledBy\n ? { \"aria-labelledby\": ariaLabelledBy }\n : { hideLabel, label }\n\n return (\n <Field\n {...fieldLabelProps}\n className={fieldClassName}\n description={description}\n expand={expand}\n id={inputId}\n >\n <div className=\"tds-currency-input-control\">\n <div aria-hidden=\"true\" className=\"tds-currency-input-prefix\">\n {symbol}\n </div>\n <input\n {...restProps}\n aria-describedby={describedBy}\n aria-invalid={invalid || undefined}\n aria-labelledby={ariaLabelledBy}\n id={inputId}\n inputMode=\"decimal\"\n ref={ref}\n type=\"text\"\n />\n </div>\n <div\n className=\"tds-currency-input-currency-name--hidden\"\n id={currencyNameId}\n >\n {currencyName}\n </div>\n </Field>\n )\n})\n\nCurrencyInput.displayName = \"CurrencyInput\"\n"],"names":["React"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/currency-input/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,YAAY,EACV,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -63,13 +63,14 @@ export interface PopoverContentProps {
|
|
|
63
63
|
/** Appended to `tds-popover` on the positioned surface. */
|
|
64
64
|
className?: string;
|
|
65
65
|
/**
|
|
66
|
-
* Defaults to `true`. When `true`, the surface
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
66
|
+
* Defaults to `true`. When `true`, the surface hides outside content from
|
|
67
|
+
* assistive tech and sits over a click-blocking underlay — the right
|
|
68
|
+
* default for most popovers. Set to `false` for lighter-weight dismissal
|
|
69
|
+
* behavior (maps to React Aria's `isNonModal`, whose own doc comment scopes
|
|
70
|
+
* it to "components such as combobox" — this is why it isn't the default).
|
|
71
|
+
* `<Dialog>` always renders regardless of this prop; `modal` only toggles
|
|
72
|
+
* the underlay and outside-content handling, not whether a dialog role
|
|
73
|
+
* exists.
|
|
73
74
|
*
|
|
74
75
|
* `modal={false}` still blocks page scroll while open — React Aria's own
|
|
75
76
|
* scroll-close behavior for non-modal popovers can't be disabled, so
|
|
@@ -87,6 +88,10 @@ export type PopoverContentElementProps = PopoverContentProps;
|
|
|
87
88
|
* The popover surface for a `<Popover>`. Wraps React Aria's `<Popover>` and
|
|
88
89
|
* `<Dialog>` and contains arbitrary content.
|
|
89
90
|
*
|
|
91
|
+
* On close, focus returns to the popover's own trigger — even when it was
|
|
92
|
+
* opened programmatically via `open` or `defaultOpen` rather than by pressing
|
|
93
|
+
* the trigger.
|
|
94
|
+
*
|
|
90
95
|
* @component
|
|
91
96
|
*/
|
|
92
97
|
export declare function PopoverContent({ "aria-label": ariaLabel, children, className, modal, placement, }: PopoverContentElementProps): React.JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../../src/components/popover/Popover.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,EAGZ,KAAK,YAAY,EACjB,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../../src/components/popover/Popover.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,EAGZ,KAAK,YAAY,EACjB,KAAK,SAAS,EAOf,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AASrF,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,QAAQ,EAAE,SAAS,CAAA;IACnB,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,wFAAwF;IACxF,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAA;AAE9C;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,IAAI,GACL,EAAE,mBAAmB,qBAUrB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,QAAQ,EAAE,YAAY,CAAA;CACvB;AAED,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,CAAA;AAI5D;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,0BAA0B,qBAEtE;AAID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,IAAI,CAE5C;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yBAAyB;IACzB,QAAQ,EAAE,SAAS,CAAA;IACnB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;CAC1C;AAED,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,CAAA;AAE5D;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAC7B,YAAY,EAAE,SAAS,EACvB,QAAQ,EACR,SAAS,EACT,KAAY,EACZ,SAA0B,GAC3B,EAAE,0BAA0B,qBA6C5B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import React__default, { createContext, useRef, useContext, useCallback } from 'react';
|
|
2
|
+
import React__default, { createContext, useRef, useContext, useCallback, useLayoutEffect, useEffect } from 'react';
|
|
3
3
|
import { useInteractOutside, usePreventScroll, FocusScope } from 'react-aria';
|
|
4
4
|
import { OverlayTriggerStateContext } from 'react-aria-components/Dialog';
|
|
5
5
|
import { DialogTrigger, PopoverContext, Popover as Popover$1, Dialog, Pressable } from 'react-aria-components/Popover';
|
|
@@ -40,6 +40,10 @@ function usePopoverClose() {
|
|
|
40
40
|
* The popover surface for a `<Popover>`. Wraps React Aria's `<Popover>` and
|
|
41
41
|
* `<Dialog>` and contains arbitrary content.
|
|
42
42
|
*
|
|
43
|
+
* On close, focus returns to the popover's own trigger — even when it was
|
|
44
|
+
* opened programmatically via `open` or `defaultOpen` rather than by pressing
|
|
45
|
+
* the trigger.
|
|
46
|
+
*
|
|
43
47
|
* @component
|
|
44
48
|
*/
|
|
45
49
|
function PopoverContent({ "aria-label": ariaLabel, children, className, modal = true, placement = "bottom start", }) {
|
|
@@ -50,8 +54,9 @@ function PopoverContent({ "aria-label": ariaLabel, children, className, modal =
|
|
|
50
54
|
const triggerRef = popoverContext && "triggerRef" in popoverContext
|
|
51
55
|
? popoverContext.triggerRef
|
|
52
56
|
: undefined;
|
|
57
|
+
const isOpen = Boolean(state?.isOpen);
|
|
53
58
|
useInteractOutside({
|
|
54
|
-
isDisabled: modal || !
|
|
59
|
+
isDisabled: modal || !isOpen,
|
|
55
60
|
onInteractOutside: (event) => {
|
|
56
61
|
if (triggerRef?.current?.contains(event.target))
|
|
57
62
|
return;
|
|
@@ -59,12 +64,29 @@ function PopoverContent({ "aria-label": ariaLabel, children, className, modal =
|
|
|
59
64
|
},
|
|
60
65
|
ref: popoverRef,
|
|
61
66
|
});
|
|
62
|
-
usePreventScroll({ isDisabled: modal || !
|
|
67
|
+
usePreventScroll({ isDisabled: modal || !isOpen });
|
|
68
|
+
const isOpenRef = useRef(isOpen);
|
|
69
|
+
useLayoutEffect(() => {
|
|
70
|
+
isOpenRef.current = isOpen;
|
|
71
|
+
}, [isOpen]);
|
|
63
72
|
return (React__default.createElement(Popover$1, { className: classNames("tds-popover", className), isNonModal: !modal, placement: placement, ref: popoverRef, shouldCloseOnInteractOutside: modal ? undefined : () => false },
|
|
64
|
-
React__default.createElement(FocusScope, { contain: !modal },
|
|
73
|
+
React__default.createElement(FocusScope, { contain: !modal && isOpen },
|
|
65
74
|
React__default.createElement(Dialog, { "aria-label": ariaLabel, className: "tds-popover-content" },
|
|
75
|
+
React__default.createElement(FocusTriggerOnClose, { isOpenRef: isOpenRef, triggerRef: triggerRef }),
|
|
66
76
|
React__default.createElement(PopoverCloseContext.Provider, { value: close }, children)))));
|
|
67
77
|
}
|
|
78
|
+
function FocusTriggerOnClose({ isOpenRef, triggerRef, }) {
|
|
79
|
+
useEffect(() => () => {
|
|
80
|
+
const trigger = triggerRef?.current;
|
|
81
|
+
if (!isOpenRef.current &&
|
|
82
|
+
document.activeElement === document.body &&
|
|
83
|
+
trigger instanceof HTMLElement &&
|
|
84
|
+
trigger.isConnected) {
|
|
85
|
+
trigger.focus({ preventScroll: true });
|
|
86
|
+
}
|
|
87
|
+
}, [isOpenRef, triggerRef]);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
68
90
|
|
|
69
91
|
export { Popover, PopoverContent, PopoverTrigger, usePopoverClose };
|
|
70
92
|
//# sourceMappingURL=Popover.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.js","sources":["../../../src/components/popover/Popover.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport classNames from \"classnames\"\nimport React, {\n type ComponentProps,\n createContext,\n type ReactElement,\n type ReactNode,\n useCallback,\n useContext,\n useRef,\n} from \"react\"\nimport { FocusScope, useInteractOutside, usePreventScroll } from \"react-aria\"\nimport { OverlayTriggerStateContext } from \"react-aria-components/Dialog\"\nimport type { PopoverProps as AriaPopoverProps } from \"react-aria-components/Popover\"\nimport {\n Dialog,\n DialogTrigger,\n Popover as AriaPopover,\n PopoverContext as AriaPopoverContext,\n Pressable,\n} from \"react-aria-components/Popover\"\n\nexport interface PopoverProps {\n /** A `<PopoverTrigger>` followed by a `<PopoverContent>`. */\n children: ReactNode\n /** Initial open state for the uncontrolled case. Useful for stories and tests. */\n defaultOpen?: boolean\n /**\n * Fires whenever the surface opens or closes — trigger press, Escape,\n * outside press, or `close()`. Required when `open` is set.\n */\n onOpenChange?: (open: boolean) => void\n /** Controlled open state. Renamed from React Aria's `isOpen`. Omit for uncontrolled. */\n open?: boolean\n}\n\nexport type PopoverElementProps = PopoverProps\n\n/**\n * A generic popover surface anchored to a trigger button. Unlike `Dropdown`\n * (a semantic `role=\"menu\"` action list), `Popover` renders arbitrary content\n * — a search field, a form, a list, plain text.\n *\n * **Rule of thumb:** a flat list of actions or links → `Dropdown`. Anything\n * else anchored to a button → `Popover`. Owns the screen until resolved →\n * `Modal`.\n *\n * @component\n * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/components-popover--docs | Storybook Documentation}\n */\nexport function Popover({\n children,\n defaultOpen,\n onOpenChange,\n open,\n}: PopoverElementProps) {\n return (\n <DialogTrigger\n defaultOpen={defaultOpen}\n isOpen={open}\n onOpenChange={onOpenChange}\n >\n {children}\n </DialogTrigger>\n )\n}\n\nexport interface PopoverTriggerProps {\n /**\n * Exactly one Tapestry button element — `Button`, `IconButton`,\n * `DropdownButton`, `DropdownIconButton`, `ActionsDropdownIconButton`, or\n * `PageHeaderActionsDropdownButton`. Receives `aria-expanded`,\n * `aria-controls`, `id`, and press wiring via `Pressable` — the same RAC\n * mechanism `Dropdown`'s trigger already uses. Must render a real `<button>`.\n */\n children: ReactElement\n}\n\nexport type PopoverTriggerElementProps = PopoverTriggerProps\n\ntype PressableChild = ComponentProps<typeof Pressable>[\"children\"]\n\n/**\n * Wraps a single Tapestry button and bridges it to React Aria's\n * `DialogTrigger` via `<Pressable>`.\n *\n * @component\n */\nexport function PopoverTrigger({ children }: PopoverTriggerElementProps) {\n return <Pressable>{children as PressableChild}</Pressable>\n}\n\nconst PopoverCloseContext = createContext<() => void>(() => {})\n\n/**\n * Returns the enclosing `<PopoverContent>`'s `close` function. Call it from\n * any descendant to close the surface — e.g. a plain `<Button onClick={close}>`.\n */\nexport function usePopoverClose(): () => void {\n return useContext(PopoverCloseContext)\n}\n\nexport interface PopoverContentProps {\n /**\n * Accessible name for the surface. Defaults to the trigger's accessible\n * name (React Aria points `aria-labelledby` at the trigger's id). Set this\n * when the surface needs a different name than the trigger.\n */\n \"aria-label\"?: string\n /** Arbitrary content. */\n children: ReactNode\n /** Appended to `tds-popover` on the positioned surface. */\n className?: string\n /**\n * Defaults to `true`. When `true`, the surface
|
|
1
|
+
{"version":3,"file":"Popover.js","sources":["../../../src/components/popover/Popover.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport classNames from \"classnames\"\nimport React, {\n type ComponentProps,\n createContext,\n type ReactElement,\n type ReactNode,\n type RefObject,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n} from \"react\"\nimport { FocusScope, useInteractOutside, usePreventScroll } from \"react-aria\"\nimport { OverlayTriggerStateContext } from \"react-aria-components/Dialog\"\nimport type { PopoverProps as AriaPopoverProps } from \"react-aria-components/Popover\"\nimport {\n Dialog,\n DialogTrigger,\n Popover as AriaPopover,\n PopoverContext as AriaPopoverContext,\n Pressable,\n} from \"react-aria-components/Popover\"\n\nexport interface PopoverProps {\n /** A `<PopoverTrigger>` followed by a `<PopoverContent>`. */\n children: ReactNode\n /** Initial open state for the uncontrolled case. Useful for stories and tests. */\n defaultOpen?: boolean\n /**\n * Fires whenever the surface opens or closes — trigger press, Escape,\n * outside press, or `close()`. Required when `open` is set.\n */\n onOpenChange?: (open: boolean) => void\n /** Controlled open state. Renamed from React Aria's `isOpen`. Omit for uncontrolled. */\n open?: boolean\n}\n\nexport type PopoverElementProps = PopoverProps\n\n/**\n * A generic popover surface anchored to a trigger button. Unlike `Dropdown`\n * (a semantic `role=\"menu\"` action list), `Popover` renders arbitrary content\n * — a search field, a form, a list, plain text.\n *\n * **Rule of thumb:** a flat list of actions or links → `Dropdown`. Anything\n * else anchored to a button → `Popover`. Owns the screen until resolved →\n * `Modal`.\n *\n * @component\n * @see {@link https://planningcenter.github.io/tapestry/?path=/docs/components-popover--docs | Storybook Documentation}\n */\nexport function Popover({\n children,\n defaultOpen,\n onOpenChange,\n open,\n}: PopoverElementProps) {\n return (\n <DialogTrigger\n defaultOpen={defaultOpen}\n isOpen={open}\n onOpenChange={onOpenChange}\n >\n {children}\n </DialogTrigger>\n )\n}\n\nexport interface PopoverTriggerProps {\n /**\n * Exactly one Tapestry button element — `Button`, `IconButton`,\n * `DropdownButton`, `DropdownIconButton`, `ActionsDropdownIconButton`, or\n * `PageHeaderActionsDropdownButton`. Receives `aria-expanded`,\n * `aria-controls`, `id`, and press wiring via `Pressable` — the same RAC\n * mechanism `Dropdown`'s trigger already uses. Must render a real `<button>`.\n */\n children: ReactElement\n}\n\nexport type PopoverTriggerElementProps = PopoverTriggerProps\n\ntype PressableChild = ComponentProps<typeof Pressable>[\"children\"]\n\n/**\n * Wraps a single Tapestry button and bridges it to React Aria's\n * `DialogTrigger` via `<Pressable>`.\n *\n * @component\n */\nexport function PopoverTrigger({ children }: PopoverTriggerElementProps) {\n return <Pressable>{children as PressableChild}</Pressable>\n}\n\nconst PopoverCloseContext = createContext<() => void>(() => {})\n\n/**\n * Returns the enclosing `<PopoverContent>`'s `close` function. Call it from\n * any descendant to close the surface — e.g. a plain `<Button onClick={close}>`.\n */\nexport function usePopoverClose(): () => void {\n return useContext(PopoverCloseContext)\n}\n\nexport interface PopoverContentProps {\n /**\n * Accessible name for the surface. Defaults to the trigger's accessible\n * name (React Aria points `aria-labelledby` at the trigger's id). Set this\n * when the surface needs a different name than the trigger.\n */\n \"aria-label\"?: string\n /** Arbitrary content. */\n children: ReactNode\n /** Appended to `tds-popover` on the positioned surface. */\n className?: string\n /**\n * Defaults to `true`. When `true`, the surface hides outside content from\n * assistive tech and sits over a click-blocking underlay — the right\n * default for most popovers. Set to `false` for lighter-weight dismissal\n * behavior (maps to React Aria's `isNonModal`, whose own doc comment scopes\n * it to \"components such as combobox\" — this is why it isn't the default).\n * `<Dialog>` always renders regardless of this prop; `modal` only toggles\n * the underlay and outside-content handling, not whether a dialog role\n * exists.\n *\n * `modal={false}` still blocks page scroll while open — React Aria's own\n * scroll-close behavior for non-modal popovers can't be disabled, so\n * scrolling is locked instead, the same way `Dropdown`'s menu does.\n */\n modal?: boolean\n /**\n * Position relative to the trigger, in React Aria's space-separated form.\n * Defaults to `'bottom start'`. Flips automatically when there isn't room.\n */\n placement?: AriaPopoverProps[\"placement\"]\n}\n\nexport type PopoverContentElementProps = PopoverContentProps\n\n/**\n * The popover surface for a `<Popover>`. Wraps React Aria's `<Popover>` and\n * `<Dialog>` and contains arbitrary content.\n *\n * On close, focus returns to the popover's own trigger — even when it was\n * opened programmatically via `open` or `defaultOpen` rather than by pressing\n * the trigger.\n *\n * @component\n */\nexport function PopoverContent({\n \"aria-label\": ariaLabel,\n children,\n className,\n modal = true,\n placement = \"bottom start\",\n}: PopoverContentElementProps) {\n const popoverRef = useRef<HTMLElement>(null)\n const state = useContext(OverlayTriggerStateContext)\n const close = useCallback(() => state?.close(), [state])\n\n const popoverContext = useContext(AriaPopoverContext)\n const triggerRef =\n popoverContext && \"triggerRef\" in popoverContext\n ? popoverContext.triggerRef\n : undefined\n\n const isOpen = Boolean(state?.isOpen)\n useInteractOutside({\n isDisabled: modal || !isOpen,\n onInteractOutside: (event) => {\n if (triggerRef?.current?.contains(event.target as Node)) return\n close()\n },\n ref: popoverRef,\n })\n usePreventScroll({ isDisabled: modal || !isOpen })\n\n const isOpenRef = useRef(isOpen)\n useLayoutEffect(() => {\n isOpenRef.current = isOpen\n }, [isOpen])\n\n return (\n <AriaPopover\n className={classNames(\"tds-popover\", className)}\n isNonModal={!modal}\n placement={placement}\n ref={popoverRef}\n shouldCloseOnInteractOutside={modal ? undefined : () => false}\n >\n <FocusScope contain={!modal && isOpen}>\n <Dialog aria-label={ariaLabel} className=\"tds-popover-content\">\n <FocusTriggerOnClose isOpenRef={isOpenRef} triggerRef={triggerRef} />\n <PopoverCloseContext.Provider value={close}>\n {children}\n </PopoverCloseContext.Provider>\n </Dialog>\n </FocusScope>\n </AriaPopover>\n )\n}\n\nfunction FocusTriggerOnClose({\n isOpenRef,\n triggerRef,\n}: {\n isOpenRef: RefObject<boolean>\n triggerRef?: RefObject<Element | null>\n}) {\n useEffect(\n () => () => {\n const trigger = triggerRef?.current\n if (\n !isOpenRef.current &&\n document.activeElement === document.body &&\n trigger instanceof HTMLElement &&\n trigger.isConnected\n ) {\n trigger.focus({ preventScroll: true })\n }\n },\n [isOpenRef, triggerRef]\n )\n return null\n}\n"],"names":["React","AriaPopoverContext","AriaPopover"],"mappings":"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"}
|