@aglyn/shared-ui-jsx 1.0.0-beta.230 → 1.0.0-beta.231

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 (40) hide show
  1. package/package.json +5 -5
  2. package/src/lib/components/copy-field.component.d.ts +25 -0
  3. package/src/lib/components/copy-field.component.js +77 -0
  4. package/src/lib/components/copy-field.component.js.map +1 -0
  5. package/src/lib/components/download-cost-badge.component.d.ts +37 -0
  6. package/src/lib/components/download-cost-badge.component.js +89 -0
  7. package/src/lib/components/download-cost-badge.component.js.map +1 -0
  8. package/src/lib/components/file-drop-zone.component.d.ts +48 -0
  9. package/src/lib/components/file-drop-zone.component.js +173 -0
  10. package/src/lib/components/file-drop-zone.component.js.map +1 -0
  11. package/src/lib/components/list-query-notices.component.d.ts +1 -18
  12. package/src/lib/components/list-query-notices.component.js +1 -11
  13. package/src/lib/components/list-query-notices.component.js.map +1 -1
  14. package/src/lib/components/searchable-virtual-list.component.d.ts +72 -0
  15. package/src/lib/components/searchable-virtual-list.component.js +212 -0
  16. package/src/lib/components/searchable-virtual-list.component.js.map +1 -0
  17. package/src/lib/components/site-live-notice.component.d.ts +39 -0
  18. package/src/lib/components/site-live-notice.component.js +95 -0
  19. package/src/lib/components/site-live-notice.component.js.map +1 -0
  20. package/src/lib/components/status-chip.component.d.ts +26 -0
  21. package/src/lib/components/status-chip.component.js +48 -0
  22. package/src/lib/components/status-chip.component.js.map +1 -0
  23. package/src/lib/const/list-filter-sentence.d.ts +1 -24
  24. package/src/lib/const/list-filter-sentence.js +3 -105
  25. package/src/lib/const/list-filter-sentence.js.map +1 -1
  26. package/src/lib/const/list-filter.d.ts +1 -219
  27. package/src/lib/const/list-filter.js +4 -282
  28. package/src/lib/const/list-filter.js.map +1 -1
  29. package/src/lib/const/list-grid-filter.d.ts +4 -25
  30. package/src/lib/const/list-grid-filter.js +20 -44
  31. package/src/lib/const/list-grid-filter.js.map +1 -1
  32. package/src/lib/const/list-query-plan.d.ts +1 -154
  33. package/src/lib/const/list-query-plan.js +4 -610
  34. package/src/lib/const/list-query-plan.js.map +1 -1
  35. package/src/lib/hooks/use-client-pagination.d.ts +25 -0
  36. package/src/lib/hooks/use-client-pagination.js +65 -0
  37. package/src/lib/hooks/use-client-pagination.js.map +1 -0
  38. package/src/lib/hooks/use-in-view.d.ts +16 -0
  39. package/src/lib/hooks/use-in-view.js +66 -0
  40. package/src/lib/hooks/use-in-view.js.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-ui-jsx",
3
- "version": "1.0.0-beta.230",
3
+ "version": "1.0.0-beta.231",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,10 +25,10 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-mdi": "1.0.0-beta.230",
29
- "@aglyn/shared-ui-theme": "1.0.0-beta.230",
30
- "@aglyn/shared-util-tools": "1.0.0-beta.230",
31
- "@aglyn/shared-util-vendor": "1.0.0-beta.230",
28
+ "@aglyn/shared-data-mdi": "1.0.0-beta.231",
29
+ "@aglyn/shared-ui-theme": "1.0.0-beta.231",
30
+ "@aglyn/shared-util-tools": "1.0.0-beta.231",
31
+ "@aglyn/shared-util-vendor": "1.0.0-beta.231",
32
32
  "@mdi/js": "^7.4.47",
33
33
  "@swc/helpers": "0.5.23",
34
34
  "change-case": "^5.4.4",
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface CopyFieldProps {
3
+ label: string;
4
+ value: string;
5
+ helperText?: ReactNode;
6
+ /** Called once the value is on the clipboard, e.g. to show a snackbar. */
7
+ onCopied?: () => void;
8
+ /** Called when the browser refused the clipboard. */
9
+ onCopyFailed?: (error: unknown) => void;
10
+ /** The copy button's accessible name; `Copy {label}` by default. */
11
+ copyLabel?: string;
12
+ size?: 'small' | 'medium';
13
+ fullWidth?: boolean;
14
+ }
15
+ /**
16
+ * A value a reader copies somewhere else — a feed address, an API key's
17
+ * public half, a webhook URL — shown read-only with a copy button inside the
18
+ * field (AGL-3637). Focusing the field selects the whole value, so copying by
19
+ * hand works too.
20
+ */
21
+ export declare function CopyField(props: CopyFieldProps): import("react").JSX.Element;
22
+ export declare namespace CopyField {
23
+ var displayName: string;
24
+ }
25
+ export default CopyField;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ import { mdiContentCopy } from "@aglyn/shared-data-mdi";
19
+ import IconButton from "@mui/material/IconButton";
20
+ import InputAdornment from "@mui/material/InputAdornment";
21
+ import TextField from "@mui/material/TextField";
22
+ import Tooltip from "@mui/material/Tooltip";
23
+ import { useState } from "react";
24
+ import { MdiIcon } from "./mdi-icon/mdi-icon.js";
25
+ /**
26
+ * A value a reader copies somewhere else — a feed address, an API key's
27
+ * public half, a webhook URL — shown read-only with a copy button inside the
28
+ * field (AGL-3637). Focusing the field selects the whole value, so copying by
29
+ * hand works too.
30
+ */ export function CopyField(props) {
31
+ var _props_copyLabel;
32
+ const { label, value, helperText, onCopied, onCopyFailed, size = 'small', fullWidth = true } = props;
33
+ const [copied, setCopied] = useState(false);
34
+ const copyLabel = (_props_copyLabel = props.copyLabel) != null ? _props_copyLabel : `Copy ${label.toLowerCase()}`;
35
+ const copy = async ()=>{
36
+ try {
37
+ await navigator.clipboard.writeText(value);
38
+ setCopied(true);
39
+ onCopied == null ? void 0 : onCopied();
40
+ } catch (error) {
41
+ onCopyFailed == null ? void 0 : onCopyFailed(error);
42
+ }
43
+ };
44
+ return /*#__PURE__*/ _jsx(TextField, {
45
+ label: label,
46
+ value: value,
47
+ size: size,
48
+ fullWidth: fullWidth,
49
+ helperText: helperText,
50
+ onFocus: (event)=>event.target.select(),
51
+ slotProps: {
52
+ input: {
53
+ readOnly: true,
54
+ endAdornment: /*#__PURE__*/ _jsx(InputAdornment, {
55
+ position: "end",
56
+ children: /*#__PURE__*/ _jsx(Tooltip, {
57
+ title: copied ? 'Copied' : copyLabel,
58
+ children: /*#__PURE__*/ _jsx(IconButton, {
59
+ "aria-label": copyLabel,
60
+ edge: "end",
61
+ onClick: ()=>void copy(),
62
+ onMouseLeave: ()=>setCopied(false),
63
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
64
+ path: mdiContentCopy,
65
+ fontSize: "small"
66
+ })
67
+ })
68
+ })
69
+ })
70
+ }
71
+ }
72
+ });
73
+ }
74
+ CopyField.displayName = 'CopyField';
75
+ export default CopyField;
76
+
77
+ //# sourceMappingURL=copy-field.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/copy-field.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { mdiContentCopy } from '@aglyn/shared-data-mdi'\nimport IconButton from '@mui/material/IconButton'\nimport InputAdornment from '@mui/material/InputAdornment'\nimport TextField from '@mui/material/TextField'\nimport Tooltip from '@mui/material/Tooltip'\nimport { useState, type ReactNode } from 'react'\nimport { MdiIcon } from './mdi-icon/mdi-icon'\n\nexport interface CopyFieldProps {\n label: string\n value: string\n helperText?: ReactNode\n /** Called once the value is on the clipboard, e.g. to show a snackbar. */\n onCopied?: () => void\n /** Called when the browser refused the clipboard. */\n onCopyFailed?: (error: unknown) => void\n /** The copy button's accessible name; `Copy {label}` by default. */\n copyLabel?: string\n size?: 'small' | 'medium'\n fullWidth?: boolean\n}\n\n/**\n * A value a reader copies somewhere else — a feed address, an API key's\n * public half, a webhook URL — shown read-only with a copy button inside the\n * field (AGL-3637). Focusing the field selects the whole value, so copying by\n * hand works too.\n */\nexport function CopyField(props: CopyFieldProps) {\n const { label, value, helperText, onCopied, onCopyFailed, size = 'small', fullWidth = true } = props\n const [copied, setCopied] = useState(false)\n const copyLabel = props.copyLabel ?? `Copy ${label.toLowerCase()}`\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(value)\n setCopied(true)\n onCopied?.()\n } catch (error) {\n onCopyFailed?.(error)\n }\n }\n return (\n <TextField\n label={label}\n value={value}\n size={size}\n fullWidth={fullWidth}\n helperText={helperText}\n onFocus={(event) => event.target.select()}\n slotProps={{\n input: {\n readOnly: true,\n endAdornment: (\n <InputAdornment position=\"end\">\n <Tooltip title={copied ? 'Copied' : copyLabel}>\n <IconButton aria-label={copyLabel} edge=\"end\" onClick={() => void copy()} onMouseLeave={() => setCopied(false)}>\n <MdiIcon path={mdiContentCopy} fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </InputAdornment>\n ),\n },\n }}\n />\n )\n}\nCopyField.displayName = 'CopyField'\n\nexport default CopyField\n"],"names":["mdiContentCopy","IconButton","InputAdornment","TextField","Tooltip","useState","MdiIcon","CopyField","props","label","value","helperText","onCopied","onCopyFailed","size","fullWidth","copied","setCopied","copyLabel","toLowerCase","copy","navigator","clipboard","writeText","error","onFocus","event","target","select","slotProps","input","readOnly","endAdornment","position","title","aria-label","edge","onClick","onMouseLeave","path","fontSize","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,cAAc,QAAQ,yBAAwB;AACvD,OAAOC,gBAAgB,2BAA0B;AACjD,OAAOC,oBAAoB,+BAA8B;AACzD,OAAOC,eAAe,0BAAyB;AAC/C,OAAOC,aAAa,wBAAuB;AAC3C,SAASC,QAAQ,QAAwB,QAAO;AAChD,SAASC,OAAO,QAAQ,yBAAqB;AAgB7C;;;;;CAKC,GACD,OAAO,SAASC,UAAUC,KAAqB;QAG3BA;IAFlB,MAAM,EAAEC,KAAK,EAAEC,KAAK,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,YAAY,EAAEC,OAAO,OAAO,EAAEC,YAAY,IAAI,EAAE,GAAGP;IAC/F,MAAM,CAACQ,QAAQC,UAAU,GAAGZ,SAAS;IACrC,MAAMa,aAAYV,mBAAAA,MAAMU,SAAS,YAAfV,mBAAmB,CAAC,KAAK,EAAEC,MAAMU,WAAW,IAAI;IAClE,MAAMC,OAAO;QACX,IAAI;YACF,MAAMC,UAAUC,SAAS,CAACC,SAAS,CAACb;YACpCO,UAAU;YACVL,4BAAAA;QACF,EAAE,OAAOY,OAAO;YACdX,gCAAAA,aAAeW;QACjB;IACF;IACA,qBACE,KAACrB;QACCM,OAAOA;QACPC,OAAOA;QACPI,MAAMA;QACNC,WAAWA;QACXJ,YAAYA;QACZc,SAAS,CAACC,QAAUA,MAAMC,MAAM,CAACC,MAAM;QACvCC,WAAW;YACTC,OAAO;gBACLC,UAAU;gBACVC,4BACE,KAAC9B;oBAAe+B,UAAS;8BACvB,cAAA,KAAC7B;wBAAQ8B,OAAOlB,SAAS,WAAWE;kCAClC,cAAA,KAACjB;4BAAWkC,cAAYjB;4BAAWkB,MAAK;4BAAMC,SAAS,IAAM,KAAKjB;4BAAQkB,cAAc,IAAMrB,UAAU;sCACtG,cAAA,KAACX;gCAAQiC,MAAMvC;gCAAgBwC,UAAS;;;;;YAKlD;QACF;;AAGN;AACAjC,UAAUkC,WAAW,GAAG;AAExB,eAAelC,UAAS"}
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * A download size as a person reads it: `0 KB`, `38 KB`, `1.2 MB`.
4
+ * Kilobytes are 1,024 bytes, as browsers' network panels count them.
5
+ */
6
+ export declare function formatDownloadSize(bytes: number): string;
7
+ /** `38 KB · 2 files`, approximate when `approximate`. */
8
+ export declare function downloadCostLabel(bytes: number, files: number, approximate?: boolean): string;
9
+ export interface DownloadCostBadgeProps {
10
+ /** Bytes a visitor downloads; absent while unknown. */
11
+ bytes?: number | null;
12
+ /** How many files those bytes are. */
13
+ files?: number | null;
14
+ /** Shows `≈`: some of the size could not be measured, or it is an estimate. */
15
+ approximate?: boolean;
16
+ /** Still measuring. */
17
+ loading?: boolean;
18
+ /** Why it could not be measured; shows the badge's error state. */
19
+ error?: string | null;
20
+ /** What the figure counts, for the badge's tooltip. */
21
+ tooltip?: ReactNode;
22
+ /** The label while measuring. */
23
+ loadingLabel?: string;
24
+ }
25
+ /**
26
+ * What something costs a visitor to download, as one small badge: the size
27
+ * and the number of files, with a tooltip saying what is counted, and its
28
+ * own measuring and failed states (AGL-3656, first for the font picker).
29
+ *
30
+ * A badge, not a sentence, because it sits beside a choice and is read at a
31
+ * glance while comparing two. NOT in the barrel: subpath-import it.
32
+ */
33
+ export declare function DownloadCostBadge(props: DownloadCostBadgeProps): import("react").JSX.Element;
34
+ export declare namespace DownloadCostBadge {
35
+ var displayName: string;
36
+ }
37
+ export default DownloadCostBadge;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { mdiAlertCircleOutline, mdiTrayArrowDown } from "@aglyn/shared-data-mdi";
19
+ import { Chip, CircularProgress, Tooltip } from "@mui/material";
20
+ import MdiIcon from "./mdi-icon/mdi-icon.js";
21
+ /**
22
+ * A download size as a person reads it: `0 KB`, `38 KB`, `1.2 MB`.
23
+ * Kilobytes are 1,024 bytes, as browsers' network panels count them.
24
+ */ export function formatDownloadSize(bytes) {
25
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 KB';
26
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
27
+ return `${Math.max(1, Math.round(bytes / 1024))} KB`;
28
+ }
29
+ /** `38 KB · 2 files`, approximate when `approximate`. */ export function downloadCostLabel(bytes, files, approximate = false) {
30
+ const size = `${approximate ? '≈ ' : ''}${formatDownloadSize(bytes)}`;
31
+ if (!files) return size;
32
+ return `${size} · ${files} ${files === 1 ? 'file' : 'files'}`;
33
+ }
34
+ /**
35
+ * What something costs a visitor to download, as one small badge: the size
36
+ * and the number of files, with a tooltip saying what is counted, and its
37
+ * own measuring and failed states (AGL-3656, first for the font picker).
38
+ *
39
+ * A badge, not a sentence, because it sits beside a choice and is read at a
40
+ * glance while comparing two. NOT in the barrel: subpath-import it.
41
+ */ export function DownloadCostBadge(props) {
42
+ const { bytes, files, approximate = false, loading = false, error = null, tooltip, loadingLabel = 'Measuring…' } = props;
43
+ let chip;
44
+ if (loading) {
45
+ chip = /*#__PURE__*/ _jsx(Chip, {
46
+ size: "small",
47
+ variant: "outlined",
48
+ icon: /*#__PURE__*/ _jsx(CircularProgress, {
49
+ size: "1em",
50
+ color: "inherit"
51
+ }),
52
+ label: loadingLabel,
53
+ "aria-busy": true
54
+ });
55
+ } else if (error || bytes === null || bytes === undefined) {
56
+ chip = /*#__PURE__*/ _jsx(Chip, {
57
+ size: "small",
58
+ variant: "outlined",
59
+ color: "warning",
60
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
61
+ path: mdiAlertCircleOutline.path
62
+ }),
63
+ label: "Size unavailable"
64
+ });
65
+ } else {
66
+ chip = /*#__PURE__*/ _jsx(Chip, {
67
+ size: "small",
68
+ variant: "outlined",
69
+ color: bytes === 0 ? 'success' : 'default',
70
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
71
+ path: mdiTrayArrowDown.path
72
+ }),
73
+ label: downloadCostLabel(bytes, files != null ? files : 0, approximate)
74
+ });
75
+ }
76
+ const title = error != null ? error : tooltip;
77
+ if (!title) return /*#__PURE__*/ _jsx(_Fragment, {
78
+ children: chip
79
+ });
80
+ return /*#__PURE__*/ _jsx(Tooltip, {
81
+ title: title,
82
+ describeChild: true,
83
+ children: chip
84
+ });
85
+ }
86
+ DownloadCostBadge.displayName = 'DownloadCostBadge';
87
+ export default DownloadCostBadge;
88
+
89
+ //# sourceMappingURL=download-cost-badge.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/download-cost-badge.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { mdiAlertCircleOutline, mdiTrayArrowDown } from '@aglyn/shared-data-mdi'\nimport { Chip, CircularProgress, Tooltip } from '@mui/material'\nimport type { ReactElement, ReactNode } from 'react'\nimport MdiIcon from './mdi-icon/mdi-icon'\n\n/**\n * A download size as a person reads it: `0 KB`, `38 KB`, `1.2 MB`.\n * Kilobytes are 1,024 bytes, as browsers' network panels count them.\n */\nexport function formatDownloadSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) return '0 KB'\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${Math.max(1, Math.round(bytes / 1024))} KB`\n}\n\n/** `38 KB · 2 files`, approximate when `approximate`. */\nexport function downloadCostLabel(\n bytes: number,\n files: number,\n approximate = false,\n): string {\n const size = `${approximate ? '≈ ' : ''}${formatDownloadSize(bytes)}`\n if (!files) return size\n return `${size} · ${files} ${files === 1 ? 'file' : 'files'}`\n}\n\nexport interface DownloadCostBadgeProps {\n /** Bytes a visitor downloads; absent while unknown. */\n bytes?: number | null\n /** How many files those bytes are. */\n files?: number | null\n /** Shows `≈`: some of the size could not be measured, or it is an estimate. */\n approximate?: boolean\n /** Still measuring. */\n loading?: boolean\n /** Why it could not be measured; shows the badge's error state. */\n error?: string | null\n /** What the figure counts, for the badge's tooltip. */\n tooltip?: ReactNode\n /** The label while measuring. */\n loadingLabel?: string\n}\n\n/**\n * What something costs a visitor to download, as one small badge: the size\n * and the number of files, with a tooltip saying what is counted, and its\n * own measuring and failed states (AGL-3656, first for the font picker).\n *\n * A badge, not a sentence, because it sits beside a choice and is read at a\n * glance while comparing two. NOT in the barrel: subpath-import it.\n */\nexport function DownloadCostBadge(props: DownloadCostBadgeProps) {\n const {\n bytes,\n files,\n approximate = false,\n loading = false,\n error = null,\n tooltip,\n loadingLabel = 'Measuring…',\n } = props\n let chip: ReactElement\n if (loading) {\n chip = (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n icon={<CircularProgress size=\"1em\" color=\"inherit\" />}\n label={loadingLabel}\n aria-busy\n />\n )\n } else if (error || bytes === null || bytes === undefined) {\n chip = (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color=\"warning\"\n icon={<MdiIcon path={mdiAlertCircleOutline.path} />}\n label=\"Size unavailable\"\n />\n )\n } else {\n chip = (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={bytes === 0 ? 'success' : 'default'}\n icon={<MdiIcon path={mdiTrayArrowDown.path} />}\n label={downloadCostLabel(bytes, files ?? 0, approximate)}\n />\n )\n }\n const title = error ?? tooltip\n if (!title) return <>{chip}</>\n return (\n <Tooltip title={title} describeChild>\n {chip}\n </Tooltip>\n )\n}\nDownloadCostBadge.displayName = 'DownloadCostBadge'\n\nexport default DownloadCostBadge\n"],"names":["mdiAlertCircleOutline","mdiTrayArrowDown","Chip","CircularProgress","Tooltip","MdiIcon","formatDownloadSize","bytes","Number","isFinite","toFixed","Math","max","round","downloadCostLabel","files","approximate","size","DownloadCostBadge","props","loading","error","tooltip","loadingLabel","chip","variant","icon","color","label","aria-busy","undefined","path","title","describeChild","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,qBAAqB,EAAEC,gBAAgB,QAAQ,yBAAwB;AAChF,SAASC,IAAI,EAAEC,gBAAgB,EAAEC,OAAO,QAAQ,gBAAe;AAE/D,OAAOC,aAAa,yBAAqB;AAEzC;;;CAGC,GACD,OAAO,SAASC,mBAAmBC,KAAa;IAC9C,IAAI,CAACC,OAAOC,QAAQ,CAACF,UAAUA,SAAS,GAAG,OAAO;IAClD,IAAIA,SAAS,OAAO,MAAM,OAAO,GAAG,AAACA,CAAAA,QAAS,CAAA,OAAO,IAAG,CAAC,EAAGG,OAAO,CAAC,GAAG,GAAG,CAAC;IAC3E,OAAO,GAAGC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACN,QAAQ,OAAO,GAAG,CAAC;AACtD;AAEA,uDAAuD,GACvD,OAAO,SAASO,kBACdP,KAAa,EACbQ,KAAa,EACbC,cAAc,KAAK;IAEnB,MAAMC,OAAO,GAAGD,cAAc,OAAO,KAAKV,mBAAmBC,QAAQ;IACrE,IAAI,CAACQ,OAAO,OAAOE;IACnB,OAAO,GAAGA,KAAK,GAAG,EAAEF,MAAM,CAAC,EAAEA,UAAU,IAAI,SAAS,SAAS;AAC/D;AAmBA;;;;;;;CAOC,GACD,OAAO,SAASG,kBAAkBC,KAA6B;IAC7D,MAAM,EACJZ,KAAK,EACLQ,KAAK,EACLC,cAAc,KAAK,EACnBI,UAAU,KAAK,EACfC,QAAQ,IAAI,EACZC,OAAO,EACPC,eAAe,YAAY,EAC5B,GAAGJ;IACJ,IAAIK;IACJ,IAAIJ,SAAS;QACXI,qBACE,KAACtB;YACCe,MAAK;YACLQ,SAAQ;YACRC,oBAAM,KAACvB;gBAAiBc,MAAK;gBAAMU,OAAM;;YACzCC,OAAOL;YACPM,WAAS;;IAGf,OAAO,IAAIR,SAASd,UAAU,QAAQA,UAAUuB,WAAW;QACzDN,qBACE,KAACtB;YACCe,MAAK;YACLQ,SAAQ;YACRE,OAAM;YACND,oBAAM,KAACrB;gBAAQ0B,MAAM/B,sBAAsB+B,IAAI;;YAC/CH,OAAM;;IAGZ,OAAO;QACLJ,qBACE,KAACtB;YACCe,MAAK;YACLQ,SAAQ;YACRE,OAAOpB,UAAU,IAAI,YAAY;YACjCmB,oBAAM,KAACrB;gBAAQ0B,MAAM9B,iBAAiB8B,IAAI;;YAC1CH,OAAOd,kBAAkBP,OAAOQ,gBAAAA,QAAS,GAAGC;;IAGlD;IACA,MAAMgB,QAAQX,gBAAAA,QAASC;IACvB,IAAI,CAACU,OAAO,qBAAO;kBAAGR;;IACtB,qBACE,KAACpB;QAAQ4B,OAAOA;QAAOC,aAAa;kBACjCT;;AAGP;AACAN,kBAAkBgB,WAAW,GAAG;AAEhC,eAAehB,kBAAiB"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type SxProps, type Theme } from '@mui/material';
18
+ import { type ReactNode } from 'react';
19
+ /**
20
+ * A place to drop files, which is also the button that opens the file
21
+ * chooser (AGL-3656). Keyboard and screen reader reachable as one button;
22
+ * the drag states are drawn from theme tokens, so it follows light and dark.
23
+ *
24
+ * It hands back the files and nothing else: what is accepted, how each one
25
+ * is checked and where it goes are the caller's, which is what lets a font
26
+ * installer, an importer and a media library share it.
27
+ */
28
+ export interface FileDropZoneProps {
29
+ /** Called with the files dropped or chosen, in order. */
30
+ onFiles: (files: File[]) => void;
31
+ /** The file input's `accept`; dropped files are filtered by it too. */
32
+ accept?: string;
33
+ multiple?: boolean;
34
+ disabled?: boolean;
35
+ /** The headline, e.g. "Drop font files here". */
36
+ label: ReactNode;
37
+ /** One line under it, e.g. the formats and the size limit. */
38
+ hint?: ReactNode;
39
+ /** An mdi path for the icon above the label. */
40
+ icon?: string;
41
+ /** Called with the files `accept` refused, so the caller can say why. */
42
+ onRejected?: (files: File[]) => void;
43
+ sx?: SxProps<Theme>;
44
+ }
45
+ /** Whether a file matches an `accept` list of types, wildcards and extensions. */
46
+ export declare function fileMatchesAccept(file: Pick<File, 'name' | 'type'>, accept: string | undefined): boolean;
47
+ export declare function FileDropZone(props: FileDropZoneProps): import("react").JSX.Element;
48
+ export default FileDropZone;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { mdiTrayArrowUp } from "@mdi/js";
19
+ import { Box, ButtonBase, Typography } from "@mui/material";
20
+ import { useCallback, useId, useRef, useState } from "react";
21
+ import { MdiIcon } from "./mdi-icon/index.js";
22
+ /** Whether a file matches an `accept` list of types, wildcards and extensions. */ export function fileMatchesAccept(file, accept) {
23
+ if (!accept) return true;
24
+ const name = file.name.toLowerCase();
25
+ const type = (file.type || '').toLowerCase();
26
+ return accept.split(',').map((entry)=>entry.trim().toLowerCase()).filter(Boolean).some((entry)=>{
27
+ if (entry.startsWith('.')) return name.endsWith(entry);
28
+ if (entry.endsWith('/*')) return type.startsWith(entry.slice(0, -1));
29
+ return type === entry;
30
+ });
31
+ }
32
+ export function FileDropZone(props) {
33
+ const { onFiles, accept, multiple = true, disabled, label, hint, icon = mdiTrayArrowUp, onRejected, sx } = props;
34
+ const input = useRef(null);
35
+ const hintId = useId();
36
+ const [dragging, setDragging] = useState(false);
37
+ // Child elements fire their own enter/leave; counting keeps the highlight
38
+ // steady while the pointer crosses them.
39
+ const depth = useRef(0);
40
+ const deliver = useCallback((list)=>{
41
+ const files = Array.from(list != null ? list : []);
42
+ if (!files.length) return;
43
+ const accepted = files.filter((file)=>fileMatchesAccept(file, accept));
44
+ const rejected = files.filter((file)=>!accepted.includes(file));
45
+ if (rejected.length) onRejected == null ? void 0 : onRejected(rejected);
46
+ const chosen = multiple ? accepted : accepted.slice(0, 1);
47
+ if (chosen.length) onFiles(chosen);
48
+ }, [
49
+ accept,
50
+ multiple,
51
+ onFiles,
52
+ onRejected
53
+ ]);
54
+ const onDragEnter = (event)=>{
55
+ event.preventDefault();
56
+ if (disabled) return;
57
+ depth.current += 1;
58
+ setDragging(true);
59
+ };
60
+ const onDragLeave = (event)=>{
61
+ event.preventDefault();
62
+ depth.current = Math.max(0, depth.current - 1);
63
+ if (!depth.current) setDragging(false);
64
+ };
65
+ const onDragOver = (event)=>{
66
+ event.preventDefault();
67
+ if (!disabled) event.dataTransfer.dropEffect = 'copy';
68
+ };
69
+ const onDrop = (event)=>{
70
+ event.preventDefault();
71
+ depth.current = 0;
72
+ setDragging(false);
73
+ if (!disabled) deliver(event.dataTransfer.files);
74
+ };
75
+ return /*#__PURE__*/ _jsxs(ButtonBase, {
76
+ component: "div",
77
+ role: "button",
78
+ "aria-describedby": hint ? hintId : undefined,
79
+ "aria-disabled": disabled || undefined,
80
+ disabled: disabled,
81
+ onClick: ()=>{
82
+ var _input_current;
83
+ return (_input_current = input.current) == null ? void 0 : _input_current.click();
84
+ },
85
+ onDragEnter: onDragEnter,
86
+ onDragLeave: onDragLeave,
87
+ onDragOver: onDragOver,
88
+ onDrop: onDrop,
89
+ "data-dragging": dragging || undefined,
90
+ sx: [
91
+ (theme)=>{
92
+ var _theme_vars;
93
+ const tokens = (_theme_vars = theme.vars) != null ? _theme_vars : theme;
94
+ return {
95
+ width: '100%',
96
+ display: 'flex',
97
+ flexDirection: 'column',
98
+ alignItems: 'center',
99
+ justifyContent: 'center',
100
+ gap: 0.5,
101
+ px: 2,
102
+ py: 3,
103
+ textAlign: 'center',
104
+ borderRadius: `${tokens.shape.borderRadius}px`,
105
+ border: `1px dashed ${tokens.palette.divider}`,
106
+ backgroundColor: 'transparent',
107
+ color: tokens.palette.text.secondary,
108
+ transition: theme.transitions.create([
109
+ 'border-color',
110
+ 'background-color',
111
+ 'color'
112
+ ], {
113
+ duration: theme.transitions.duration.shortest
114
+ }),
115
+ '&:hover, &.Mui-focusVisible': {
116
+ borderColor: tokens.palette.primary.main,
117
+ color: tokens.palette.text.primary
118
+ },
119
+ '&[data-dragging]': {
120
+ borderStyle: 'solid',
121
+ borderColor: tokens.palette.primary.main,
122
+ backgroundColor: theme.alpha(tokens.palette.primary.main, tokens.palette.action.hoverOpacity),
123
+ color: tokens.palette.primary.main
124
+ },
125
+ '&.Mui-disabled': {
126
+ opacity: tokens.palette.action.disabledOpacity
127
+ }
128
+ };
129
+ },
130
+ ...Array.isArray(sx) ? sx : [
131
+ sx
132
+ ]
133
+ ],
134
+ children: [
135
+ /*#__PURE__*/ _jsx(MdiIcon, {
136
+ path: icon,
137
+ fontSize: "medium",
138
+ color: "inherit"
139
+ }),
140
+ /*#__PURE__*/ _jsx(Typography, {
141
+ variant: "body2",
142
+ sx: {
143
+ fontWeight: 'fontWeightMedium',
144
+ color: 'inherit'
145
+ },
146
+ children: label
147
+ }),
148
+ hint ? /*#__PURE__*/ _jsx(Typography, {
149
+ id: hintId,
150
+ variant: "caption",
151
+ color: "text.secondary",
152
+ children: hint
153
+ }) : null,
154
+ /*#__PURE__*/ _jsx(Box, {
155
+ component: "input",
156
+ ref: input,
157
+ type: "file",
158
+ accept: accept,
159
+ multiple: multiple,
160
+ hidden: true,
161
+ tabIndex: -1,
162
+ onClick: (event)=>event.stopPropagation(),
163
+ onChange: (event)=>{
164
+ deliver(event.target.files);
165
+ event.target.value = '';
166
+ }
167
+ })
168
+ ]
169
+ });
170
+ }
171
+ export default FileDropZone;
172
+
173
+ //# sourceMappingURL=file-drop-zone.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/file-drop-zone.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { mdiTrayArrowUp } from '@mdi/js'\nimport { Box, ButtonBase, Typography, type SxProps, type Theme } from '@mui/material'\nimport { useCallback, useId, useRef, useState, type DragEvent, type ReactNode } from 'react'\nimport { MdiIcon } from './mdi-icon'\n\n/**\n * A place to drop files, which is also the button that opens the file\n * chooser (AGL-3656). Keyboard and screen reader reachable as one button;\n * the drag states are drawn from theme tokens, so it follows light and dark.\n *\n * It hands back the files and nothing else: what is accepted, how each one\n * is checked and where it goes are the caller's, which is what lets a font\n * installer, an importer and a media library share it.\n */\nexport interface FileDropZoneProps {\n /** Called with the files dropped or chosen, in order. */\n onFiles: (files: File[]) => void\n /** The file input's `accept`; dropped files are filtered by it too. */\n accept?: string\n multiple?: boolean\n disabled?: boolean\n /** The headline, e.g. \"Drop font files here\". */\n label: ReactNode\n /** One line under it, e.g. the formats and the size limit. */\n hint?: ReactNode\n /** An mdi path for the icon above the label. */\n icon?: string\n /** Called with the files `accept` refused, so the caller can say why. */\n onRejected?: (files: File[]) => void\n sx?: SxProps<Theme>\n}\n\n/** Whether a file matches an `accept` list of types, wildcards and extensions. */\nexport function fileMatchesAccept(file: Pick<File, 'name' | 'type'>, accept: string | undefined): boolean {\n if (!accept) return true\n const name = file.name.toLowerCase()\n const type = (file.type || '').toLowerCase()\n return accept\n .split(',')\n .map((entry) => entry.trim().toLowerCase())\n .filter(Boolean)\n .some((entry) => {\n if (entry.startsWith('.')) return name.endsWith(entry)\n if (entry.endsWith('/*')) return type.startsWith(entry.slice(0, -1))\n return type === entry\n })\n}\n\nexport function FileDropZone(props: FileDropZoneProps) {\n const { onFiles, accept, multiple = true, disabled, label, hint, icon = mdiTrayArrowUp, onRejected, sx } = props\n const input = useRef<HTMLInputElement>(null)\n const hintId = useId()\n const [dragging, setDragging] = useState(false)\n // Child elements fire their own enter/leave; counting keeps the highlight\n // steady while the pointer crosses them.\n const depth = useRef(0)\n\n const deliver = useCallback(\n (list: FileList | null) => {\n const files = Array.from(list ?? [])\n if (!files.length) return\n const accepted = files.filter((file) => fileMatchesAccept(file, accept))\n const rejected = files.filter((file) => !accepted.includes(file))\n if (rejected.length) onRejected?.(rejected)\n const chosen = multiple ? accepted : accepted.slice(0, 1)\n if (chosen.length) onFiles(chosen)\n },\n [accept, multiple, onFiles, onRejected],\n )\n\n const onDragEnter = (event: DragEvent) => {\n event.preventDefault()\n if (disabled) return\n depth.current += 1\n setDragging(true)\n }\n const onDragLeave = (event: DragEvent) => {\n event.preventDefault()\n depth.current = Math.max(0, depth.current - 1)\n if (!depth.current) setDragging(false)\n }\n const onDragOver = (event: DragEvent) => {\n event.preventDefault()\n if (!disabled) event.dataTransfer.dropEffect = 'copy'\n }\n const onDrop = (event: DragEvent) => {\n event.preventDefault()\n depth.current = 0\n setDragging(false)\n if (!disabled) deliver(event.dataTransfer.files)\n }\n\n return (\n <ButtonBase\n component=\"div\"\n role=\"button\"\n aria-describedby={hint ? hintId : undefined}\n aria-disabled={disabled || undefined}\n disabled={disabled}\n onClick={() => input.current?.click()}\n onDragEnter={onDragEnter}\n onDragLeave={onDragLeave}\n onDragOver={onDragOver}\n onDrop={onDrop}\n data-dragging={dragging || undefined}\n sx={[\n (theme) => {\n const tokens = theme.vars ?? theme\n return {\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 0.5,\n px: 2,\n py: 3,\n textAlign: 'center',\n borderRadius: `${tokens.shape.borderRadius}px`,\n border: `1px dashed ${tokens.palette.divider}`,\n backgroundColor: 'transparent',\n color: tokens.palette.text.secondary,\n transition: theme.transitions.create(['border-color', 'background-color', 'color'], {\n duration: theme.transitions.duration.shortest,\n }),\n '&:hover, &.Mui-focusVisible': {\n borderColor: tokens.palette.primary.main,\n color: tokens.palette.text.primary,\n },\n '&[data-dragging]': {\n borderStyle: 'solid',\n borderColor: tokens.palette.primary.main,\n backgroundColor: theme.alpha(tokens.palette.primary.main, tokens.palette.action.hoverOpacity),\n color: tokens.palette.primary.main,\n },\n '&.Mui-disabled': { opacity: tokens.palette.action.disabledOpacity },\n }\n },\n ...(Array.isArray(sx) ? sx : [sx]),\n ]}\n >\n <MdiIcon path={icon} fontSize=\"medium\" color=\"inherit\" />\n <Typography variant=\"body2\" sx={{ fontWeight: 'fontWeightMedium', color: 'inherit' }}>\n {label}\n </Typography>\n {hint ? (\n <Typography id={hintId} variant=\"caption\" color=\"text.secondary\">\n {hint}\n </Typography>\n ) : null}\n <Box\n component=\"input\"\n ref={input}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n hidden\n tabIndex={-1}\n onClick={(event) => event.stopPropagation()}\n onChange={(event) => {\n deliver((event.target as HTMLInputElement).files)\n ;(event.target as HTMLInputElement).value = ''\n }}\n />\n </ButtonBase>\n )\n}\n\nexport default FileDropZone\n"],"names":["mdiTrayArrowUp","Box","ButtonBase","Typography","useCallback","useId","useRef","useState","MdiIcon","fileMatchesAccept","file","accept","name","toLowerCase","type","split","map","entry","trim","filter","Boolean","some","startsWith","endsWith","slice","FileDropZone","props","onFiles","multiple","disabled","label","hint","icon","onRejected","sx","input","hintId","dragging","setDragging","depth","deliver","list","files","Array","from","length","accepted","rejected","includes","chosen","onDragEnter","event","preventDefault","current","onDragLeave","Math","max","onDragOver","dataTransfer","dropEffect","onDrop","component","role","aria-describedby","undefined","aria-disabled","onClick","click","data-dragging","theme","tokens","vars","width","display","flexDirection","alignItems","justifyContent","gap","px","py","textAlign","borderRadius","shape","border","palette","divider","backgroundColor","color","text","secondary","transition","transitions","create","duration","shortest","borderColor","primary","main","borderStyle","alpha","action","hoverOpacity","opacity","disabledOpacity","isArray","path","fontSize","variant","fontWeight","id","ref","hidden","tabIndex","stopPropagation","onChange","target","value"],"mappings":"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"}
@@ -14,24 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import type { ListFilterField } from '../const/list-filter';
18
- import type { ListFilterClause, ListFilterOption } from '../const/list-grid-filter';
19
- /**
20
- * A plan's refusals as the notices read them: each refused clause by the
21
- * same sentence its chip reads ("Reason is Bounced"), and the search as
22
- * "Search". Every list maps `plan.refused` through this and nothing else.
23
- */
24
- export declare function listQueryRefusals(refused: ReadonlyArray<{
25
- clause: ListFilterClause | 'search';
26
- reason: string;
27
- }>, context: {
28
- fields: readonly ListFilterField[];
29
- headers?: Readonly<Record<string, string>>;
30
- options?: Readonly<Record<string, readonly ListFilterOption[]>>;
31
- }): Array<{
32
- label: string;
33
- reason: string;
34
- }>;
17
+ export { listQueryRefusals } from '@aglyn/shared-util-tools/list-query/list-query-refusals';
35
18
  export interface ListQueryNoticesProps {
36
19
  /**
37
20
  * What the list was asked and could not put on its query, each as the thing
@@ -16,17 +16,7 @@
16
16
  */ 'use client';
17
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
18
  import { Alert, Stack } from "@mui/material";
19
- import { listFilterClauseSentence } from "../const/list-filter-sentence.js";
20
- /**
21
- * A plan's refusals as the notices read them: each refused clause by the
22
- * same sentence its chip reads ("Reason is Bounced"), and the search as
23
- * "Search". Every list maps `plan.refused` through this and nothing else.
24
- */ export function listQueryRefusals(refused, context) {
25
- return refused.map((entry)=>({
26
- label: entry.clause === 'search' ? 'Search' : listFilterClauseSentence(entry.clause, context),
27
- reason: entry.reason
28
- }));
29
- }
19
+ export { listQueryRefusals } from "@aglyn/shared-util-tools/list-query/list-query-refusals";
30
20
  /**
31
21
  * WHAT THE QUERY DID NOT TAKE, said above the list (AGL-3321).
32
22
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/list-query-notices.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { Alert, Stack } from '@mui/material'\nimport type { ListFilterField } from '../const/list-filter'\nimport { listFilterClauseSentence } from '../const/list-filter-sentence'\nimport type { ListFilterClause, ListFilterOption } from '../const/list-grid-filter'\n\n/**\n * A plan's refusals as the notices read them: each refused clause by the\n * same sentence its chip reads (\"Reason is Bounced\"), and the search as\n * \"Search\". Every list maps `plan.refused` through this and nothing else.\n */\nexport function listQueryRefusals(\n refused: ReadonlyArray<{ clause: ListFilterClause | 'search'; reason: string }>,\n context: {\n fields: readonly ListFilterField[]\n headers?: Readonly<Record<string, string>>\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n },\n): Array<{ label: string; reason: string }> {\n return refused.map((entry) => ({\n label: entry.clause === 'search' ? 'Search' : listFilterClauseSentence(entry.clause, context),\n reason: entry.reason,\n }))\n}\n\nexport interface ListQueryNoticesProps {\n /**\n * What the list was asked and could not put on its query, each as the thing\n * asked (\"Tags contains vip\", \"Search\") and why.\n */\n refused: ReadonlyArray<{ label: string; reason: string }>\n /** Said about what WAS served, e.g. that search matched one word. */\n notices?: readonly string[]\n}\n\n/**\n * WHAT THE QUERY DID NOT TAKE, said above the list (AGL-3321).\n *\n * Every filter and search word a list applies is on its Firestore query, and\n * one query holds one array clause, one range and thirty disjunctions. A\n * combination past that is not applied at all rather than applied to the\n * rows that happen to be loaded — and a filter the reader set that is not\n * applied must say so, or the list reads as the answer to a question it did\n * not ask.\n */\nexport function ListQueryNotices(props: ListQueryNoticesProps) {\n const { refused, notices = [] } = props\n if (!refused.length && !notices.length) return null\n return (\n <Stack spacing={1} role=\"status\" aria-label=\"Filter notices\">\n {refused.map((entry) => (\n <Alert key={`${entry.label}:${entry.reason}`} severity=\"warning\">\n {`${entry.label} is not applied: ${entry.reason}.`}\n </Alert>\n ))}\n {notices.map((notice) => (\n <Alert key={notice} severity=\"info\">\n {notice}\n </Alert>\n ))}\n </Stack>\n )\n}\nListQueryNotices.displayName = 'ListQueryNotices'\n\nexport default ListQueryNotices\n"],"names":["Alert","Stack","listFilterClauseSentence","listQueryRefusals","refused","context","map","entry","label","clause","reason","ListQueryNotices","props","notices","length","spacing","role","aria-label","severity","notice","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,KAAK,EAAEC,KAAK,QAAQ,gBAAe;AAE5C,SAASC,wBAAwB,QAAQ,mCAA+B;AAGxE;;;;CAIC,GACD,OAAO,SAASC,kBACdC,OAA+E,EAC/EC,OAIC;IAED,OAAOD,QAAQE,GAAG,CAAC,CAACC,QAAW,CAAA;YAC7BC,OAAOD,MAAME,MAAM,KAAK,WAAW,WAAWP,yBAAyBK,MAAME,MAAM,EAAEJ;YACrFK,QAAQH,MAAMG,MAAM;QACtB,CAAA;AACF;AAYA;;;;;;;;;CASC,GACD,OAAO,SAASC,iBAAiBC,KAA4B;IAC3D,MAAM,EAAER,OAAO,EAAES,UAAU,EAAE,EAAE,GAAGD;IAClC,IAAI,CAACR,QAAQU,MAAM,IAAI,CAACD,QAAQC,MAAM,EAAE,OAAO;IAC/C,qBACE,MAACb;QAAMc,SAAS;QAAGC,MAAK;QAASC,cAAW;;YACzCb,QAAQE,GAAG,CAAC,CAACC,sBACZ,KAACP;oBAA6CkB,UAAS;8BACpD,GAAGX,MAAMC,KAAK,CAAC,iBAAiB,EAAED,MAAMG,MAAM,CAAC,CAAC,CAAC;mBADxC,GAAGH,MAAMC,KAAK,CAAC,CAAC,EAAED,MAAMG,MAAM,EAAE;YAI7CG,QAAQP,GAAG,CAAC,CAACa,uBACZ,KAACnB;oBAAmBkB,UAAS;8BAC1BC;mBADSA;;;AAMpB;AACAR,iBAAiBS,WAAW,GAAG;AAE/B,eAAeT,iBAAgB"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/list-query-notices.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { Alert, Stack } from '@mui/material'\n\nexport { listQueryRefusals } from '@aglyn/shared-util-tools/list-query/list-query-refusals'\n\nexport interface ListQueryNoticesProps {\n /**\n * What the list was asked and could not put on its query, each as the thing\n * asked (\"Tags contains vip\", \"Search\") and why.\n */\n refused: ReadonlyArray<{ label: string; reason: string }>\n /** Said about what WAS served, e.g. that search matched one word. */\n notices?: readonly string[]\n}\n\n/**\n * WHAT THE QUERY DID NOT TAKE, said above the list (AGL-3321).\n *\n * Every filter and search word a list applies is on its Firestore query, and\n * one query holds one array clause, one range and thirty disjunctions. A\n * combination past that is not applied at all rather than applied to the\n * rows that happen to be loaded — and a filter the reader set that is not\n * applied must say so, or the list reads as the answer to a question it did\n * not ask.\n */\nexport function ListQueryNotices(props: ListQueryNoticesProps) {\n const { refused, notices = [] } = props\n if (!refused.length && !notices.length) return null\n return (\n <Stack spacing={1} role=\"status\" aria-label=\"Filter notices\">\n {refused.map((entry) => (\n <Alert key={`${entry.label}:${entry.reason}`} severity=\"warning\">\n {`${entry.label} is not applied: ${entry.reason}.`}\n </Alert>\n ))}\n {notices.map((notice) => (\n <Alert key={notice} severity=\"info\">\n {notice}\n </Alert>\n ))}\n </Stack>\n )\n}\nListQueryNotices.displayName = 'ListQueryNotices'\n\nexport default ListQueryNotices\n"],"names":["Alert","Stack","listQueryRefusals","ListQueryNotices","props","refused","notices","length","spacing","role","aria-label","map","entry","severity","label","reason","notice","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,KAAK,EAAEC,KAAK,QAAQ,gBAAe;AAE5C,SAASC,iBAAiB,QAAQ,0DAAyD;AAY3F;;;;;;;;;CASC,GACD,OAAO,SAASC,iBAAiBC,KAA4B;IAC3D,MAAM,EAAEC,OAAO,EAAEC,UAAU,EAAE,EAAE,GAAGF;IAClC,IAAI,CAACC,QAAQE,MAAM,IAAI,CAACD,QAAQC,MAAM,EAAE,OAAO;IAC/C,qBACE,MAACN;QAAMO,SAAS;QAAGC,MAAK;QAASC,cAAW;;YACzCL,QAAQM,GAAG,CAAC,CAACC,sBACZ,KAACZ;oBAA6Ca,UAAS;8BACpD,GAAGD,MAAME,KAAK,CAAC,iBAAiB,EAAEF,MAAMG,MAAM,CAAC,CAAC,CAAC;mBADxC,GAAGH,MAAME,KAAK,CAAC,CAAC,EAAEF,MAAMG,MAAM,EAAE;YAI7CT,QAAQK,GAAG,CAAC,CAACK,uBACZ,KAAChB;oBAAmBa,UAAS;8BAC1BG;mBADSA;;;AAMpB;AACAb,iBAAiBc,WAAW,GAAG;AAE/B,eAAed,iBAAgB"}