@aglyn/plugins-mui 1.0.0-beta.232 → 1.0.0-beta.233
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 +7 -7
- package/src/lib/components/breadcrumbs.js +7 -0
- package/src/lib/components/breadcrumbs.js.map +1 -1
- package/src/lib/components/collection-entries.js +8 -0
- package/src/lib/components/collection-entries.js.map +1 -1
- package/src/lib/components/stack.js +7 -2
- package/src/lib/components/stack.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-mui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.233",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.233",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.233",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.233",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.233",
|
|
32
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.233",
|
|
33
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.233",
|
|
34
34
|
"@swc/helpers": "0.5.23",
|
|
35
35
|
"mobx": "^7.0.5",
|
|
36
36
|
"mobx-react-lite": "^5.1.0"
|
|
@@ -77,6 +77,13 @@ export const schema = {
|
|
|
77
77
|
color: '#2196f3'
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
|
+
// MUI wraps each of `Children.toArray(children)` in its own `<li>` and puts
|
|
81
|
+
// a separator between them, so the renderer has to hand it one React child
|
|
82
|
+
// per crumb (AGL-3660). Wrapped in the single `<Branch>`, a trail of any
|
|
83
|
+
// length was ONE item: no separators, and `maxItems` never collapsed it.
|
|
84
|
+
flags: {
|
|
85
|
+
positionalChildren: Aglyn.FEATURE_FLAG.ENABLED
|
|
86
|
+
},
|
|
80
87
|
attributes: [
|
|
81
88
|
{
|
|
82
89
|
name: 'separator',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/breadcrumbs.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 * as Aglyn from '@aglyn/aglyn'\nimport { mdiSlashForward } from '@aglyn/shared-data-mdi'\nimport MuiBreadcrumbs from '@mui/material/Breadcrumbs'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'muiBreadcrumbs'\n\nexport interface BreadcrumbsElementProps {\n /** Text drawn between crumbs; defaults to `/`. */\n separator?: string\n /** Collapse into an ellipsis above this many crumbs. */\n maxItems?: number | string\n itemsBeforeCollapse?: number | string\n itemsAfterCollapse?: number | string\n /** Accessible name of the ellipsis button. */\n expandText?: string\n /** Accessible name of the navigation landmark. */\n ariaLabel?: string\n children?: ReactNode\n}\n\nfunction toCount(value: unknown): number | undefined {\n if (value == null || value === '') return undefined\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed < 1) return undefined\n return Math.round(parsed)\n}\n\n/**\n * Breadcrumb trail (https://mui.com/material-ui/react-breadcrumbs/).\n *\n * MUI renders a `nav` and inserts the separators between whatever\n * children it is given, so the crumbs themselves are ordinary Screen\n * Links — the trail stays rename-safe for free, and the last crumb can\n * be plain Typography for the current page.\n */\nconst BreadcrumbsElement = forwardRef<\n HTMLElement,\n BreadcrumbsElementProps\n>((props, ref) => {\n const {\n separator,\n maxItems,\n itemsBeforeCollapse,\n itemsAfterCollapse,\n expandText,\n ariaLabel,\n children,\n ...rest\n } = props\n return (\n <MuiBreadcrumbs\n ref={ref}\n // A blank separator would collapse the trail into run-on text.\n separator={separator || '/'}\n maxItems={toCount(maxItems)}\n itemsBeforeCollapse={toCount(itemsBeforeCollapse)}\n itemsAfterCollapse={toCount(itemsAfterCollapse)}\n expandText={expandText || undefined}\n aria-label={ariaLabel || 'Breadcrumb'}\n {...rest}\n >\n {children}\n </MuiBreadcrumbs>\n )\n})\nBreadcrumbsElement.displayName = 'AglynBreadcrumbs'\n\n/** The collapse tuning only matters once a max is set. */\nconst COLLAPSING = { when: 'maxItems', isNotEmpty: true }\n\nexport const schema: Aglyn.ComponentSchema<BreadcrumbsElementProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Breadcrumbs',\n description:\n 'Trail showing where a page sits. Fill it with Page Links so it ' +\n 'survives slug changes.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiSlashForward.path, sx: { color: '#2196f3' } },\n attributes: [\n {\n name: 'separator',\n label: 'Separator',\n description: 'Text drawn between crumbs, e.g. / or ›. Default /.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'maxItems',\n label: 'Collapse above',\n description:\n 'Collapse the middle of the trail into an ellipsis once there ' +\n 'are more crumbs than this. Blank never collapses.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'itemsBeforeCollapse',\n label: 'Keep at start',\n description: 'Crumbs kept before the ellipsis. Default 1.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n condition: COLLAPSING,\n },\n {\n name: 'itemsAfterCollapse',\n label: 'Keep at end',\n description: 'Crumbs kept after the ellipsis. Default 1.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n condition: COLLAPSING,\n },\n {\n name: 'expandText',\n label: 'Expand button label',\n description:\n 'Accessible name of the ellipsis button. Default \"Show path\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n condition: COLLAPSING,\n },\n {\n name: 'ariaLabel',\n label: 'Accessible label',\n description:\n 'Names the navigation landmark for screen readers. Default ' +\n '\"Breadcrumb\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\nconst crumb = (label: string) => ({\n $id: null,\n componentId: 'muiScreenLink',\n pluginId: BUNDLE_ID,\n // Text links, not buttons: a breadcrumb rendered as a button gets\n // button typography and the wrong role for assistive tech (AGL-1195).\n props: { children: label, renderAs: 'link', color: 'inherit' },\n})\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Breadcrumbs',\n pluginId: BUNDLE_ID,\n description: 'Two links and the current page',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: schema.icon,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n nodes: [\n crumb('Home'),\n crumb('Section'),\n {\n // The current page is not a link — linking a page to itself is\n // the classic breadcrumb accessibility mistake.\n $id: null,\n componentId: 'muiTypography',\n pluginId: BUNDLE_ID,\n props: {\n variant: 'body2',\n color: 'text.primary',\n children: 'Current page',\n },\n },\n ],\n },\n },\n]\n\nexport default BreadcrumbsElement\n"],"names":["Aglyn","mdiSlashForward","MuiBreadcrumbs","forwardRef","BUNDLE_ID","generatePresetId","ID","toCount","value","undefined","parsed","Number","isFinite","Math","round","BreadcrumbsElement","props","ref","separator","maxItems","itemsBeforeCollapse","itemsAfterCollapse","expandText","ariaLabel","children","rest","aria-label","displayName","COLLAPSING","when","isNotEmpty","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","sx","color","attributes","name","label","component","FieldComponentType","TEXT_FIELD","type","condition","crumb","componentId","renderAs","presets","NodeType","PRESET","data","nodes","variant"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,eAAe,QAAQ,yBAAwB;AACxD,OAAOC,oBAAoB,4BAA2B;AACtD,SAASC,UAAU,QAAwB,QAAO;AAClD,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,gBAAgB,QAAQ,iCAA6B;AAE9D,iEAAiE;AACjE,OAAO,MAAMC,KAAwB,iBAAgB;AAgBrD,SAASC,QAAQC,KAAc;IAC7B,IAAIA,SAAS,QAAQA,UAAU,IAAI,OAAOC;IAC1C,MAAMC,SAASC,OAAOH;IACtB,IAAI,CAACG,OAAOC,QAAQ,CAACF,WAAWA,SAAS,GAAG,OAAOD;IACnD,OAAOI,KAAKC,KAAK,CAACJ;AACpB;AAEA;;;;;;;CAOC,GACD,MAAMK,mCAAqBZ,WAGzB,CAACa,OAAOC;IACR,MAAM,EACJC,SAAS,EACTC,QAAQ,EACRC,mBAAmB,EACnBC,kBAAkB,EAClBC,UAAU,EACVC,SAAS,EACTC,QAAQ,EAET,GAAGR,OADCS,wCACDT;;;;;;;;;IACJ,qBACE,KAACd;QACCe,KAAKA;QACL,+DAA+D;QAC/DC,WAAWA,aAAa;QACxBC,UAAUZ,QAAQY;QAClBC,qBAAqBb,QAAQa;QAC7BC,oBAAoBd,QAAQc;QAC5BC,YAAYA,cAAcb;QAC1BiB,cAAYH,aAAa;OACrBE;kBAEHD;;AAGP;AACAT,mBAAmBY,WAAW,GAAG;AAEjC,wDAAwD,GACxD,MAAMC,aAAa;IAAEC,MAAM;IAAYC,YAAY;AAAK;AAExD,OAAO,MAAMC,SAAyD;IACpEC,KAAK1B;IACL2B,UAAU7B;IACVuB,aAAa;IACbO,aACE,oEACA;IACFC,UAAUnC,MAAMoC,iBAAiB,CAACC,UAAU;IAC5CC,MAAM;QAAEC,MAAMtC,gBAAgBsC,IAAI;QAAEC,IAAI;YAAEC,OAAO;QAAU;IAAE;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/breadcrumbs.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 * as Aglyn from '@aglyn/aglyn'\nimport { mdiSlashForward } from '@aglyn/shared-data-mdi'\nimport MuiBreadcrumbs from '@mui/material/Breadcrumbs'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'muiBreadcrumbs'\n\nexport interface BreadcrumbsElementProps {\n /** Text drawn between crumbs; defaults to `/`. */\n separator?: string\n /** Collapse into an ellipsis above this many crumbs. */\n maxItems?: number | string\n itemsBeforeCollapse?: number | string\n itemsAfterCollapse?: number | string\n /** Accessible name of the ellipsis button. */\n expandText?: string\n /** Accessible name of the navigation landmark. */\n ariaLabel?: string\n children?: ReactNode\n}\n\nfunction toCount(value: unknown): number | undefined {\n if (value == null || value === '') return undefined\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed < 1) return undefined\n return Math.round(parsed)\n}\n\n/**\n * Breadcrumb trail (https://mui.com/material-ui/react-breadcrumbs/).\n *\n * MUI renders a `nav` and inserts the separators between whatever\n * children it is given, so the crumbs themselves are ordinary Screen\n * Links — the trail stays rename-safe for free, and the last crumb can\n * be plain Typography for the current page.\n */\nconst BreadcrumbsElement = forwardRef<\n HTMLElement,\n BreadcrumbsElementProps\n>((props, ref) => {\n const {\n separator,\n maxItems,\n itemsBeforeCollapse,\n itemsAfterCollapse,\n expandText,\n ariaLabel,\n children,\n ...rest\n } = props\n return (\n <MuiBreadcrumbs\n ref={ref}\n // A blank separator would collapse the trail into run-on text.\n separator={separator || '/'}\n maxItems={toCount(maxItems)}\n itemsBeforeCollapse={toCount(itemsBeforeCollapse)}\n itemsAfterCollapse={toCount(itemsAfterCollapse)}\n expandText={expandText || undefined}\n aria-label={ariaLabel || 'Breadcrumb'}\n {...rest}\n >\n {children}\n </MuiBreadcrumbs>\n )\n})\nBreadcrumbsElement.displayName = 'AglynBreadcrumbs'\n\n/** The collapse tuning only matters once a max is set. */\nconst COLLAPSING = { when: 'maxItems', isNotEmpty: true }\n\nexport const schema: Aglyn.ComponentSchema<BreadcrumbsElementProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Breadcrumbs',\n description:\n 'Trail showing where a page sits. Fill it with Page Links so it ' +\n 'survives slug changes.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiSlashForward.path, sx: { color: '#2196f3' } },\n // MUI wraps each of `Children.toArray(children)` in its own `<li>` and puts\n // a separator between them, so the renderer has to hand it one React child\n // per crumb (AGL-3660). Wrapped in the single `<Branch>`, a trail of any\n // length was ONE item: no separators, and `maxItems` never collapsed it.\n flags: { positionalChildren: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'separator',\n label: 'Separator',\n description: 'Text drawn between crumbs, e.g. / or ›. Default /.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'maxItems',\n label: 'Collapse above',\n description:\n 'Collapse the middle of the trail into an ellipsis once there ' +\n 'are more crumbs than this. Blank never collapses.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'itemsBeforeCollapse',\n label: 'Keep at start',\n description: 'Crumbs kept before the ellipsis. Default 1.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n condition: COLLAPSING,\n },\n {\n name: 'itemsAfterCollapse',\n label: 'Keep at end',\n description: 'Crumbs kept after the ellipsis. Default 1.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n condition: COLLAPSING,\n },\n {\n name: 'expandText',\n label: 'Expand button label',\n description:\n 'Accessible name of the ellipsis button. Default \"Show path\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n condition: COLLAPSING,\n },\n {\n name: 'ariaLabel',\n label: 'Accessible label',\n description:\n 'Names the navigation landmark for screen readers. Default ' +\n '\"Breadcrumb\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n}\n\nconst crumb = (label: string) => ({\n $id: null,\n componentId: 'muiScreenLink',\n pluginId: BUNDLE_ID,\n // Text links, not buttons: a breadcrumb rendered as a button gets\n // button typography and the wrong role for assistive tech (AGL-1195).\n props: { children: label, renderAs: 'link', color: 'inherit' },\n})\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Breadcrumbs',\n pluginId: BUNDLE_ID,\n description: 'Two links and the current page',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: schema.icon,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n nodes: [\n crumb('Home'),\n crumb('Section'),\n {\n // The current page is not a link — linking a page to itself is\n // the classic breadcrumb accessibility mistake.\n $id: null,\n componentId: 'muiTypography',\n pluginId: BUNDLE_ID,\n props: {\n variant: 'body2',\n color: 'text.primary',\n children: 'Current page',\n },\n },\n ],\n },\n },\n]\n\nexport default BreadcrumbsElement\n"],"names":["Aglyn","mdiSlashForward","MuiBreadcrumbs","forwardRef","BUNDLE_ID","generatePresetId","ID","toCount","value","undefined","parsed","Number","isFinite","Math","round","BreadcrumbsElement","props","ref","separator","maxItems","itemsBeforeCollapse","itemsAfterCollapse","expandText","ariaLabel","children","rest","aria-label","displayName","COLLAPSING","when","isNotEmpty","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","sx","color","flags","positionalChildren","FEATURE_FLAG","ENABLED","attributes","name","label","component","FieldComponentType","TEXT_FIELD","type","condition","crumb","componentId","renderAs","presets","NodeType","PRESET","data","nodes","variant"],"mappings":"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"}
|
|
@@ -146,6 +146,14 @@ export const collectionEntriesSchema = {
|
|
|
146
146
|
color: 'secondary.main'
|
|
147
147
|
}
|
|
148
148
|
},
|
|
149
|
+
// Search divides `Children.toArray(children)` into one group per entry
|
|
150
|
+
// (AGL-1516), so the renderer has to hand it one React child per stamped
|
|
151
|
+
// node (AGL-3660). Wrapped in the single `<Branch>`, a block of N entries
|
|
152
|
+
// read as ONE child, never divided evenly, and failed open: no search
|
|
153
|
+
// field on the published page at all.
|
|
154
|
+
flags: {
|
|
155
|
+
positionalChildren: Aglyn.FEATURE_FLAG.ENABLED
|
|
156
|
+
},
|
|
149
157
|
attributes: [
|
|
150
158
|
{
|
|
151
159
|
name: 'collectionSlug',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-entries.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 * as Aglyn from '@aglyn/aglyn'\nimport { mdiPostOutline } from '@aglyn/shared-data-mdi'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport { forwardRef, use } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const ENTRIES_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_ENTRIES_COMPONENT_ID\n\nexport interface CollectionEntriesProps extends StackProps {\n /**\n * Collection to repeat over (compose-time, AGL-551). Blank = the\n * collection routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /** Maximum entries rendered (compose-time; blank = all, capped at 100). */\n entriesLimit?: number | string\n /**\n * Only entries in this category repeat (compose-time, AGL-582).\n * Matches the collection's category by stable id or display name.\n */\n filterCategory?: string\n /** Only entries carrying this tag repeat (compose-time, AGL-582). */\n filterTag?: string\n /**\n * Entries per page (compose-time, AGL-620). When set, the block renders one\n * page window instead of the top `entriesLimit`; the built-in collection\n * list uses this with the `page` from `/{collection}/page/{n}`.\n */\n perPage?: number | string\n /** 1-based page for `perPage` (compose-time, AGL-620). */\n page?: number | string\n /**\n * Render this block only on page 1 of the routed listing (compose-time,\n * AGL-1871). For a LEAD card — a block with no `perPage`, showing the top\n * of the set — every `/{collection}/page/{n}` past the first otherwise\n * repeats the identical entry above a page of different ones.\n *\n * Opt-in and default off: a block that legitimately belongs on every page\n * of a listing (a \"popular posts\" rail) has exactly the same shape.\n */\n firstPageOnly?: boolean\n /**\n * Show a search box that filters the RENDERED entries by title/excerpt as\n * the reader types (AGL-1516, Figma 494:1220). Opt-in and default off, so\n * every existing instance renders exactly as before.\n *\n * Client-evaluated over the entries this block already holds — the page is\n * ISR-cached, so a keystroke never costs a Firestore read. That set is\n * whatever the expansion left the block holding, which paging, an\n * `entriesLimit` or the 100-entry cap can each cut down. A truncated block\n * says so under its results either way — on a miss and on a hit\n * (AGL-1516, AGL-2569) — rather than pretending global search.\n */\n search?: boolean\n /**\n * What the search box DOES with a query (AGL-1525, Figma 496:1218).\n *\n * - `filter` (default, and the reading of an absent value) — hides the\n * non-matching cards in place. What AGL-1516 shipped.\n * - `suggest` — the card grid stays exactly as it was and a floating panel\n * of matching entries opens under the field, ending in a link to the\n * site-wide results page. The frame's toolbar behaviour: a reader\n * skimming the list is not made to lose it to a typo.\n */\n searchMode?: CollectionSearchMode\n /** Hint text inside the search box (blank = \"Search posts…\"). */\n searchPlaceholder?: string\n /**\n * Server-stamped matchable text per rendered entry\n * (`expandCollectionEntries`, AGL-1516); never set by hand.\n */\n searchIndex?: Aglyn.CollectionEntrySearchItem[]\n /**\n * How many entries `searchIndex` was drawn from — server-stamped by\n * `expandCollectionEntries` alongside it (AGL-1516), never set by hand.\n * `searchIndex.length < searchTotal` is the only honest test of whether\n * this block holds the whole set; `perPage` is not, because\n * `entriesLimit` and the 100-entry cap truncate too.\n */\n searchTotal?: number\n /**\n * Whether the read behind `searchTotal` reached its own bound — server-\n * stamped beside it (AGL-1516), never set by hand.\n *\n * `searchTotal` is a count of what the server SAW, and the collection read\n * is limited. Without this flag a block holding 100 of 400 posts satisfies\n * `searchIndex.length === searchTotal` and renders the one empty state that\n * claims to have looked everywhere.\n */\n searchCapped?: boolean\n}\n\n/** The two things the toolbar search box can do (AGL-1516/AGL-1525). */\nexport type CollectionSearchMode = 'filter' | 'suggest'\n\n/**\n * Author-facing choices for {@link CollectionEntriesProps.searchMode}.\n *\n * Both values are truthy on purpose (AGL-1453): `''` cannot survive a save,\n * so an author who tried the other mode would have no route back. \"Filter\"\n * carries the absent-value reading, which is what every block published\n * before AGL-1525 has.\n */\nexport const COLLECTION_SEARCH_MODE_OPTIONS: ReadonlyArray<{\n value: CollectionSearchMode\n label: string\n}> = [\n { value: 'filter', label: 'Filter the entries in place' },\n { value: 'suggest', label: 'Show a suggestions dropdown' },\n]\n\n/** The searching variant's module (AGL-3438). */\ntype EntriesSearchModule = typeof import('./collection-entries-search')\n\n/**\n * What a searching block stamps on its root. The browser looks for it before\n * the page hydrates (`prepareEntriesSearch`); nothing styles or targets it.\n */\nexport const ENTRIES_SEARCH_ATTRIBUTE = 'data-aglyn-entries-search'\n\n/**\n * React's thenable contract (`status`/`value`), stamped on the load once it\n * settles — the plugin loader's ensure promise carries it for the same reason\n * (AGL-1541): `use()` unwraps a stamped promise synchronously, and suspends on\n * a bare one even after it has resolved.\n */\ntype StampedLoad = Promise<EntriesSearchModule> & {\n status?: 'pending' | 'fulfilled' | 'rejected'\n value?: EntriesSearchModule\n reason?: unknown\n}\n\nlet searchLoad: StampedLoad | undefined\n\n/**\n * The searching variant, fetched once per visit however many blocks ask. A\n * failed fetch (a deploy mid-visit) is forgotten rather than cached, so the\n * next render that asks tries again.\n */\nexport const loadEntriesSearch = (): Promise<EntriesSearchModule> => {\n if (searchLoad) return searchLoad\n const load: StampedLoad = import('./collection-entries-search')\n searchLoad = load\n load.then(\n (module) => {\n load.status = 'fulfilled'\n load.value = module\n },\n (reason: unknown) => {\n load.status = 'rejected'\n load.reason = reason\n if (searchLoad === load) searchLoad = undefined\n },\n )\n return load\n}\n\n/**\n * Loads the searching variant BEFORE the element renders, wherever a render\n * that waited for it would cost something (AGL-3438). The plugin calls it as\n * the element registers (`prepare` in `plugin.ts`), and awaits it.\n *\n * - **On the server, always.** A render that suspended there would hold the\n * whole streamed shell for the fetch — and a first, cold one would do it\n * on a page the ISR cache then keeps.\n * - **Where the whole library is loading** (`everything`): the console and\n * the besigner, where an author can turn search on at any moment.\n * - **In a browser about to hydrate a searching block**, which the server\n * HTML says by carrying {@link ENTRIES_SEARCH_ATTRIBUTE}. The fetch then\n * rides along with the element's own instead of starting when hydration\n * reaches the block.\n *\n * Anywhere else nothing is fetched: a page whose blocks have search off —\n * most of them — never downloads the box. A searching block that renders\n * without it (a client-side navigation onto one) suspends until it lands,\n * which holds the navigation, never a half-drawn list.\n */\nexport const prepareEntriesSearch = (\n everything: boolean,\n): Promise<unknown> | undefined =>\n everything ||\n typeof document === 'undefined' ||\n document.querySelector(`[${ENTRIES_SEARCH_ATTRIBUTE}]`)\n ? loadEntriesSearch()\n : undefined\n\n/**\n * Repeats its children once per published entry of a content collection\n * (AGL-551) — the collections sibling of the dataset repeatable. The tenant\n * expands it at compose time with `{{entry.*}}` tokens; in the besigner the\n * template renders once with literal tokens, matching the repeatable UX.\n *\n * With `search` on (AGL-1516) the block is `SearchingEntries`, from its own\n * module (AGL-3438). Off — the default, and most blocks — it is the plain\n * stack below, and the search box's MUI input never reaches the page.\n *\n * There is NO Suspense boundary around the searching variant, on purpose. A\n * boundary would have to be in the server's tree too, and a client render\n * that suspended inside it would show its fallback: a list without its box,\n * then the box pushing it down. Without one, a render that has to wait holds\n * whatever is above it — the hydration of the page (whose server HTML stays\n * exactly as it is), or a navigation (whose old page stays up) — and\n * `prepareEntriesSearch` makes sure the published page almost never has to.\n */\nconst CollectionEntries = forwardRef<HTMLDivElement, CollectionEntriesProps>(\n (props, ref) => {\n if (props.search) {\n const { SearchingEntries } = use(loadEntriesSearch())\n return <SearchingEntries ref={ref} {...props} />\n }\n // collectionSlug/entriesLimit/filter*/search* are compose-time or\n // search-only attributes: strip so they never hit the DOM.\n const {\n collectionSlug,\n entriesLimit,\n filterCategory,\n filterTag,\n perPage,\n page,\n firstPageOnly,\n search,\n searchMode,\n searchPlaceholder,\n searchIndex,\n searchTotal,\n searchCapped,\n children,\n ...rest\n } = props\n return (\n <MuiStack ref={ref} spacing={4} {...rest}>\n {children}\n </MuiStack>\n )\n },\n)\nCollectionEntries.displayName = 'AglynCollectionEntries'\n\nexport const collectionEntriesSchema: Aglyn.ComponentSchema<CollectionEntriesProps> =\n {\n $id: ENTRIES_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Collection Entries',\n description: 'Repeats its children once per entry in a content collection.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPostOutline.path, sx: { color: 'secondary.main' } },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection whose published entries the children repeat ' +\n 'over (e.g. \"blog\"). Leave blank on a list-template page to use ' +\n 'the collection from the URL.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'entriesLimit',\n label: 'Entries limit',\n description: 'Maximum entries rendered (blank = all, capped at 100).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'filterCategory',\n label: 'Filter by category',\n description:\n 'Only entries in this category repeat — the category name or its ' +\n 'stable id both match (e.g. \"Guides\"). Blank = no category filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'filterTag',\n label: 'Filter by tag',\n description:\n 'Only entries carrying this tag repeat (e.g. \"nextjs\"). Blank = ' +\n 'no tag filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'perPage',\n label: 'Entries per page',\n description:\n 'Paginate the list: entries per page (blank = no pagination). ' +\n 'Pairs with the page from /{collection}/page/{n}.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'page',\n label: 'Page',\n description: '1-based page to render when Entries per page is set.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'firstPageOnly',\n label: 'Only on page 1',\n description:\n 'Show this block on the first page of the listing only. Use it ' +\n 'for a featured or lead card: without it the same entry repeats ' +\n 'at the top of every /page/{n}, above a page of different ones.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'search',\n label: 'Search',\n description:\n 'Show a search box that filters the rendered entries by title ' +\n 'and excerpt as the reader types. It searches the entries this ' +\n 'block rendered — whatever paging, an entry limit or the ' +\n '100-entry cap left it holding, and it says so on a miss.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'searchMode',\n label: 'When the reader types',\n description:\n 'Filter the entries in place, or leave the list alone and open a ' +\n 'dropdown of matching entries under the box — each one a link, ' +\n 'ending in \"View all results\" for a search across the whole site.',\n component: Aglyn.FieldComponentType.SELECT,\n options: COLLECTION_SEARCH_MODE_OPTIONS.map((option) => ({\n ...option,\n })),\n // Meaningless while there is no search box to type in.\n condition: { when: 'search', is: true },\n },\n {\n name: 'searchPlaceholder',\n label: 'Search placeholder',\n description:\n 'Hint text inside this block’s own search box — the one that ' +\n 'filters the entries below it as a reader types. Blank shows ' +\n '\"Search posts…\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n // Meaningless while there is no search box to hint in.\n condition: { when: 'search', is: true },\n },\n // `searchIndex` and `searchTotal` are deliberately NOT attributes: both\n // are server-stamped by expandCollectionEntries, like Category Pills'\n // `items` and Related Posts' `entries`.\n {\n name: 'spacing',\n label: 'Spacing',\n description: 'Defines the space/gap between entries.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n ],\n }\n\nexport { CollectionEntries }\n// The collection family's palette presets ride this element's registry entry\n// (`presets: 'collectionPresets'` in plugin.ts), as they did when the family\n// was one module.\nexport { collectionPresets } from './collection-presets'\n"],"names":["Aglyn","mdiPostOutline","MuiStack","forwardRef","use","BUNDLE_ID","ENTRIES_ID","COLLECTION_ENTRIES_COMPONENT_ID","COLLECTION_SEARCH_MODE_OPTIONS","value","label","ENTRIES_SEARCH_ATTRIBUTE","searchLoad","loadEntriesSearch","load","then","module","status","reason","undefined","prepareEntriesSearch","everything","document","querySelector","CollectionEntries","props","ref","search","SearchingEntries","collectionSlug","entriesLimit","filterCategory","filterTag","perPage","page","firstPageOnly","searchMode","searchPlaceholder","searchIndex","searchTotal","searchCapped","children","rest","spacing","displayName","collectionEntriesSchema","$id","pluginId","description","category","ComponentCategory","DATA_DISPLAY","icon","path","sx","color","attributes","name","component","FieldComponentType","TEXT_FIELD","type","SWITCH","SELECT","options","map","option","condition","when","is","collectionPresets"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,cAAc,QAAQ,yBAAwB;AACvD,OAAOC,cAAmC,sBAAqB;AAC/D,SAASC,UAAU,EAAEC,GAAG,QAAQ,QAAO;AACvC,SAASC,SAAS,QAAQ,gCAA4B;AAEtD,2EAA2E;AAC3E,mDAAmD;AACnD,OAAO,MAAMC,aACXN,MAAMO,+BAA+B,CAAA;AAyFvC;;;;;;;CAOC,GACD,OAAO,MAAMC,iCAGR;IACH;QAAEC,OAAO;QAAUC,OAAO;IAA8B;IACxD;QAAED,OAAO;QAAWC,OAAO;IAA8B;CAC1D,CAAA;AAKD;;;CAGC,GACD,OAAO,MAAMC,2BAA2B,4BAA2B;AAcnE,IAAIC;AAEJ;;;;CAIC,GACD,OAAO,MAAMC,oBAAoB;IAC/B,IAAID,YAAY,OAAOA;IACvB,MAAME,OAAoB,MAAM,CAAC;IACjCF,aAAaE;IACbA,KAAKC,IAAI,CACP,CAACC;QACCF,KAAKG,MAAM,GAAG;QACdH,KAAKL,KAAK,GAAGO;IACf,GACA,CAACE;QACCJ,KAAKG,MAAM,GAAG;QACdH,KAAKI,MAAM,GAAGA;QACd,IAAIN,eAAeE,MAAMF,aAAaO;IACxC;IAEF,OAAOL;AACT,EAAC;AAED;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAMM,uBAAuB,CAClCC,aAEAA,cACA,OAAOC,aAAa,eACpBA,SAASC,aAAa,CAAC,CAAC,CAAC,EAAEZ,yBAAyB,CAAC,CAAC,IAClDE,sBACAM,UAAS;AAEf;;;;;;;;;;;;;;;;;CAiBC,GACD,MAAMK,kCAAoBrB,WACxB,CAACsB,OAAOC;IACN,IAAID,MAAME,MAAM,EAAE;QAChB,MAAM,EAAEC,gBAAgB,EAAE,GAAGxB,IAAIS;QACjC,qBAAO,KAACe;YAAiBF,KAAKA;WAASD;IACzC;IACA,kEAAkE;IAClE,2DAA2D;IAC3D,MAAM,EACJI,cAAc,EACdC,YAAY,EACZC,cAAc,EACdC,SAAS,EACTC,OAAO,EACPC,IAAI,EACJC,aAAa,EACbR,MAAM,EACNS,UAAU,EACVC,iBAAiB,EACjBC,WAAW,EACXC,WAAW,EACXC,YAAY,EACZC,QAAQ,EAET,GAAGhB,OADCiB,wCACDjB;;;;;;;;;;;;;;;;IACJ,qBACE,KAACvB;QAASwB,KAAKA;QAAKiB,SAAS;OAAOD;kBACjCD;;AAGP;AAEFjB,kBAAkBoB,WAAW,GAAG;AAEhC,OAAO,MAAMC,0BACX;IACEC,KAAKxC;IACLyC,UAAU1C;IACVuC,aAAa;IACbI,aAAa;IACbC,UAAUjD,MAAMkD,iBAAiB,CAACC,YAAY;IAC9CC,MAAM;QAAEC,MAAMpD,eAAeoD,IAAI;QAAEC,IAAI;YAAEC,OAAO;QAAiB;IAAE;IACnEC,YAAY;QACV;YACEC,MAAM;YACN/C,OAAO;YACPsC,aACE,oEACA,oEACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;QAChD;QACA;YACEH,MAAM;YACN/C,OAAO;YACPsC,aAAa;YACbU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;YAC9CC,MAAM;QACR;QACA;YACEJ,MAAM;YACN/C,OAAO;YACPsC,aACE,qEACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;QAChD;QACA;YACEH,MAAM;YACN/C,OAAO;YACPsC,aACE,oEACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;QAChD;QACA;YACEH,MAAM;YACN/C,OAAO;YACPsC,aACE,kEACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;YAC9CC,MAAM;QACR;QACA;YACEJ,MAAM;YACN/C,OAAO;YACPsC,aAAa;YACbU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;YAC9CC,MAAM;QACR;QACA;YACEJ,MAAM;YACN/C,OAAO;YACPsC,aACE,mEACA,oEACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACG,MAAM;QAC5C;QACA;YACEL,MAAM;YACN/C,OAAO;YACPsC,aACE,kEACA,mEACA,6DACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACG,MAAM;QAC5C;QACA;YACEL,MAAM;YACN/C,OAAO;YACPsC,aACE,qEACA,mEACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACI,MAAM;YAC1CC,SAASxD,+BAA+ByD,GAAG,CAAC,CAACC,SAAY,aACpDA;YAEL,uDAAuD;YACvDC,WAAW;gBAAEC,MAAM;gBAAUC,IAAI;YAAK;QACxC;QACA;YACEZ,MAAM;YACN/C,OAAO;YACPsC,aACE,iEACA,iEACA;YACFU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;YAC9C,uDAAuD;YACvDO,WAAW;gBAAEC,MAAM;gBAAUC,IAAI;YAAK;QACxC;QACA,wEAAwE;QACxE,sEAAsE;QACtE,wCAAwC;QACxC;YACEZ,MAAM;YACN/C,OAAO;YACPsC,aAAa;YACbU,WAAW1D,MAAM2D,kBAAkB,CAACC,UAAU;YAC9CC,MAAM;QACR;KACD;AACH,EAAC;AAEH,SAASrC,iBAAiB,GAAE;AAC5B,6EAA6E;AAC7E,6EAA6E;AAC7E,kBAAkB;AAClB,SAAS8C,iBAAiB,QAAQ,0BAAsB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-entries.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 * as Aglyn from '@aglyn/aglyn'\nimport { mdiPostOutline } from '@aglyn/shared-data-mdi'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport { forwardRef, use } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const ENTRIES_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_ENTRIES_COMPONENT_ID\n\nexport interface CollectionEntriesProps extends StackProps {\n /**\n * Collection to repeat over (compose-time, AGL-551). Blank = the\n * collection routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /** Maximum entries rendered (compose-time; blank = all, capped at 100). */\n entriesLimit?: number | string\n /**\n * Only entries in this category repeat (compose-time, AGL-582).\n * Matches the collection's category by stable id or display name.\n */\n filterCategory?: string\n /** Only entries carrying this tag repeat (compose-time, AGL-582). */\n filterTag?: string\n /**\n * Entries per page (compose-time, AGL-620). When set, the block renders one\n * page window instead of the top `entriesLimit`; the built-in collection\n * list uses this with the `page` from `/{collection}/page/{n}`.\n */\n perPage?: number | string\n /** 1-based page for `perPage` (compose-time, AGL-620). */\n page?: number | string\n /**\n * Render this block only on page 1 of the routed listing (compose-time,\n * AGL-1871). For a LEAD card — a block with no `perPage`, showing the top\n * of the set — every `/{collection}/page/{n}` past the first otherwise\n * repeats the identical entry above a page of different ones.\n *\n * Opt-in and default off: a block that legitimately belongs on every page\n * of a listing (a \"popular posts\" rail) has exactly the same shape.\n */\n firstPageOnly?: boolean\n /**\n * Show a search box that filters the RENDERED entries by title/excerpt as\n * the reader types (AGL-1516, Figma 494:1220). Opt-in and default off, so\n * every existing instance renders exactly as before.\n *\n * Client-evaluated over the entries this block already holds — the page is\n * ISR-cached, so a keystroke never costs a Firestore read. That set is\n * whatever the expansion left the block holding, which paging, an\n * `entriesLimit` or the 100-entry cap can each cut down. A truncated block\n * says so under its results either way — on a miss and on a hit\n * (AGL-1516, AGL-2569) — rather than pretending global search.\n */\n search?: boolean\n /**\n * What the search box DOES with a query (AGL-1525, Figma 496:1218).\n *\n * - `filter` (default, and the reading of an absent value) — hides the\n * non-matching cards in place. What AGL-1516 shipped.\n * - `suggest` — the card grid stays exactly as it was and a floating panel\n * of matching entries opens under the field, ending in a link to the\n * site-wide results page. The frame's toolbar behaviour: a reader\n * skimming the list is not made to lose it to a typo.\n */\n searchMode?: CollectionSearchMode\n /** Hint text inside the search box (blank = \"Search posts…\"). */\n searchPlaceholder?: string\n /**\n * Server-stamped matchable text per rendered entry\n * (`expandCollectionEntries`, AGL-1516); never set by hand.\n */\n searchIndex?: Aglyn.CollectionEntrySearchItem[]\n /**\n * How many entries `searchIndex` was drawn from — server-stamped by\n * `expandCollectionEntries` alongside it (AGL-1516), never set by hand.\n * `searchIndex.length < searchTotal` is the only honest test of whether\n * this block holds the whole set; `perPage` is not, because\n * `entriesLimit` and the 100-entry cap truncate too.\n */\n searchTotal?: number\n /**\n * Whether the read behind `searchTotal` reached its own bound — server-\n * stamped beside it (AGL-1516), never set by hand.\n *\n * `searchTotal` is a count of what the server SAW, and the collection read\n * is limited. Without this flag a block holding 100 of 400 posts satisfies\n * `searchIndex.length === searchTotal` and renders the one empty state that\n * claims to have looked everywhere.\n */\n searchCapped?: boolean\n}\n\n/** The two things the toolbar search box can do (AGL-1516/AGL-1525). */\nexport type CollectionSearchMode = 'filter' | 'suggest'\n\n/**\n * Author-facing choices for {@link CollectionEntriesProps.searchMode}.\n *\n * Both values are truthy on purpose (AGL-1453): `''` cannot survive a save,\n * so an author who tried the other mode would have no route back. \"Filter\"\n * carries the absent-value reading, which is what every block published\n * before AGL-1525 has.\n */\nexport const COLLECTION_SEARCH_MODE_OPTIONS: ReadonlyArray<{\n value: CollectionSearchMode\n label: string\n}> = [\n { value: 'filter', label: 'Filter the entries in place' },\n { value: 'suggest', label: 'Show a suggestions dropdown' },\n]\n\n/** The searching variant's module (AGL-3438). */\ntype EntriesSearchModule = typeof import('./collection-entries-search')\n\n/**\n * What a searching block stamps on its root. The browser looks for it before\n * the page hydrates (`prepareEntriesSearch`); nothing styles or targets it.\n */\nexport const ENTRIES_SEARCH_ATTRIBUTE = 'data-aglyn-entries-search'\n\n/**\n * React's thenable contract (`status`/`value`), stamped on the load once it\n * settles — the plugin loader's ensure promise carries it for the same reason\n * (AGL-1541): `use()` unwraps a stamped promise synchronously, and suspends on\n * a bare one even after it has resolved.\n */\ntype StampedLoad = Promise<EntriesSearchModule> & {\n status?: 'pending' | 'fulfilled' | 'rejected'\n value?: EntriesSearchModule\n reason?: unknown\n}\n\nlet searchLoad: StampedLoad | undefined\n\n/**\n * The searching variant, fetched once per visit however many blocks ask. A\n * failed fetch (a deploy mid-visit) is forgotten rather than cached, so the\n * next render that asks tries again.\n */\nexport const loadEntriesSearch = (): Promise<EntriesSearchModule> => {\n if (searchLoad) return searchLoad\n const load: StampedLoad = import('./collection-entries-search')\n searchLoad = load\n load.then(\n (module) => {\n load.status = 'fulfilled'\n load.value = module\n },\n (reason: unknown) => {\n load.status = 'rejected'\n load.reason = reason\n if (searchLoad === load) searchLoad = undefined\n },\n )\n return load\n}\n\n/**\n * Loads the searching variant BEFORE the element renders, wherever a render\n * that waited for it would cost something (AGL-3438). The plugin calls it as\n * the element registers (`prepare` in `plugin.ts`), and awaits it.\n *\n * - **On the server, always.** A render that suspended there would hold the\n * whole streamed shell for the fetch — and a first, cold one would do it\n * on a page the ISR cache then keeps.\n * - **Where the whole library is loading** (`everything`): the console and\n * the besigner, where an author can turn search on at any moment.\n * - **In a browser about to hydrate a searching block**, which the server\n * HTML says by carrying {@link ENTRIES_SEARCH_ATTRIBUTE}. The fetch then\n * rides along with the element's own instead of starting when hydration\n * reaches the block.\n *\n * Anywhere else nothing is fetched: a page whose blocks have search off —\n * most of them — never downloads the box. A searching block that renders\n * without it (a client-side navigation onto one) suspends until it lands,\n * which holds the navigation, never a half-drawn list.\n */\nexport const prepareEntriesSearch = (\n everything: boolean,\n): Promise<unknown> | undefined =>\n everything ||\n typeof document === 'undefined' ||\n document.querySelector(`[${ENTRIES_SEARCH_ATTRIBUTE}]`)\n ? loadEntriesSearch()\n : undefined\n\n/**\n * Repeats its children once per published entry of a content collection\n * (AGL-551) — the collections sibling of the dataset repeatable. The tenant\n * expands it at compose time with `{{entry.*}}` tokens; in the besigner the\n * template renders once with literal tokens, matching the repeatable UX.\n *\n * With `search` on (AGL-1516) the block is `SearchingEntries`, from its own\n * module (AGL-3438). Off — the default, and most blocks — it is the plain\n * stack below, and the search box's MUI input never reaches the page.\n *\n * There is NO Suspense boundary around the searching variant, on purpose. A\n * boundary would have to be in the server's tree too, and a client render\n * that suspended inside it would show its fallback: a list without its box,\n * then the box pushing it down. Without one, a render that has to wait holds\n * whatever is above it — the hydration of the page (whose server HTML stays\n * exactly as it is), or a navigation (whose old page stays up) — and\n * `prepareEntriesSearch` makes sure the published page almost never has to.\n */\nconst CollectionEntries = forwardRef<HTMLDivElement, CollectionEntriesProps>(\n (props, ref) => {\n if (props.search) {\n const { SearchingEntries } = use(loadEntriesSearch())\n return <SearchingEntries ref={ref} {...props} />\n }\n // collectionSlug/entriesLimit/filter*/search* are compose-time or\n // search-only attributes: strip so they never hit the DOM.\n const {\n collectionSlug,\n entriesLimit,\n filterCategory,\n filterTag,\n perPage,\n page,\n firstPageOnly,\n search,\n searchMode,\n searchPlaceholder,\n searchIndex,\n searchTotal,\n searchCapped,\n children,\n ...rest\n } = props\n return (\n <MuiStack ref={ref} spacing={4} {...rest}>\n {children}\n </MuiStack>\n )\n },\n)\nCollectionEntries.displayName = 'AglynCollectionEntries'\n\nexport const collectionEntriesSchema: Aglyn.ComponentSchema<CollectionEntriesProps> =\n {\n $id: ENTRIES_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Collection Entries',\n description: 'Repeats its children once per entry in a content collection.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPostOutline.path, sx: { color: 'secondary.main' } },\n // Search divides `Children.toArray(children)` into one group per entry\n // (AGL-1516), so the renderer has to hand it one React child per stamped\n // node (AGL-3660). Wrapped in the single `<Branch>`, a block of N entries\n // read as ONE child, never divided evenly, and failed open: no search\n // field on the published page at all.\n flags: { positionalChildren: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection whose published entries the children repeat ' +\n 'over (e.g. \"blog\"). Leave blank on a list-template page to use ' +\n 'the collection from the URL.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'entriesLimit',\n label: 'Entries limit',\n description: 'Maximum entries rendered (blank = all, capped at 100).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'filterCategory',\n label: 'Filter by category',\n description:\n 'Only entries in this category repeat — the category name or its ' +\n 'stable id both match (e.g. \"Guides\"). Blank = no category filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'filterTag',\n label: 'Filter by tag',\n description:\n 'Only entries carrying this tag repeat (e.g. \"nextjs\"). Blank = ' +\n 'no tag filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'perPage',\n label: 'Entries per page',\n description:\n 'Paginate the list: entries per page (blank = no pagination). ' +\n 'Pairs with the page from /{collection}/page/{n}.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'page',\n label: 'Page',\n description: '1-based page to render when Entries per page is set.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'firstPageOnly',\n label: 'Only on page 1',\n description:\n 'Show this block on the first page of the listing only. Use it ' +\n 'for a featured or lead card: without it the same entry repeats ' +\n 'at the top of every /page/{n}, above a page of different ones.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'search',\n label: 'Search',\n description:\n 'Show a search box that filters the rendered entries by title ' +\n 'and excerpt as the reader types. It searches the entries this ' +\n 'block rendered — whatever paging, an entry limit or the ' +\n '100-entry cap left it holding, and it says so on a miss.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'searchMode',\n label: 'When the reader types',\n description:\n 'Filter the entries in place, or leave the list alone and open a ' +\n 'dropdown of matching entries under the box — each one a link, ' +\n 'ending in \"View all results\" for a search across the whole site.',\n component: Aglyn.FieldComponentType.SELECT,\n options: COLLECTION_SEARCH_MODE_OPTIONS.map((option) => ({\n ...option,\n })),\n // Meaningless while there is no search box to type in.\n condition: { when: 'search', is: true },\n },\n {\n name: 'searchPlaceholder',\n label: 'Search placeholder',\n description:\n 'Hint text inside this block’s own search box — the one that ' +\n 'filters the entries below it as a reader types. Blank shows ' +\n '\"Search posts…\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n // Meaningless while there is no search box to hint in.\n condition: { when: 'search', is: true },\n },\n // `searchIndex` and `searchTotal` are deliberately NOT attributes: both\n // are server-stamped by expandCollectionEntries, like Category Pills'\n // `items` and Related Posts' `entries`.\n {\n name: 'spacing',\n label: 'Spacing',\n description: 'Defines the space/gap between entries.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n ],\n }\n\nexport { CollectionEntries }\n// The collection family's palette presets ride this element's registry entry\n// (`presets: 'collectionPresets'` in plugin.ts), as they did when the family\n// was one module.\nexport { collectionPresets } from './collection-presets'\n"],"names":["Aglyn","mdiPostOutline","MuiStack","forwardRef","use","BUNDLE_ID","ENTRIES_ID","COLLECTION_ENTRIES_COMPONENT_ID","COLLECTION_SEARCH_MODE_OPTIONS","value","label","ENTRIES_SEARCH_ATTRIBUTE","searchLoad","loadEntriesSearch","load","then","module","status","reason","undefined","prepareEntriesSearch","everything","document","querySelector","CollectionEntries","props","ref","search","SearchingEntries","collectionSlug","entriesLimit","filterCategory","filterTag","perPage","page","firstPageOnly","searchMode","searchPlaceholder","searchIndex","searchTotal","searchCapped","children","rest","spacing","displayName","collectionEntriesSchema","$id","pluginId","description","category","ComponentCategory","DATA_DISPLAY","icon","path","sx","color","flags","positionalChildren","FEATURE_FLAG","ENABLED","attributes","name","component","FieldComponentType","TEXT_FIELD","type","SWITCH","SELECT","options","map","option","condition","when","is","collectionPresets"],"mappings":"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"}
|
|
@@ -136,9 +136,14 @@ export const schema = {
|
|
|
136
136
|
$id: ID,
|
|
137
137
|
pluginId: BUNDLE_ID,
|
|
138
138
|
displayName: 'Stack',
|
|
139
|
-
// Static: may sit in a subtree that keeps its server HTML (AGL-3581).
|
|
140
139
|
flags: {
|
|
141
|
-
|
|
140
|
+
// Static: may sit in a subtree that keeps its server HTML (AGL-3581).
|
|
141
|
+
lazyHydration: Aglyn.FEATURE_FLAG.ENABLED,
|
|
142
|
+
// MUI draws the divider between `Children.toArray(children)`, so the
|
|
143
|
+
// renderer has to hand it one React child per node child (AGL-3660).
|
|
144
|
+
// Wrapped in the single `<Branch>`, a Stack of any size read as ONE
|
|
145
|
+
// child and no divider was ever drawn, on the canvas or a live page.
|
|
146
|
+
positionalChildren: Aglyn.FEATURE_FLAG.ENABLED
|
|
142
147
|
},
|
|
143
148
|
description: 'Lays children out in a row or column with even spacing — the workhorse for most layouts.',
|
|
144
149
|
category: Aglyn.ComponentCategory.LAYOUT,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/stack.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 * as Aglyn from '@aglyn/aglyn'\nimport {\n mdiViewColumn,\n mdiViewSequential,\n} from '@aglyn/shared-data-mdi'\nimport MuiDivider from '@mui/material/Divider'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport type { CSSProperties, ReactElement } from 'react'\nimport { createElement, forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { colorSchemeAttribute } from '../utils/color-scheme-attribute'\nimport {\n applySemanticElement,\n semanticElementAttribute,\n semanticElementLabelAttribute,\n} from '../utils/element-picker'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/**\n * The dividers the `divider` attribute can name (AGL-2486).\n *\n * MUI's own `divider` prop takes a NODE, which an attributes panel cannot\n * author — so the persisted value is a style name and this file builds the\n * element. Anything else (an author's stray string, a paste) renders no\n * divider rather than the literal text between every pair of children.\n */\nconst DIVIDER_STYLES = {\n line: {},\n dashed: { borderStyle: 'dashed' },\n} as const\n\nexport type StackDivider = keyof typeof DIVIDER_STYLES\n\n// justifyContent/alignItems/flexWrap are not direct StackProps in MUI v6+;\n// pass them through sx.\ntype StackWithFlexProps = Omit<StackProps, 'divider'> & {\n justifyContent?: CSSProperties['justifyContent']\n alignItems?: CSSProperties['alignItems']\n flexWrap?: CSSProperties['flexWrap']\n /** Divider STYLE name, not a node — see {@link DIVIDER_STYLES}. */\n divider?: StackDivider | string\n /**\n * A repeat's directives (AGL-103, AGL-181). The published page's\n * composition consumes them before anything renders; a surface that renders\n * the stored node as written, like a component editor, still hands them over.\n */\n repeatDataset?: string\n repeatLimit?: number | string\n repeatFilter?: string\n repeatSort?: string\n}\n\n/**\n * A Stack's divider runs ACROSS the flow, so its orientation is the\n * opposite of the stack's own axis. A responsive `direction` (an array or\n * object) is not authorable here, so only the string form is inspected;\n * anything else falls back to MUI's default horizontal rule.\n */\nfunction buildDivider(\n divider: unknown,\n direction: unknown,\n): ReactElement | undefined {\n if (typeof divider !== 'string') return undefined\n // `hasOwnProperty`, not a truthiness test on the lookup: `\"constructor\"`\n // and friends resolve through Object.prototype and would sail past one.\n if (!Object.prototype.hasOwnProperty.call(DIVIDER_STYLES, divider)) {\n return undefined\n }\n const style = DIVIDER_STYLES[divider as StackDivider]\n const horizontal =\n typeof direction === 'string' && direction.startsWith('row')\n return createElement(MuiDivider, {\n orientation: horizontal ? 'vertical' : 'horizontal',\n // Without it a vertical divider collapses to zero height in a flex row.\n flexItem: horizontal,\n sx: style,\n })\n}\n\n/**\n * Displays that make a Stack the flex container MUI's `spacing` assumes.\n */\nconst FLEX_DISPLAYS = new Set(['flex', 'inline-flex'])\n\n/**\n * Whether an author's `sx` takes the Stack out of flex at any breakpoint\n * (AGL-3214).\n *\n * A function `sx` cannot be read without a theme, so it counts as flex and\n * the stack keeps MUI's own behaviour.\n */\nfunction hasNonFlexDisplay(sx: unknown): boolean {\n if (Array.isArray(sx)) return sx.some(hasNonFlexDisplay)\n if (!sx || typeof sx !== 'object') return false\n const display = (sx as Record<string, unknown>).display\n const notFlex = (value: unknown) =>\n typeof value === 'string' && !FLEX_DISPLAYS.has(value)\n if (typeof display === 'string') return notFlex(display)\n // Responsive, in either of the forms the styles panel writes: an array of\n // breakpoint values, or a `{ xs, md }` map.\n if (Array.isArray(display)) return display.some(notFlex)\n if (display && typeof display === 'object')\n return Object.values(display).some(notFlex)\n return false\n}\n\nconst Stack = forwardRef<HTMLDivElement, StackWithFlexProps>(\n // repeatDataset/repeatLimit are compose-time attributes (AGL-103): the\n // tenant expands them before render; strip so they never hit the DOM.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n ({ justifyContent, alignItems, flexWrap, divider, useFlexGap, repeatDataset, repeatLimit, repeatFilter, repeatSort, sx, ...props }, ref) =>\n createElement(MuiStack, {\n ref,\n sx: [\n { justifyContent, alignItems, flexWrap },\n ...(Array.isArray(sx) ? sx : sx ? [sx] : []),\n ],\n // The stored value is a style NAME; MUI wants a node (AGL-2486).\n divider: buildDivider(divider, props.direction),\n // A cleared `direction` persists as null, and MUI resolves it as a\n // responsive value — `null.xs` throws during SSR and 500s the page.\n // Same class as the AGL-1226 button colour, different sharp edge.\n // Last, so an author's element wins over anything `props` carried\n // (AGL-2525). A stack IS the nav row on most sites; before this it\n // could only be wrapped in one.\n ...applySemanticElement(dropClearedProps(props) as Record<string, unknown>),\n // A spacing stored as text — every AI-built page stores its props as\n // text — is still a step on the spacing scale: MUI reads \"2\" as a CSS\n // length and spaces nothing (AGL-3660).\n ...(typeof props.spacing === 'string' && props.spacing.trim() && Number.isFinite(Number(props.spacing))\n ? { spacing: Number(props.spacing) }\n : {}),\n // After the spread, so the element picker cannot carry a `useFlexGap`\n // past it. `spacing` reaches children as `margin-left` unless this is\n // on, and those margins assume flex: on a grid they land INSIDE the\n // track, so every item but the first sits a spacing-step right of its\n // column and the left gutter goes ragged (AGL-3214). `gap` is correct\n // for grid, multi-column and flex alike, and an author's own `sx` gap\n // still wins over it. A cleared switch persists as null, which is why\n // this reads `??` and not `||` — an author's explicit OFF stands.\n useFlexGap: useFlexGap ?? (hasNonFlexDisplay(sx) || undefined),\n }),\n)\n\n// Component ids are persisted in screen documents; keep the legacy ids.\nexport const ID: Aglyn.ComponentId = 'muiStack'\n\nexport const schema: Aglyn.ComponentSchema = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Stack',\n // Static: may sit in a subtree that keeps its server HTML (AGL-3581).\n flags: { lazyHydration: Aglyn.FEATURE_FLAG.ENABLED },\n description:\n 'Lays children out in a row or column with even spacing — the workhorse for most layouts.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: {\n path: mdiViewColumn.path,\n sx: { color: '#2196f3' },\n },\n attributes: [\n semanticElementAttribute('this stack'),\n semanticElementLabelAttribute(),\n {\n name: 'direction',\n label: 'Direction',\n description:\n 'Defines the directional flow using the `flex-direction` style property. It is applied for all screen sizes.',\n component: Aglyn.FieldComponentType.SELECT,\n // \"Default\" deleted (AGL-1453): unpersistable, and a second name for\n // `column`, MUI Stack's own default, already on the list.\n options: [\n { value: 'column', label: 'Column' },\n { value: 'column-reverse', label: 'Column Reversed' },\n { value: 'row', label: 'Row' },\n { value: 'row-reverse', label: 'Row Reversed' },\n ],\n },\n {\n name: 'justifyContent',\n label: 'Justify Content',\n description:\n 'Defines how the browser distributes space between and around content items along the main-axis of the container.',\n component: Aglyn.FieldComponentType.SELECT,\n // \"Default\" deleted (AGL-1453). Unpersistable, and a second name for\n // `flex-start`: CSS's initial `justify-content` is `normal`, which on a\n // flex container — which a Stack always is — behaves as `flex-start`.\n //\n // Deleted rather than sentinelled for a second reason specific to this\n // prop: `justifyContent` is destructured out ABOVE and pushed into\n // `sx`, so it never passes through `dropClearedProps`. It is the one\n // attribute on this component the guard does not cover, so the right\n // move is to stop minting values it would have to catch.\n options: [\n { value: 'flex-start', label: 'Flex Start' },\n { value: 'center', label: 'Center' },\n { value: 'flex-end', label: 'Flex End' },\n { value: 'space-between', label: 'Space Between' },\n { value: 'space-around', label: 'Space Around' },\n { value: 'space-evenly', label: 'Space Evenly' },\n ],\n },\n {\n name: 'alignItems',\n label: 'Align Items',\n description:\n 'Defines how the browser distributes space between and around ' +\n 'content items along the cross-axis of the container — the axis ' +\n 'across the Direction.',\n component: Aglyn.FieldComponentType.SELECT,\n // No \"Default\" sentinel (AGL-1453): `stretch` is CSS's own initial\n // `align-items` and is already on the list, and like `justifyContent`\n // this prop is pushed into `sx` rather than through\n // `dropClearedProps`, so an unpersistable `''` would not be caught.\n options: [\n { value: 'stretch', label: 'Stretch' },\n { value: 'flex-start', label: 'Flex Start' },\n { value: 'center', label: 'Center' },\n { value: 'flex-end', label: 'Flex End' },\n { value: 'baseline', label: 'Baseline' },\n ],\n },\n {\n name: 'spacing',\n label: 'Spacing',\n description: 'Defines the space/gap between its immediate children.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'useFlexGap',\n label: 'Use flex gap',\n description:\n 'Space children with the CSS `gap` property instead of margins. ' +\n 'Required for Spacing to survive wrapping, and it leaves child ' +\n 'margins alone — but `gap` is unsupported in some older browsers.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'flexWrap',\n label: 'Wrap',\n description:\n 'Whether children that do not fit move onto a new line. Turn on ' +\n 'Use flex gap as well, or Spacing is applied as margins and the ' +\n 'wrapped rows get an uneven gap.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'nowrap', label: 'No Wrap' },\n { value: 'wrap', label: 'Wrap' },\n { value: 'wrap-reverse', label: 'Wrap Reversed' },\n ],\n },\n {\n name: 'divider',\n label: 'Divider',\n description:\n 'Draws a rule between each pair of children, across the ' +\n 'Direction. Clear the field for no divider.',\n component: Aglyn.FieldComponentType.SELECT,\n // MUI's own prop takes a node; the persisted value is a style name and\n // the component builds the element. No `''` option (AGL-1453) — \"no\n // divider\" is the field's ✕, which persists as cleared.\n options: [\n { value: 'line', label: 'Line' },\n { value: 'dashed', label: 'Dashed' },\n ],\n },\n // No Repeat fields: every element's Attributes panel draws the same Repeat\n // section (AGL-3111), writing the props a Stack has always stored.\n colorSchemeAttribute(),\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Stack Horizontal',\n icon: {\n path: mdiViewColumn.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { direction: 'row' },\n },\n },\n {\n $id: generatePresetId(ID, 'vertical'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Stack Vertical',\n icon: {\n path: mdiViewSequential.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { direction: 'column' },\n },\n },\n]\n\nexport default Stack\n"],"names":["Aglyn","mdiViewColumn","mdiViewSequential","MuiDivider","MuiStack","createElement","forwardRef","BUNDLE_ID","colorSchemeAttribute","applySemanticElement","semanticElementAttribute","semanticElementLabelAttribute","dropClearedProps","generatePresetId","DIVIDER_STYLES","line","dashed","borderStyle","buildDivider","divider","direction","undefined","Object","prototype","hasOwnProperty","call","style","horizontal","startsWith","orientation","flexItem","sx","FLEX_DISPLAYS","Set","hasNonFlexDisplay","Array","isArray","some","display","notFlex","value","has","values","Stack","justifyContent","alignItems","flexWrap","useFlexGap","repeatDataset","repeatLimit","repeatFilter","repeatSort","props","ref","spacing","trim","Number","isFinite","ID","schema","$id","pluginId","displayName","flags","lazyHydration","FEATURE_FLAG","ENABLED","description","category","ComponentCategory","LAYOUT","icon","path","color","attributes","name","label","component","FieldComponentType","SELECT","options","TEXT_FIELD","type","SWITCH","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/stack.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 * as Aglyn from '@aglyn/aglyn'\nimport {\n mdiViewColumn,\n mdiViewSequential,\n} from '@aglyn/shared-data-mdi'\nimport MuiDivider from '@mui/material/Divider'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport type { CSSProperties, ReactElement } from 'react'\nimport { createElement, forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { colorSchemeAttribute } from '../utils/color-scheme-attribute'\nimport {\n applySemanticElement,\n semanticElementAttribute,\n semanticElementLabelAttribute,\n} from '../utils/element-picker'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/**\n * The dividers the `divider` attribute can name (AGL-2486).\n *\n * MUI's own `divider` prop takes a NODE, which an attributes panel cannot\n * author — so the persisted value is a style name and this file builds the\n * element. Anything else (an author's stray string, a paste) renders no\n * divider rather than the literal text between every pair of children.\n */\nconst DIVIDER_STYLES = {\n line: {},\n dashed: { borderStyle: 'dashed' },\n} as const\n\nexport type StackDivider = keyof typeof DIVIDER_STYLES\n\n// justifyContent/alignItems/flexWrap are not direct StackProps in MUI v6+;\n// pass them through sx.\ntype StackWithFlexProps = Omit<StackProps, 'divider'> & {\n justifyContent?: CSSProperties['justifyContent']\n alignItems?: CSSProperties['alignItems']\n flexWrap?: CSSProperties['flexWrap']\n /** Divider STYLE name, not a node — see {@link DIVIDER_STYLES}. */\n divider?: StackDivider | string\n /**\n * A repeat's directives (AGL-103, AGL-181). The published page's\n * composition consumes them before anything renders; a surface that renders\n * the stored node as written, like a component editor, still hands them over.\n */\n repeatDataset?: string\n repeatLimit?: number | string\n repeatFilter?: string\n repeatSort?: string\n}\n\n/**\n * A Stack's divider runs ACROSS the flow, so its orientation is the\n * opposite of the stack's own axis. A responsive `direction` (an array or\n * object) is not authorable here, so only the string form is inspected;\n * anything else falls back to MUI's default horizontal rule.\n */\nfunction buildDivider(\n divider: unknown,\n direction: unknown,\n): ReactElement | undefined {\n if (typeof divider !== 'string') return undefined\n // `hasOwnProperty`, not a truthiness test on the lookup: `\"constructor\"`\n // and friends resolve through Object.prototype and would sail past one.\n if (!Object.prototype.hasOwnProperty.call(DIVIDER_STYLES, divider)) {\n return undefined\n }\n const style = DIVIDER_STYLES[divider as StackDivider]\n const horizontal =\n typeof direction === 'string' && direction.startsWith('row')\n return createElement(MuiDivider, {\n orientation: horizontal ? 'vertical' : 'horizontal',\n // Without it a vertical divider collapses to zero height in a flex row.\n flexItem: horizontal,\n sx: style,\n })\n}\n\n/**\n * Displays that make a Stack the flex container MUI's `spacing` assumes.\n */\nconst FLEX_DISPLAYS = new Set(['flex', 'inline-flex'])\n\n/**\n * Whether an author's `sx` takes the Stack out of flex at any breakpoint\n * (AGL-3214).\n *\n * A function `sx` cannot be read without a theme, so it counts as flex and\n * the stack keeps MUI's own behaviour.\n */\nfunction hasNonFlexDisplay(sx: unknown): boolean {\n if (Array.isArray(sx)) return sx.some(hasNonFlexDisplay)\n if (!sx || typeof sx !== 'object') return false\n const display = (sx as Record<string, unknown>).display\n const notFlex = (value: unknown) =>\n typeof value === 'string' && !FLEX_DISPLAYS.has(value)\n if (typeof display === 'string') return notFlex(display)\n // Responsive, in either of the forms the styles panel writes: an array of\n // breakpoint values, or a `{ xs, md }` map.\n if (Array.isArray(display)) return display.some(notFlex)\n if (display && typeof display === 'object')\n return Object.values(display).some(notFlex)\n return false\n}\n\nconst Stack = forwardRef<HTMLDivElement, StackWithFlexProps>(\n // repeatDataset/repeatLimit are compose-time attributes (AGL-103): the\n // tenant expands them before render; strip so they never hit the DOM.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n ({ justifyContent, alignItems, flexWrap, divider, useFlexGap, repeatDataset, repeatLimit, repeatFilter, repeatSort, sx, ...props }, ref) =>\n createElement(MuiStack, {\n ref,\n sx: [\n { justifyContent, alignItems, flexWrap },\n ...(Array.isArray(sx) ? sx : sx ? [sx] : []),\n ],\n // The stored value is a style NAME; MUI wants a node (AGL-2486).\n divider: buildDivider(divider, props.direction),\n // A cleared `direction` persists as null, and MUI resolves it as a\n // responsive value — `null.xs` throws during SSR and 500s the page.\n // Same class as the AGL-1226 button colour, different sharp edge.\n // Last, so an author's element wins over anything `props` carried\n // (AGL-2525). A stack IS the nav row on most sites; before this it\n // could only be wrapped in one.\n ...applySemanticElement(dropClearedProps(props) as Record<string, unknown>),\n // A spacing stored as text — every AI-built page stores its props as\n // text — is still a step on the spacing scale: MUI reads \"2\" as a CSS\n // length and spaces nothing (AGL-3660).\n ...(typeof props.spacing === 'string' && props.spacing.trim() && Number.isFinite(Number(props.spacing))\n ? { spacing: Number(props.spacing) }\n : {}),\n // After the spread, so the element picker cannot carry a `useFlexGap`\n // past it. `spacing` reaches children as `margin-left` unless this is\n // on, and those margins assume flex: on a grid they land INSIDE the\n // track, so every item but the first sits a spacing-step right of its\n // column and the left gutter goes ragged (AGL-3214). `gap` is correct\n // for grid, multi-column and flex alike, and an author's own `sx` gap\n // still wins over it. A cleared switch persists as null, which is why\n // this reads `??` and not `||` — an author's explicit OFF stands.\n useFlexGap: useFlexGap ?? (hasNonFlexDisplay(sx) || undefined),\n }),\n)\n\n// Component ids are persisted in screen documents; keep the legacy ids.\nexport const ID: Aglyn.ComponentId = 'muiStack'\n\nexport const schema: Aglyn.ComponentSchema = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Stack',\n flags: {\n // Static: may sit in a subtree that keeps its server HTML (AGL-3581).\n lazyHydration: Aglyn.FEATURE_FLAG.ENABLED,\n // MUI draws the divider between `Children.toArray(children)`, so the\n // renderer has to hand it one React child per node child (AGL-3660).\n // Wrapped in the single `<Branch>`, a Stack of any size read as ONE\n // child and no divider was ever drawn, on the canvas or a live page.\n positionalChildren: Aglyn.FEATURE_FLAG.ENABLED,\n },\n description:\n 'Lays children out in a row or column with even spacing — the workhorse for most layouts.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: {\n path: mdiViewColumn.path,\n sx: { color: '#2196f3' },\n },\n attributes: [\n semanticElementAttribute('this stack'),\n semanticElementLabelAttribute(),\n {\n name: 'direction',\n label: 'Direction',\n description:\n 'Defines the directional flow using the `flex-direction` style property. It is applied for all screen sizes.',\n component: Aglyn.FieldComponentType.SELECT,\n // \"Default\" deleted (AGL-1453): unpersistable, and a second name for\n // `column`, MUI Stack's own default, already on the list.\n options: [\n { value: 'column', label: 'Column' },\n { value: 'column-reverse', label: 'Column Reversed' },\n { value: 'row', label: 'Row' },\n { value: 'row-reverse', label: 'Row Reversed' },\n ],\n },\n {\n name: 'justifyContent',\n label: 'Justify Content',\n description:\n 'Defines how the browser distributes space between and around content items along the main-axis of the container.',\n component: Aglyn.FieldComponentType.SELECT,\n // \"Default\" deleted (AGL-1453). Unpersistable, and a second name for\n // `flex-start`: CSS's initial `justify-content` is `normal`, which on a\n // flex container — which a Stack always is — behaves as `flex-start`.\n //\n // Deleted rather than sentinelled for a second reason specific to this\n // prop: `justifyContent` is destructured out ABOVE and pushed into\n // `sx`, so it never passes through `dropClearedProps`. It is the one\n // attribute on this component the guard does not cover, so the right\n // move is to stop minting values it would have to catch.\n options: [\n { value: 'flex-start', label: 'Flex Start' },\n { value: 'center', label: 'Center' },\n { value: 'flex-end', label: 'Flex End' },\n { value: 'space-between', label: 'Space Between' },\n { value: 'space-around', label: 'Space Around' },\n { value: 'space-evenly', label: 'Space Evenly' },\n ],\n },\n {\n name: 'alignItems',\n label: 'Align Items',\n description:\n 'Defines how the browser distributes space between and around ' +\n 'content items along the cross-axis of the container — the axis ' +\n 'across the Direction.',\n component: Aglyn.FieldComponentType.SELECT,\n // No \"Default\" sentinel (AGL-1453): `stretch` is CSS's own initial\n // `align-items` and is already on the list, and like `justifyContent`\n // this prop is pushed into `sx` rather than through\n // `dropClearedProps`, so an unpersistable `''` would not be caught.\n options: [\n { value: 'stretch', label: 'Stretch' },\n { value: 'flex-start', label: 'Flex Start' },\n { value: 'center', label: 'Center' },\n { value: 'flex-end', label: 'Flex End' },\n { value: 'baseline', label: 'Baseline' },\n ],\n },\n {\n name: 'spacing',\n label: 'Spacing',\n description: 'Defines the space/gap between its immediate children.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'useFlexGap',\n label: 'Use flex gap',\n description:\n 'Space children with the CSS `gap` property instead of margins. ' +\n 'Required for Spacing to survive wrapping, and it leaves child ' +\n 'margins alone — but `gap` is unsupported in some older browsers.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'flexWrap',\n label: 'Wrap',\n description:\n 'Whether children that do not fit move onto a new line. Turn on ' +\n 'Use flex gap as well, or Spacing is applied as margins and the ' +\n 'wrapped rows get an uneven gap.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'nowrap', label: 'No Wrap' },\n { value: 'wrap', label: 'Wrap' },\n { value: 'wrap-reverse', label: 'Wrap Reversed' },\n ],\n },\n {\n name: 'divider',\n label: 'Divider',\n description:\n 'Draws a rule between each pair of children, across the ' +\n 'Direction. Clear the field for no divider.',\n component: Aglyn.FieldComponentType.SELECT,\n // MUI's own prop takes a node; the persisted value is a style name and\n // the component builds the element. No `''` option (AGL-1453) — \"no\n // divider\" is the field's ✕, which persists as cleared.\n options: [\n { value: 'line', label: 'Line' },\n { value: 'dashed', label: 'Dashed' },\n ],\n },\n // No Repeat fields: every element's Attributes panel draws the same Repeat\n // section (AGL-3111), writing the props a Stack has always stored.\n colorSchemeAttribute(),\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Stack Horizontal',\n icon: {\n path: mdiViewColumn.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { direction: 'row' },\n },\n },\n {\n $id: generatePresetId(ID, 'vertical'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Stack Vertical',\n icon: {\n path: mdiViewSequential.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { direction: 'column' },\n },\n },\n]\n\nexport default Stack\n"],"names":["Aglyn","mdiViewColumn","mdiViewSequential","MuiDivider","MuiStack","createElement","forwardRef","BUNDLE_ID","colorSchemeAttribute","applySemanticElement","semanticElementAttribute","semanticElementLabelAttribute","dropClearedProps","generatePresetId","DIVIDER_STYLES","line","dashed","borderStyle","buildDivider","divider","direction","undefined","Object","prototype","hasOwnProperty","call","style","horizontal","startsWith","orientation","flexItem","sx","FLEX_DISPLAYS","Set","hasNonFlexDisplay","Array","isArray","some","display","notFlex","value","has","values","Stack","justifyContent","alignItems","flexWrap","useFlexGap","repeatDataset","repeatLimit","repeatFilter","repeatSort","props","ref","spacing","trim","Number","isFinite","ID","schema","$id","pluginId","displayName","flags","lazyHydration","FEATURE_FLAG","ENABLED","positionalChildren","description","category","ComponentCategory","LAYOUT","icon","path","color","attributes","name","label","component","FieldComponentType","SELECT","options","TEXT_FIELD","type","SWITCH","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|