@aglyn/shared-ui-jsx 1.0.0-beta.275 → 1.0.0-beta.282

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-ui-jsx",
3
- "version": "1.0.0-beta.275",
3
+ "version": "1.0.0-beta.282",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,10 +25,11 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-mdi": "1.0.0-beta.275",
29
- "@aglyn/shared-ui-theme": "1.0.0-beta.275",
30
- "@aglyn/shared-util-tools": "1.0.0-beta.275",
31
- "@aglyn/shared-util-vendor": "1.0.0-beta.275",
28
+ "@aglyn/shared-data-mdi": "1.0.0-beta.282",
29
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.282",
30
+ "@aglyn/shared-ui-theme": "1.0.0-beta.282",
31
+ "@aglyn/shared-util-tools": "1.0.0-beta.282",
32
+ "@aglyn/shared-util-vendor": "1.0.0-beta.282",
32
33
  "@mdi/js": "^7.4.47",
33
34
  "@swc/helpers": "0.5.23",
34
35
  "change-case": "^5.4.4",
@@ -0,0 +1,27 @@
1
+ import type { SxProps, Theme } from '@mui/material/styles';
2
+ /** What the chip reads. One word, the same on every surface that wears it. */
3
+ export declare const BETA_CHIP_LABEL = "Beta";
4
+ /**
5
+ * What Beta means to the reader, as the chip's hover and screen-reader
6
+ * description: the feature is open, not a preview, and it is still changing.
7
+ */
8
+ export declare const BETA_CHIP_DESCRIPTION = "Available on every plan that includes it, and still changing as we improve it.";
9
+ export interface BetaChipProps {
10
+ /** Spacing from what it sits beside; the chip itself takes no margin. */
11
+ sx?: SxProps<Theme>;
12
+ 'data-testid'?: string;
13
+ }
14
+ /**
15
+ * The Beta label for a released feature that is still changing (AGL-4052),
16
+ * beside its nav tab, its page heading or its panel title.
17
+ *
18
+ * Inline on purpose: the root is a `span` and so is the chip, because it sits
19
+ * inside a heading or a tab label. The description is attached with
20
+ * `describeChild`, so a tab or heading keeps "Beta" as part of its name
21
+ * rather than taking the sentence as its name.
22
+ */
23
+ export declare function BetaChip(props: BetaChipProps): import("react").JSX.Element;
24
+ export declare namespace BetaChip {
25
+ var displayName: string;
26
+ }
27
+ export default BetaChip;
@@ -0,0 +1,65 @@
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 Box from "@mui/material/Box";
19
+ import Tooltip from "@mui/material/Tooltip";
20
+ import { StatusChip } from "./status-chip.component.js";
21
+ /** What the chip reads. One word, the same on every surface that wears it. */ export const BETA_CHIP_LABEL = 'Beta';
22
+ /**
23
+ * What Beta means to the reader, as the chip's hover and screen-reader
24
+ * description: the feature is open, not a preview, and it is still changing.
25
+ */ export const BETA_CHIP_DESCRIPTION = 'Available on every plan that includes it, and still changing as we improve it.';
26
+ /**
27
+ * The Beta label for a released feature that is still changing (AGL-4052),
28
+ * beside its nav tab, its page heading or its panel title.
29
+ *
30
+ * Inline on purpose: the root is a `span` and so is the chip, because it sits
31
+ * inside a heading or a tab label. The description is attached with
32
+ * `describeChild`, so a tab or heading keeps "Beta" as part of its name
33
+ * rather than taking the sentence as its name.
34
+ */ export function BetaChip(props) {
35
+ const { sx } = props;
36
+ return /*#__PURE__*/ _jsx(Tooltip, {
37
+ title: BETA_CHIP_DESCRIPTION,
38
+ describeChild: true,
39
+ children: /*#__PURE__*/ _jsx(Box, {
40
+ component: "span",
41
+ "data-testid": props['data-testid'],
42
+ "data-release-stage": "beta",
43
+ sx: [
44
+ {
45
+ display: 'inline-flex',
46
+ alignItems: 'center',
47
+ flexShrink: 0
48
+ },
49
+ ...Array.isArray(sx) ? sx : [
50
+ sx
51
+ ]
52
+ ],
53
+ children: /*#__PURE__*/ _jsx(StatusChip, {
54
+ label: BETA_CHIP_LABEL,
55
+ tone: "info",
56
+ variant: "outlined",
57
+ component: "span"
58
+ })
59
+ })
60
+ });
61
+ }
62
+ BetaChip.displayName = 'BetaChip';
63
+ export default BetaChip;
64
+
65
+ //# sourceMappingURL=beta-chip.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/beta-chip.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 Box from '@mui/material/Box'\nimport type { SxProps, Theme } from '@mui/material/styles'\nimport Tooltip from '@mui/material/Tooltip'\nimport { StatusChip } from './status-chip.component'\n\n/** What the chip reads. One word, the same on every surface that wears it. */\nexport const BETA_CHIP_LABEL = 'Beta'\n\n/**\n * What Beta means to the reader, as the chip's hover and screen-reader\n * description: the feature is open, not a preview, and it is still changing.\n */\nexport const BETA_CHIP_DESCRIPTION =\n 'Available on every plan that includes it, and still changing as we improve it.'\n\nexport interface BetaChipProps {\n /** Spacing from what it sits beside; the chip itself takes no margin. */\n sx?: SxProps<Theme>\n 'data-testid'?: string\n}\n\n/**\n * The Beta label for a released feature that is still changing (AGL-4052),\n * beside its nav tab, its page heading or its panel title.\n *\n * Inline on purpose: the root is a `span` and so is the chip, because it sits\n * inside a heading or a tab label. The description is attached with\n * `describeChild`, so a tab or heading keeps \"Beta\" as part of its name\n * rather than taking the sentence as its name.\n */\nexport function BetaChip(props: BetaChipProps) {\n const { sx } = props\n return (\n <Tooltip title={BETA_CHIP_DESCRIPTION} describeChild>\n <Box\n component=\"span\"\n data-testid={props['data-testid']}\n data-release-stage=\"beta\"\n sx={[\n { display: 'inline-flex', alignItems: 'center', flexShrink: 0 },\n ...(Array.isArray(sx) ? sx : [sx]),\n ]}\n >\n <StatusChip\n label={BETA_CHIP_LABEL}\n tone=\"info\"\n variant=\"outlined\"\n component=\"span\"\n />\n </Box>\n </Tooltip>\n )\n}\nBetaChip.displayName = 'BetaChip'\n\nexport default BetaChip\n"],"names":["Box","Tooltip","StatusChip","BETA_CHIP_LABEL","BETA_CHIP_DESCRIPTION","BetaChip","props","sx","title","describeChild","component","data-testid","data-release-stage","display","alignItems","flexShrink","Array","isArray","label","tone","variant","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,OAAOA,SAAS,oBAAmB;AAEnC,OAAOC,aAAa,wBAAuB;AAC3C,SAASC,UAAU,QAAQ,6BAAyB;AAEpD,4EAA4E,GAC5E,OAAO,MAAMC,kBAAkB,OAAM;AAErC;;;CAGC,GACD,OAAO,MAAMC,wBACX,iFAAgF;AAQlF;;;;;;;;CAQC,GACD,OAAO,SAASC,SAASC,KAAoB;IAC3C,MAAM,EAAEC,EAAE,EAAE,GAAGD;IACf,qBACE,KAACL;QAAQO,OAAOJ;QAAuBK,aAAa;kBAClD,cAAA,KAACT;YACCU,WAAU;YACVC,eAAaL,KAAK,CAAC,cAAc;YACjCM,sBAAmB;YACnBL,IAAI;gBACF;oBAAEM,SAAS;oBAAeC,YAAY;oBAAUC,YAAY;gBAAE;mBAC1DC,MAAMC,OAAO,CAACV,MAAMA,KAAK;oBAACA;iBAAG;aAClC;sBAED,cAAA,KAACL;gBACCgB,OAAOf;gBACPgB,MAAK;gBACLC,SAAQ;gBACRV,WAAU;;;;AAKpB;AACAL,SAASgB,WAAW,GAAG;AAEvB,eAAehB,SAAQ"}
@@ -0,0 +1,72 @@
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 ReactNode } from 'react';
18
+ /**
19
+ * A grouped, searchable list of permissions with three states each (AGL-4047):
20
+ * inherit (the layer below decides), allow, or deny. Drawn by every editor of
21
+ * a permission map — a custom role, one member's overrides, one collaborator's
22
+ * per-site toggles — so they look and behave alike.
23
+ *
24
+ * Presentation only: the caller decides which keys are offered, how they are
25
+ * grouped, what a search keeps and what "inherit" resolves to, and hands the
26
+ * words in. NOT in the `@aglyn/shared-ui-jsx` barrel; subpath-import it
27
+ * (`@aglyn/shared-ui-jsx/components/permission-choice-list.component`).
28
+ */
29
+ export type PermissionChoiceValue = 'inherit' | 'grant' | 'deny';
30
+ export interface PermissionChoiceItem {
31
+ key: string;
32
+ label: string;
33
+ description?: string;
34
+ choice: PermissionChoiceValue;
35
+ /**
36
+ * The verdict the member ends up with and where it came from
37
+ * ("Allowed by the Editor role"), shown under the description.
38
+ */
39
+ effective?: {
40
+ granted: boolean;
41
+ source: string;
42
+ };
43
+ /** What inherit resolves to here, when there is one answer ("Editor default: allowed"). */
44
+ inheritHint?: string;
45
+ /** A reason the row cannot change, shown as the toggle's tooltip. */
46
+ lockedReason?: string;
47
+ }
48
+ export interface PermissionChoiceSection {
49
+ id: string;
50
+ label: string;
51
+ items: PermissionChoiceItem[];
52
+ }
53
+ export interface PermissionChoiceListProps {
54
+ sections: readonly PermissionChoiceSection[];
55
+ /** Sets one key (or, from a group heading, every key of the group) to a choice. */
56
+ onChange: (keys: string[], choice: PermissionChoiceValue) => void;
57
+ /** The search box; absent hides it. */
58
+ search?: string;
59
+ onSearchChange?: (value: string) => void;
60
+ disabled?: boolean;
61
+ /** The words for the three states. */
62
+ labels?: Partial<Record<PermissionChoiceValue, string>>;
63
+ /** Shown when a search keeps nothing. */
64
+ emptyMessage?: ReactNode;
65
+ /** Offer "set the whole group" on each heading. */
66
+ groupActions?: boolean;
67
+ }
68
+ export declare function PermissionChoiceList(props: PermissionChoiceListProps): import("react").JSX.Element;
69
+ export declare namespace PermissionChoiceList {
70
+ var displayName: string;
71
+ }
72
+ export default PermissionChoiceList;
@@ -0,0 +1,225 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ import { Box, Chip, Divider, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from "@mui/material";
19
+ import { useId } from "react";
20
+ const CHOICES = [
21
+ 'inherit',
22
+ 'grant',
23
+ 'deny'
24
+ ];
25
+ const DEFAULT_LABELS = {
26
+ inherit: 'Inherit',
27
+ grant: 'Allow',
28
+ deny: 'Deny'
29
+ };
30
+ function ChoiceToggle(props) {
31
+ return /*#__PURE__*/ _jsx(ToggleButtonGroup, {
32
+ size: "small",
33
+ exclusive: true,
34
+ value: props.value,
35
+ disabled: props.disabled,
36
+ "aria-label": props.ariaLabel,
37
+ onChange: (_event, next)=>{
38
+ if (next) props.onChange(next);
39
+ },
40
+ sx: {
41
+ flexShrink: 0
42
+ },
43
+ children: CHOICES.map((choice)=>/*#__PURE__*/ _jsx(ToggleButton, {
44
+ value: choice,
45
+ color: choice === 'grant' ? 'success' : choice === 'deny' ? 'error' : 'standard',
46
+ sx: {
47
+ px: 1.25,
48
+ py: 0.25,
49
+ textTransform: 'none'
50
+ },
51
+ children: props.labels[choice]
52
+ }, choice))
53
+ });
54
+ }
55
+ export function PermissionChoiceList(props) {
56
+ var _props_search, _props_emptyMessage;
57
+ const labels = _extends({}, DEFAULT_LABELS, props.labels);
58
+ const searchId = useId();
59
+ const visible = props.sections.filter((section)=>section.items.length > 0);
60
+ return /*#__PURE__*/ _jsxs(Stack, {
61
+ spacing: 2,
62
+ children: [
63
+ props.onSearchChange ? /*#__PURE__*/ _jsx(TextField, {
64
+ id: searchId,
65
+ size: "small",
66
+ type: "search",
67
+ label: "Search permissions",
68
+ value: (_props_search = props.search) != null ? _props_search : '',
69
+ onChange: (event)=>props.onSearchChange == null ? void 0 : props.onSearchChange.call(props, event.target.value)
70
+ }) : null,
71
+ visible.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
72
+ variant: "body2",
73
+ color: "text.secondary",
74
+ children: (_props_emptyMessage = props.emptyMessage) != null ? _props_emptyMessage : 'No permission matches your search.'
75
+ }) : null,
76
+ visible.map((section)=>{
77
+ var _ref;
78
+ var _section_items_;
79
+ const granted = section.items.filter((item)=>item.choice === 'grant').length;
80
+ const denied = section.items.filter((item)=>item.choice === 'deny').length;
81
+ const shared = section.items.every((item)=>{
82
+ var _section_items_;
83
+ return item.choice === ((_section_items_ = section.items[0]) == null ? void 0 : _section_items_.choice);
84
+ }) ? (_ref = (_section_items_ = section.items[0]) == null ? void 0 : _section_items_.choice) != null ? _ref : null : null;
85
+ const changeable = section.items.filter((item)=>!item.lockedReason).map((item)=>item.key);
86
+ return /*#__PURE__*/ _jsxs(Box, {
87
+ component: "section",
88
+ "aria-label": section.label,
89
+ children: [
90
+ /*#__PURE__*/ _jsxs(Stack, {
91
+ direction: {
92
+ xs: 'column',
93
+ sm: 'row'
94
+ },
95
+ spacing: 1,
96
+ sx: {
97
+ alignItems: {
98
+ xs: 'flex-start',
99
+ sm: 'center'
100
+ },
101
+ justifyContent: 'space-between',
102
+ mb: 1
103
+ },
104
+ children: [
105
+ /*#__PURE__*/ _jsxs(Stack, {
106
+ direction: "row",
107
+ spacing: 1,
108
+ sx: {
109
+ alignItems: 'center',
110
+ flexWrap: 'wrap',
111
+ rowGap: 0.5
112
+ },
113
+ children: [
114
+ /*#__PURE__*/ _jsx(Typography, {
115
+ variant: "subtitle2",
116
+ component: "h3",
117
+ children: section.label
118
+ }),
119
+ granted ? /*#__PURE__*/ _jsx(Chip, {
120
+ size: "small",
121
+ color: "success",
122
+ variant: "outlined",
123
+ label: `${granted} allowed`
124
+ }) : null,
125
+ denied ? /*#__PURE__*/ _jsx(Chip, {
126
+ size: "small",
127
+ color: "error",
128
+ variant: "outlined",
129
+ label: `${denied} denied`
130
+ }) : null
131
+ ]
132
+ }),
133
+ props.groupActions && changeable.length > 1 ? /*#__PURE__*/ _jsx(ChoiceToggle, {
134
+ value: shared,
135
+ labels: labels,
136
+ disabled: props.disabled,
137
+ ariaLabel: `Set every ${section.label} permission`,
138
+ onChange: (choice)=>props.onChange(changeable, choice)
139
+ }) : null
140
+ ]
141
+ }),
142
+ /*#__PURE__*/ _jsx(Stack, {
143
+ divider: /*#__PURE__*/ _jsx(Divider, {
144
+ flexItem: true
145
+ }),
146
+ sx: {
147
+ border: 1,
148
+ borderColor: 'divider',
149
+ borderRadius: 1
150
+ },
151
+ children: section.items.map((item)=>{
152
+ const toggle = /*#__PURE__*/ _jsx("span", {
153
+ children: /*#__PURE__*/ _jsx(ChoiceToggle, {
154
+ value: item.choice,
155
+ labels: labels,
156
+ disabled: props.disabled || Boolean(item.lockedReason),
157
+ ariaLabel: item.label,
158
+ onChange: (choice)=>props.onChange([
159
+ item.key
160
+ ], choice)
161
+ })
162
+ });
163
+ return /*#__PURE__*/ _jsxs(Stack, {
164
+ direction: {
165
+ xs: 'column',
166
+ sm: 'row'
167
+ },
168
+ spacing: 1.5,
169
+ sx: {
170
+ px: 1.5,
171
+ py: 1,
172
+ alignItems: {
173
+ xs: 'stretch',
174
+ sm: 'center'
175
+ },
176
+ justifyContent: 'space-between'
177
+ },
178
+ children: [
179
+ /*#__PURE__*/ _jsxs(Box, {
180
+ sx: {
181
+ minWidth: 0
182
+ },
183
+ children: [
184
+ /*#__PURE__*/ _jsx(Typography, {
185
+ variant: "body2",
186
+ children: item.label
187
+ }),
188
+ item.description ? /*#__PURE__*/ _jsx(Typography, {
189
+ variant: "caption",
190
+ color: "text.secondary",
191
+ component: "p",
192
+ children: item.description
193
+ }) : null,
194
+ item.effective ? /*#__PURE__*/ _jsx(Typography, {
195
+ variant: "caption",
196
+ component: "p",
197
+ color: item.effective.granted ? 'success.main' : 'text.secondary',
198
+ children: `${item.effective.granted ? 'Allowed' : 'Not allowed'} · ${item.effective.source}`
199
+ }) : null,
200
+ item.choice === 'inherit' && item.inheritHint ? /*#__PURE__*/ _jsx(Typography, {
201
+ variant: "caption",
202
+ color: "text.secondary",
203
+ component: "p",
204
+ children: item.inheritHint
205
+ }) : null
206
+ ]
207
+ }),
208
+ item.lockedReason ? /*#__PURE__*/ _jsx(Tooltip, {
209
+ title: item.lockedReason,
210
+ children: toggle
211
+ }) : toggle
212
+ ]
213
+ }, item.key);
214
+ })
215
+ })
216
+ ]
217
+ }, section.id);
218
+ })
219
+ ]
220
+ });
221
+ }
222
+ PermissionChoiceList.displayName = 'PermissionChoiceList';
223
+ export default PermissionChoiceList;
224
+
225
+ //# sourceMappingURL=permission-choice-list.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/permission-choice-list.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\nimport {\n Box,\n Chip,\n Divider,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { useId, type ReactNode } from 'react'\n\n/**\n * A grouped, searchable list of permissions with three states each (AGL-4047):\n * inherit (the layer below decides), allow, or deny. Drawn by every editor of\n * a permission map — a custom role, one member's overrides, one collaborator's\n * per-site toggles — so they look and behave alike.\n *\n * Presentation only: the caller decides which keys are offered, how they are\n * grouped, what a search keeps and what \"inherit\" resolves to, and hands the\n * words in. NOT in the `@aglyn/shared-ui-jsx` barrel; subpath-import it\n * (`@aglyn/shared-ui-jsx/components/permission-choice-list.component`).\n */\n\nexport type PermissionChoiceValue = 'inherit' | 'grant' | 'deny'\n\nexport interface PermissionChoiceItem {\n key: string\n label: string\n description?: string\n choice: PermissionChoiceValue\n /**\n * The verdict the member ends up with and where it came from\n * (\"Allowed by the Editor role\"), shown under the description.\n */\n effective?: { granted: boolean; source: string }\n /** What inherit resolves to here, when there is one answer (\"Editor default: allowed\"). */\n inheritHint?: string\n /** A reason the row cannot change, shown as the toggle's tooltip. */\n lockedReason?: string\n}\n\nexport interface PermissionChoiceSection {\n id: string\n label: string\n items: PermissionChoiceItem[]\n}\n\nexport interface PermissionChoiceListProps {\n sections: readonly PermissionChoiceSection[]\n /** Sets one key (or, from a group heading, every key of the group) to a choice. */\n onChange: (keys: string[], choice: PermissionChoiceValue) => void\n /** The search box; absent hides it. */\n search?: string\n onSearchChange?: (value: string) => void\n disabled?: boolean\n /** The words for the three states. */\n labels?: Partial<Record<PermissionChoiceValue, string>>\n /** Shown when a search keeps nothing. */\n emptyMessage?: ReactNode\n /** Offer \"set the whole group\" on each heading. */\n groupActions?: boolean\n}\n\nconst CHOICES: readonly PermissionChoiceValue[] = ['inherit', 'grant', 'deny']\n\nconst DEFAULT_LABELS: Record<PermissionChoiceValue, string> = {\n inherit: 'Inherit',\n grant: 'Allow',\n deny: 'Deny',\n}\n\nfunction ChoiceToggle(props: {\n value: PermissionChoiceValue | null\n onChange: (choice: PermissionChoiceValue) => void\n disabled?: boolean\n labels: Record<PermissionChoiceValue, string>\n ariaLabel: string\n}) {\n return (\n <ToggleButtonGroup\n size=\"small\"\n exclusive\n value={props.value}\n disabled={props.disabled}\n aria-label={props.ariaLabel}\n onChange={(_event, next: PermissionChoiceValue | null) => {\n if (next) props.onChange(next)\n }}\n sx={{ flexShrink: 0 }}\n >\n {CHOICES.map((choice) => (\n <ToggleButton\n key={choice}\n value={choice}\n color={choice === 'grant' ? 'success' : choice === 'deny' ? 'error' : 'standard'}\n sx={{ px: 1.25, py: 0.25, textTransform: 'none' }}\n >\n {props.labels[choice]}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n )\n}\n\nexport function PermissionChoiceList(props: PermissionChoiceListProps) {\n const labels = { ...DEFAULT_LABELS, ...props.labels }\n const searchId = useId()\n const visible = props.sections.filter((section) => section.items.length > 0)\n return (\n <Stack spacing={2}>\n {props.onSearchChange ? (\n <TextField\n id={searchId}\n size=\"small\"\n type=\"search\"\n label=\"Search permissions\"\n value={props.search ?? ''}\n onChange={(event) => props.onSearchChange?.(event.target.value)}\n />\n ) : null}\n {visible.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.emptyMessage ?? 'No permission matches your search.'}\n </Typography>\n ) : null}\n {visible.map((section) => {\n const granted = section.items.filter((item) => item.choice === 'grant').length\n const denied = section.items.filter((item) => item.choice === 'deny').length\n const shared = section.items.every((item) => item.choice === section.items[0]?.choice)\n ? (section.items[0]?.choice ?? null)\n : null\n const changeable = section.items.filter((item) => !item.lockedReason).map((item) => item.key)\n return (\n <Box key={section.id} component=\"section\" aria-label={section.label}>\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ alignItems: { xs: 'flex-start', sm: 'center' }, justifyContent: 'space-between', mb: 1 }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Typography variant=\"subtitle2\" component=\"h3\">\n {section.label}\n </Typography>\n {granted ? <Chip size=\"small\" color=\"success\" variant=\"outlined\" label={`${granted} allowed`} /> : null}\n {denied ? <Chip size=\"small\" color=\"error\" variant=\"outlined\" label={`${denied} denied`} /> : null}\n </Stack>\n {props.groupActions && changeable.length > 1 ? (\n <ChoiceToggle\n value={shared}\n labels={labels}\n disabled={props.disabled}\n ariaLabel={`Set every ${section.label} permission`}\n onChange={(choice) => props.onChange(changeable, choice)}\n />\n ) : null}\n </Stack>\n <Stack divider={<Divider flexItem />} sx={{ border: 1, borderColor: 'divider', borderRadius: 1 }}>\n {section.items.map((item) => {\n const toggle = (\n <span>\n <ChoiceToggle\n value={item.choice}\n labels={labels}\n disabled={props.disabled || Boolean(item.lockedReason)}\n ariaLabel={item.label}\n onChange={(choice) => props.onChange([item.key], choice)}\n />\n </span>\n )\n return (\n <Stack\n key={item.key}\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1.5}\n sx={{ px: 1.5, py: 1, alignItems: { xs: 'stretch', sm: 'center' }, justifyContent: 'space-between' }}\n >\n <Box sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\">{item.label}</Typography>\n {item.description ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\">\n {item.description}\n </Typography>\n ) : null}\n {item.effective ? (\n <Typography\n variant=\"caption\"\n component=\"p\"\n color={item.effective.granted ? 'success.main' : 'text.secondary'}\n >\n {`${item.effective.granted ? 'Allowed' : 'Not allowed'} · ${item.effective.source}`}\n </Typography>\n ) : null}\n {item.choice === 'inherit' && item.inheritHint ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\">\n {item.inheritHint}\n </Typography>\n ) : null}\n </Box>\n {item.lockedReason ? <Tooltip title={item.lockedReason}>{toggle}</Tooltip> : toggle}\n </Stack>\n )\n })}\n </Stack>\n </Box>\n )\n })}\n </Stack>\n )\n}\nPermissionChoiceList.displayName = 'PermissionChoiceList'\n\nexport default PermissionChoiceList\n"],"names":["Box","Chip","Divider","Stack","TextField","ToggleButton","ToggleButtonGroup","Tooltip","Typography","useId","CHOICES","DEFAULT_LABELS","inherit","grant","deny","ChoiceToggle","props","size","exclusive","value","disabled","aria-label","ariaLabel","onChange","_event","next","sx","flexShrink","map","choice","color","px","py","textTransform","labels","PermissionChoiceList","searchId","visible","sections","filter","section","items","length","spacing","onSearchChange","id","type","label","search","event","target","variant","emptyMessage","granted","item","denied","shared","every","changeable","lockedReason","key","component","direction","xs","sm","alignItems","justifyContent","mb","flexWrap","rowGap","groupActions","divider","flexItem","border","borderColor","borderRadius","toggle","span","Boolean","minWidth","description","effective","source","inheritHint","title","displayName"],"mappings":"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"}
@@ -11,6 +11,11 @@ export interface StatusChipProps {
11
11
  icon?: ReactElement;
12
12
  /** Outlined for a quiet status beside other content; filled to stand out. */
13
13
  variant?: 'filled' | 'outlined';
14
+ /**
15
+ * The root element. `span` where the chip sits inside phrasing content,
16
+ * such as a heading or a tab label, where a `div` is not allowed.
17
+ */
18
+ component?: 'div' | 'span';
14
19
  'data-testid'?: string;
15
20
  }
16
21
  /**
@@ -30,13 +30,15 @@ const TONE_COLOR = {
30
30
  * per card, and the same state came out in different colors on different
31
31
  * cards; a tone names what the state means and the theme picks the color.
32
32
  */ export function StatusChip(props) {
33
- const { label, tone = 'neutral', icon, variant = 'filled' } = props;
33
+ const { label, tone = 'neutral', icon, variant = 'filled', component } = props;
34
34
  return /*#__PURE__*/ _jsx(Chip, _extends({
35
35
  size: "small",
36
36
  label: label,
37
37
  color: TONE_COLOR[tone],
38
38
  variant: variant
39
- }, icon ? {
39
+ }, component ? {
40
+ component
41
+ } : {}, icon ? {
40
42
  icon
41
43
  } : {}, {
42
44
  "data-testid": props['data-testid']
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/status-chip.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 Chip, { type ChipProps } from '@mui/material/Chip'\nimport type { ReactElement } from 'react'\n\n/**\n * What a status says about the thing it labels. The five tones are the\n * theme's own palette roles, so a status reads the same in every card.\n */\nexport type StatusTone = 'neutral' | 'info' | 'success' | 'warning' | 'error'\n\nconst TONE_COLOR: Readonly<Record<StatusTone, ChipProps['color']>> = {\n neutral: 'default',\n info: 'info',\n success: 'success',\n warning: 'warning',\n error: 'error',\n}\n\nexport interface StatusChipProps {\n label: string\n tone?: StatusTone\n /** An icon before the label, such as an `MdiIcon`. */\n icon?: ReactElement\n /** Outlined for a quiet status beside other content; filled to stand out. */\n variant?: 'filled' | 'outlined'\n 'data-testid'?: string\n}\n\n/**\n * A record's state as a small chip — On, Connected, Needs attention\n * (AGL-3637). Plugins mapped each status to a hand-picked MUI `Chip` color\n * per card, and the same state came out in different colors on different\n * cards; a tone names what the state means and the theme picks the color.\n */\nexport function StatusChip(props: StatusChipProps) {\n const { label, tone = 'neutral', icon, variant = 'filled' } = props\n return (\n <Chip\n size=\"small\"\n label={label}\n color={TONE_COLOR[tone]}\n variant={variant}\n {...(icon ? { icon } : {})}\n data-testid={props['data-testid']}\n />\n )\n}\nStatusChip.displayName = 'StatusChip'\n\nexport default StatusChip\n"],"names":["Chip","TONE_COLOR","neutral","info","success","warning","error","StatusChip","props","label","tone","icon","variant","size","color","data-testid","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,OAAOA,UAA8B,qBAAoB;AASzD,MAAMC,aAA+D;IACnEC,SAAS;IACTC,MAAM;IACNC,SAAS;IACTC,SAAS;IACTC,OAAO;AACT;AAYA;;;;;CAKC,GACD,OAAO,SAASC,WAAWC,KAAsB;IAC/C,MAAM,EAAEC,KAAK,EAAEC,OAAO,SAAS,EAAEC,IAAI,EAAEC,UAAU,QAAQ,EAAE,GAAGJ;IAC9D,qBACE,KAACR;QACCa,MAAK;QACLJ,OAAOA;QACPK,OAAOb,UAAU,CAACS,KAAK;QACvBE,SAASA;OACJD,OAAO;QAAEA;IAAK,IAAI,CAAC;QACxBI,eAAaP,KAAK,CAAC,cAAc;;AAGvC;AACAD,WAAWS,WAAW,GAAG;AAEzB,eAAeT,WAAU"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/status-chip.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 Chip, { type ChipProps } from '@mui/material/Chip'\nimport type { ReactElement } from 'react'\n\n/**\n * What a status says about the thing it labels. The five tones are the\n * theme's own palette roles, so a status reads the same in every card.\n */\nexport type StatusTone = 'neutral' | 'info' | 'success' | 'warning' | 'error'\n\nconst TONE_COLOR: Readonly<Record<StatusTone, ChipProps['color']>> = {\n neutral: 'default',\n info: 'info',\n success: 'success',\n warning: 'warning',\n error: 'error',\n}\n\nexport interface StatusChipProps {\n label: string\n tone?: StatusTone\n /** An icon before the label, such as an `MdiIcon`. */\n icon?: ReactElement\n /** Outlined for a quiet status beside other content; filled to stand out. */\n variant?: 'filled' | 'outlined'\n /**\n * The root element. `span` where the chip sits inside phrasing content,\n * such as a heading or a tab label, where a `div` is not allowed.\n */\n component?: 'div' | 'span'\n 'data-testid'?: string\n}\n\n/**\n * A record's state as a small chip — On, Connected, Needs attention\n * (AGL-3637). Plugins mapped each status to a hand-picked MUI `Chip` color\n * per card, and the same state came out in different colors on different\n * cards; a tone names what the state means and the theme picks the color.\n */\nexport function StatusChip(props: StatusChipProps) {\n const { label, tone = 'neutral', icon, variant = 'filled', component } = props\n return (\n <Chip\n size=\"small\"\n label={label}\n color={TONE_COLOR[tone]}\n variant={variant}\n {...(component ? { component } : {})}\n {...(icon ? { icon } : {})}\n data-testid={props['data-testid']}\n />\n )\n}\nStatusChip.displayName = 'StatusChip'\n\nexport default StatusChip\n"],"names":["Chip","TONE_COLOR","neutral","info","success","warning","error","StatusChip","props","label","tone","icon","variant","component","size","color","data-testid","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,OAAOA,UAA8B,qBAAoB;AASzD,MAAMC,aAA+D;IACnEC,SAAS;IACTC,MAAM;IACNC,SAAS;IACTC,SAAS;IACTC,OAAO;AACT;AAiBA;;;;;CAKC,GACD,OAAO,SAASC,WAAWC,KAAsB;IAC/C,MAAM,EAAEC,KAAK,EAAEC,OAAO,SAAS,EAAEC,IAAI,EAAEC,UAAU,QAAQ,EAAEC,SAAS,EAAE,GAAGL;IACzE,qBACE,KAACR;QACCc,MAAK;QACLL,OAAOA;QACPM,OAAOd,UAAU,CAACS,KAAK;QACvBE,SAASA;OACJC,YAAY;QAAEA;IAAU,IAAI,CAAC,GAC7BF,OAAO;QAAEA;IAAK,IAAI,CAAC;QACxBK,eAAaR,KAAK,CAAC,cAAc;;AAGvC;AACAD,WAAWU,WAAW,GAAG;AAEzB,eAAeV,WAAU"}
@@ -0,0 +1,55 @@
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
+ /**
18
+ * File ▸ Discard changes… for every besigner editor (AGL-3723).
19
+ *
20
+ * Puts the canvas back to the stored document — the published tree on the
21
+ * live version, the last save on any other — and drops the local and shared
22
+ * drafts, after a confirm because the reset cannot be undone.
23
+ * `useBesignerDocument().discardChanges` does the document half; the editor
24
+ * passes what only it owns: the co-editing mirror and its own "draft saved"
25
+ * flag.
26
+ */
27
+ export declare function useDiscardChangesMenuItem(options: {
28
+ noun: string;
29
+ /** Editing the version the sites serve, where the stored tree is live. */
30
+ live: boolean;
31
+ /** `useBesignerDocument().canDiscard`, plus any draft state the editor keeps. */
32
+ canDiscard: boolean;
33
+ discardChanges: () => boolean;
34
+ /** Clears what the editor owns: the mirror, a draft-saved flag. */
35
+ onDiscarded?: () => void;
36
+ }): {
37
+ onClick: () => Promise<void>;
38
+ ListItemTextProps: {
39
+ inset: boolean;
40
+ secondary?: undefined;
41
+ };
42
+ id: string;
43
+ children: string;
44
+ disabled: boolean;
45
+ } | {
46
+ onClick: () => Promise<void>;
47
+ ListItemTextProps: {
48
+ inset: boolean;
49
+ secondary: string;
50
+ };
51
+ id: string;
52
+ children: string;
53
+ disabled: boolean;
54
+ };
55
+ export default useDiscardChangesMenuItem;
@@ -0,0 +1,83 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { useConfirmationContext } from "../contexts/confirmation.context.js";
18
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
19
+ import { useCallback } from "react";
20
+ /**
21
+ * File ▸ Discard changes… for every besigner editor (AGL-3723).
22
+ *
23
+ * Puts the canvas back to the stored document — the published tree on the
24
+ * live version, the last save on any other — and drops the local and shared
25
+ * drafts, after a confirm because the reset cannot be undone.
26
+ * `useBesignerDocument().discardChanges` does the document half; the editor
27
+ * passes what only it owns: the co-editing mirror and its own "draft saved"
28
+ * flag.
29
+ */ export function useDiscardChangesMenuItem(options) {
30
+ const { noun, live, canDiscard, discardChanges, onDiscarded } = options;
31
+ const { confirm } = useConfirmationContext();
32
+ const { enqueueSnackbar } = useSnackbar();
33
+ const target = live ? 'the published version' : 'its last save';
34
+ const onClick = useCallback(async ()=>{
35
+ const confirmed = await confirm({
36
+ title: 'Discard changes?',
37
+ description: `This puts the ${noun} back to ${target} and deletes its unsaved ` + 'changes and saved draft. This cannot be undone.',
38
+ confirmationText: 'Discard',
39
+ confirmationButtonProps: {
40
+ color: 'error'
41
+ }
42
+ }).then(()=>true).catch(()=>false);
43
+ if (!confirmed) return;
44
+ if (!discardChanges()) {
45
+ enqueueSnackbar(`The ${noun} has not loaded yet — try again.`, {
46
+ variant: 'warning',
47
+ persist: false
48
+ });
49
+ return;
50
+ }
51
+ onDiscarded == null ? void 0 : onDiscarded();
52
+ enqueueSnackbar(`Changes discarded — back to ${target}.`, {
53
+ variant: 'success',
54
+ persist: false
55
+ });
56
+ }, [
57
+ confirm,
58
+ noun,
59
+ target,
60
+ discardChanges,
61
+ onDiscarded,
62
+ enqueueSnackbar
63
+ ]);
64
+ return _extends({
65
+ id: 'center-nav-file-discard',
66
+ children: 'Discard changes…',
67
+ disabled: !canDiscard
68
+ }, canDiscard ? {
69
+ ListItemTextProps: {
70
+ inset: true
71
+ }
72
+ } : {
73
+ ListItemTextProps: {
74
+ inset: true,
75
+ secondary: 'No changes to discard'
76
+ }
77
+ }, {
78
+ onClick
79
+ });
80
+ }
81
+ export default useDiscardChangesMenuItem;
82
+
83
+ //# sourceMappingURL=use-discard-changes-menu-item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-discard-changes-menu-item.ts"],"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\nimport { useConfirmationContext } from '../contexts/confirmation.context'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useCallback } from 'react'\n\n/**\n * File ▸ Discard changes… for every besigner editor (AGL-3723).\n *\n * Puts the canvas back to the stored document — the published tree on the\n * live version, the last save on any other — and drops the local and shared\n * drafts, after a confirm because the reset cannot be undone.\n * `useBesignerDocument().discardChanges` does the document half; the editor\n * passes what only it owns: the co-editing mirror and its own \"draft saved\"\n * flag.\n */\nexport function useDiscardChangesMenuItem(options: {\n noun: string\n /** Editing the version the sites serve, where the stored tree is live. */\n live: boolean\n /** `useBesignerDocument().canDiscard`, plus any draft state the editor keeps. */\n canDiscard: boolean\n discardChanges: () => boolean\n /** Clears what the editor owns: the mirror, a draft-saved flag. */\n onDiscarded?: () => void\n}) {\n const { noun, live, canDiscard, discardChanges, onDiscarded } = options\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n const target = live ? 'the published version' : 'its last save'\n\n const onClick = useCallback(async () => {\n const confirmed = await confirm({\n title: 'Discard changes?',\n description:\n `This puts the ${noun} back to ${target} and deletes its unsaved ` +\n 'changes and saved draft. This cannot be undone.',\n confirmationText: 'Discard',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n if (!discardChanges()) {\n enqueueSnackbar(`The ${noun} has not loaded yet — try again.`, {\n variant: 'warning',\n persist: false,\n })\n return\n }\n onDiscarded?.()\n enqueueSnackbar(`Changes discarded — back to ${target}.`, {\n variant: 'success',\n persist: false,\n })\n }, [confirm, noun, target, discardChanges, onDiscarded, enqueueSnackbar])\n\n return {\n id: 'center-nav-file-discard',\n children: 'Discard changes…',\n disabled: !canDiscard,\n // A disabled entry says why, like Save & publish does.\n ...(canDiscard\n ? { ListItemTextProps: { inset: true } }\n : {\n ListItemTextProps: { inset: true, secondary: 'No changes to discard' },\n }),\n onClick,\n }\n}\n\nexport default useDiscardChangesMenuItem\n"],"names":["useConfirmationContext","useSnackbar","useCallback","useDiscardChangesMenuItem","options","noun","live","canDiscard","discardChanges","onDiscarded","confirm","enqueueSnackbar","target","onClick","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","variant","persist","id","children","disabled","ListItemTextProps","inset","secondary"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,sCAAkC;AACzE,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SAASC,WAAW,QAAQ,QAAO;AAEnC;;;;;;;;;CASC,GACD,OAAO,SAASC,0BAA0BC,OASzC;IACC,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAEC,UAAU,EAAEC,cAAc,EAAEC,WAAW,EAAE,GAAGL;IAChE,MAAM,EAAEM,OAAO,EAAE,GAAGV;IACpB,MAAM,EAAEW,eAAe,EAAE,GAAGV;IAC5B,MAAMW,SAASN,OAAO,0BAA0B;IAEhD,MAAMO,UAAUX,YAAY;QAC1B,MAAMY,YAAY,MAAMJ,QAAQ;YAC9BK,OAAO;YACPC,aACE,CAAC,cAAc,EAAEX,KAAK,SAAS,EAAEO,OAAO,yBAAyB,CAAC,GAClE;YACFK,kBAAkB;YAClBC,yBAAyB;gBAAEC,OAAO;YAAQ;QAC5C,GACGC,IAAI,CAAC,IAAM,MACXC,KAAK,CAAC,IAAM;QACf,IAAI,CAACP,WAAW;QAChB,IAAI,CAACN,kBAAkB;YACrBG,gBAAgB,CAAC,IAAI,EAAEN,KAAK,gCAAgC,CAAC,EAAE;gBAC7DiB,SAAS;gBACTC,SAAS;YACX;YACA;QACF;QACAd,+BAAAA;QACAE,gBAAgB,CAAC,4BAA4B,EAAEC,OAAO,CAAC,CAAC,EAAE;YACxDU,SAAS;YACTC,SAAS;QACX;IACF,GAAG;QAACb;QAASL;QAAMO;QAAQJ;QAAgBC;QAAaE;KAAgB;IAExE,OAAO;QACLa,IAAI;QACJC,UAAU;QACVC,UAAU,CAACnB;OAEPA,aACA;QAAEoB,mBAAmB;YAAEC,OAAO;QAAK;IAAE,IACrC;QACED,mBAAmB;YAAEC,OAAO;YAAMC,WAAW;QAAwB;IACvE;QACJhB;;AAEJ;AAEA,eAAeV,0BAAyB"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Whether the browser currently believes it has a network connection
3
+ * (AGL-1056).
4
+ *
5
+ * The console is an authoring tool, and the failure it has to avoid is the
6
+ * silent one: edits that appear to apply, a save that never lands, and no
7
+ * indication that the network — rather than the app — is the reason. This is
8
+ * the signal the chrome renders from.
9
+ *
10
+ * `navigator.onLine` is a floor, not a guarantee. It answers "is there a
11
+ * network interface up", which is why it can read `true` on a captive portal
12
+ * or a dead uplink. It cannot produce a false OFFLINE, though, and that is the
13
+ * direction this is used in: when it says offline, the console is offline.
14
+ */
15
+ export declare function useOnlineStatus(): boolean;
16
+ export default useOnlineStatus;
@@ -0,0 +1,86 @@
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 { useSyncExternalStore } from "react";
18
+ /**
19
+ * Subscribe to every event that can change the answer.
20
+ *
21
+ * Module scope, not a closure built per render: `useSyncExternalStore`
22
+ * resubscribes whenever this identity changes, so an inline function would
23
+ * tear down and re-add the listeners on every render of every consumer.
24
+ *
25
+ * Symmetric by construction — the same three names with the same handler
26
+ * reference are removed as were added. That matters under StrictMode, which
27
+ * deliberately mounts, unmounts and remounts each effect: an asymmetric
28
+ * cleanup leaks a listener per mount and, worse, leaves a listener bound to a
29
+ * callback from a torn-down render.
30
+ *
31
+ * `visibilitychange` is in here because `online`/`offline` are not guaranteed
32
+ * to be delivered to a backgrounded or discarded tab — a laptop that slept on
33
+ * hotel wifi and woke on another network can miss the pair entirely. The
34
+ * snapshot below is a pure read, so an extra notification costs one
35
+ * `Object.is` comparison and React bails out; a missed one leaves the chrome
36
+ * asserting a connection state that stopped being true hours ago.
37
+ */ function subscribe(onStoreChange) {
38
+ window.addEventListener('online', onStoreChange);
39
+ window.addEventListener('offline', onStoreChange);
40
+ document.addEventListener('visibilitychange', onStoreChange);
41
+ return ()=>{
42
+ window.removeEventListener('online', onStoreChange);
43
+ window.removeEventListener('offline', onStoreChange);
44
+ document.removeEventListener('visibilitychange', onStoreChange);
45
+ };
46
+ }
47
+ /**
48
+ * The live client answer, read fresh on every mount rather than seeded from a
49
+ * default. A boolean is its own snapshot identity, so this never trips the
50
+ * "getSnapshot should be cached" loop that catches object-returning stores.
51
+ *
52
+ * Anything other than an explicit `false` counts as online. `navigator.onLine`
53
+ * is a positive assertion of disconnection only: a browser that does not
54
+ * implement it leaves it `undefined`, and reading that as "offline" would have
55
+ * the console announce a network failure it has no evidence for.
56
+ */ function getSnapshot() {
57
+ return typeof navigator === 'undefined' || navigator.onLine !== false;
58
+ }
59
+ /**
60
+ * There is no `navigator` on the server, and no way to know a client's
61
+ * connection before one exists — so the server renders the online branch, and
62
+ * React uses this same value for the hydration render. That is what keeps the
63
+ * markup identical on both sides: the offline state can only appear AFTER
64
+ * hydration, from a real reading, never as a flash of a state nobody measured.
65
+ */ function getServerSnapshot() {
66
+ return true;
67
+ }
68
+ /**
69
+ * Whether the browser currently believes it has a network connection
70
+ * (AGL-1056).
71
+ *
72
+ * The console is an authoring tool, and the failure it has to avoid is the
73
+ * silent one: edits that appear to apply, a save that never lands, and no
74
+ * indication that the network — rather than the app — is the reason. This is
75
+ * the signal the chrome renders from.
76
+ *
77
+ * `navigator.onLine` is a floor, not a guarantee. It answers "is there a
78
+ * network interface up", which is why it can read `true` on a captive portal
79
+ * or a dead uplink. It cannot produce a false OFFLINE, though, and that is the
80
+ * direction this is used in: when it says offline, the console is offline.
81
+ */ export function useOnlineStatus() {
82
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
83
+ }
84
+ export default useOnlineStatus;
85
+
86
+ //# sourceMappingURL=use-online-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-online-status.ts"],"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 { useSyncExternalStore } from 'react'\n\n/**\n * Subscribe to every event that can change the answer.\n *\n * Module scope, not a closure built per render: `useSyncExternalStore`\n * resubscribes whenever this identity changes, so an inline function would\n * tear down and re-add the listeners on every render of every consumer.\n *\n * Symmetric by construction — the same three names with the same handler\n * reference are removed as were added. That matters under StrictMode, which\n * deliberately mounts, unmounts and remounts each effect: an asymmetric\n * cleanup leaks a listener per mount and, worse, leaves a listener bound to a\n * callback from a torn-down render.\n *\n * `visibilitychange` is in here because `online`/`offline` are not guaranteed\n * to be delivered to a backgrounded or discarded tab — a laptop that slept on\n * hotel wifi and woke on another network can miss the pair entirely. The\n * snapshot below is a pure read, so an extra notification costs one\n * `Object.is` comparison and React bails out; a missed one leaves the chrome\n * asserting a connection state that stopped being true hours ago.\n */\nfunction subscribe(onStoreChange: () => void): () => void {\n window.addEventListener('online', onStoreChange)\n window.addEventListener('offline', onStoreChange)\n document.addEventListener('visibilitychange', onStoreChange)\n return () => {\n window.removeEventListener('online', onStoreChange)\n window.removeEventListener('offline', onStoreChange)\n document.removeEventListener('visibilitychange', onStoreChange)\n }\n}\n\n/**\n * The live client answer, read fresh on every mount rather than seeded from a\n * default. A boolean is its own snapshot identity, so this never trips the\n * \"getSnapshot should be cached\" loop that catches object-returning stores.\n *\n * Anything other than an explicit `false` counts as online. `navigator.onLine`\n * is a positive assertion of disconnection only: a browser that does not\n * implement it leaves it `undefined`, and reading that as \"offline\" would have\n * the console announce a network failure it has no evidence for.\n */\nfunction getSnapshot(): boolean {\n return typeof navigator === 'undefined' || navigator.onLine !== false\n}\n\n/**\n * There is no `navigator` on the server, and no way to know a client's\n * connection before one exists — so the server renders the online branch, and\n * React uses this same value for the hydration render. That is what keeps the\n * markup identical on both sides: the offline state can only appear AFTER\n * hydration, from a real reading, never as a flash of a state nobody measured.\n */\nfunction getServerSnapshot(): boolean {\n return true\n}\n\n/**\n * Whether the browser currently believes it has a network connection\n * (AGL-1056).\n *\n * The console is an authoring tool, and the failure it has to avoid is the\n * silent one: edits that appear to apply, a save that never lands, and no\n * indication that the network — rather than the app — is the reason. This is\n * the signal the chrome renders from.\n *\n * `navigator.onLine` is a floor, not a guarantee. It answers \"is there a\n * network interface up\", which is why it can read `true` on a captive portal\n * or a dead uplink. It cannot produce a false OFFLINE, though, and that is the\n * direction this is used in: when it says offline, the console is offline.\n */\nexport function useOnlineStatus(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n\nexport default useOnlineStatus\n"],"names":["useSyncExternalStore","subscribe","onStoreChange","window","addEventListener","document","removeEventListener","getSnapshot","navigator","onLine","getServerSnapshot","useOnlineStatus"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,oBAAoB,QAAQ,QAAO;AAE5C;;;;;;;;;;;;;;;;;;;CAmBC,GACD,SAASC,UAAUC,aAAyB;IAC1CC,OAAOC,gBAAgB,CAAC,UAAUF;IAClCC,OAAOC,gBAAgB,CAAC,WAAWF;IACnCG,SAASD,gBAAgB,CAAC,oBAAoBF;IAC9C,OAAO;QACLC,OAAOG,mBAAmB,CAAC,UAAUJ;QACrCC,OAAOG,mBAAmB,CAAC,WAAWJ;QACtCG,SAASC,mBAAmB,CAAC,oBAAoBJ;IACnD;AACF;AAEA;;;;;;;;;CASC,GACD,SAASK;IACP,OAAO,OAAOC,cAAc,eAAeA,UAAUC,MAAM,KAAK;AAClE;AAEA;;;;;;CAMC,GACD,SAASC;IACP,OAAO;AACT;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC;IACd,OAAOX,qBAAqBC,WAAWM,aAAaG;AACtD;AAEA,eAAeC,gBAAe"}