@aglyn/shared-ui-jsx 1.0.0-beta.218 → 1.0.0-beta.220
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/README.md +1 -1
- package/package.json +6 -7
- package/src/lib/components/aspect-ratio.js +1 -1
- package/src/lib/components/aspect-ratio.js.map +1 -1
- package/src/lib/hooks/use-content-stable.d.ts +29 -0
- package/src/lib/hooks/use-content-stable.js +63 -0
- package/src/lib/hooks/use-content-stable.js.map +1 -0
- package/src/lib/testing/fiber-render-counter.d.ts +51 -0
- package/src/lib/testing/fiber-render-counter.js +89 -0
- package/src/lib/testing/fiber-render-counter.js.map +1 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Aglyn's general-purpose React and Material UI component library: layout and card
|
|
|
8
8
|
|
|
9
9
|
npm install @aglyn/shared-ui-jsx@beta
|
|
10
10
|
|
|
11
|
-
Peer dependencies: `react`, `react-dom`, `next`, `@mui/material`, `@mui/system`, `@mui/utils`, `@mui/
|
|
11
|
+
Peer dependencies: `react`, `react-dom`, `next`, `@mui/material`, `@mui/system`, `@mui/utils`, `@mui/styles` and `@mui/x-data-grid`. `next` is needed by the link and router-event modules, and `@mui/x-data-grid` by the data table.
|
|
12
12
|
|
|
13
13
|
## What's in it
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/shared-ui-jsx",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.220",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.220",
|
|
29
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.220",
|
|
30
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.220",
|
|
31
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.220",
|
|
32
32
|
"@mdi/js": "^7.4.47",
|
|
33
33
|
"@swc/helpers": "0.5.23",
|
|
34
34
|
"change-case": "^5.4.4",
|
|
@@ -46,13 +46,12 @@
|
|
|
46
46
|
"warning": "^4.0.3"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@mui/base": "5.0.0-beta.70",
|
|
50
49
|
"@mui/material": "^9.3.1",
|
|
51
50
|
"@mui/styles": "^6.4.8",
|
|
52
51
|
"@mui/system": "^9.2.0",
|
|
53
52
|
"@mui/utils": "^9.2.0",
|
|
54
53
|
"@mui/x-data-grid": "^9.12.0",
|
|
55
|
-
"next": "16.3.3",
|
|
54
|
+
"next": "^16.3.3",
|
|
56
55
|
"react": "^19.2.8",
|
|
57
56
|
"react-dom": "^19.2.8"
|
|
58
57
|
},
|
|
@@ -16,7 +16,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
16
16
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
17
|
* See the License for the specific language governing permissions and
|
|
18
18
|
* limitations under the License.
|
|
19
|
-
*/ import { unstable_composeClasses as composeClasses } from "@mui/
|
|
19
|
+
*/ import { unstable_composeClasses as composeClasses } from "@mui/utils";
|
|
20
20
|
import { capitalize, generateUtilityClass, generateUtilityClasses, useThemeProps } from "@mui/material";
|
|
21
21
|
import { styled } from "@mui/material/styles";
|
|
22
22
|
import { Children, cloneElement, forwardRef, isValidElement } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/aspect-ratio.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 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 { unstable_composeClasses as composeClasses } from '@mui/base'\nimport {\n capitalize,\n generateUtilityClass,\n generateUtilityClasses,\n useThemeProps,\n} from '@mui/material'\nimport { styled } from '@mui/material/styles'\nimport { Children, cloneElement, forwardRef, isValidElement } from 'react'\n\nexport interface AspectRatioClasses {\n /** Styles applied to the root element. */\n root: string\n /** Styles applied to the content element. */\n content: string\n /** Styles applied to the content element if `color=\"primary\"`. */\n colorPrimary: string\n /** Styles applied to the content element if `color=\"neutral\"`. */\n colorNeutral: string\n /** Styles applied to the content element if `color=\"danger\"`. */\n colorDanger: string\n /** Styles applied to the content element if `color=\"info\"`. */\n colorInfo: string\n /** Styles applied to the content element if `color=\"success\"`. */\n colorSuccess: string\n /** Styles applied to the content element if `color=\"warning\"`. */\n colorWarning: string\n /** Styles applied to the root element when color inversion is triggered. */\n colorContext: string\n /** Styles applied to the content element if `variant=\"plain\"`. */\n variantPlain: string\n /** Styles applied to the content element if `variant=\"outlined\"`. */\n variantOutlined: string\n /** Styles applied to the content element if `variant=\"soft\"`. */\n variantSoft: string\n /** Styles applied to the content element if `variant=\"solid\"`. */\n variantSolid: string\n}\n\nexport type AspectRatioClassKey = keyof AspectRatioClasses\n\nexport function getAspectRatioUtilityClass(slot: string): string {\n return generateUtilityClass('MuiAspectRatio', slot)\n}\n\nconst aspectRatioClasses: AspectRatioClasses = generateUtilityClasses(\n 'MuiAspectRatio',\n [\n 'root',\n 'content',\n 'colorPrimary',\n 'colorNeutral',\n 'colorDanger',\n 'colorInfo',\n 'colorSuccess',\n 'colorWarning',\n 'colorContext',\n 'variantPlain',\n 'variantOutlined',\n 'variantSoft',\n 'variantSolid',\n ],\n)\n\nconst useUtilityClasses = (ownerState: AspectRatioOwnerState) => {\n const { variant, color } = ownerState\n const slots = {\n root: ['root'],\n content: [\n 'content',\n variant && `variant${capitalize(variant)}`,\n color && `color${capitalize(color)}`,\n ],\n }\n\n return composeClasses(slots, getAspectRatioUtilityClass, {})\n}\n\ntype AspectRatioOwnerState = {\n ratio?: number | string\n minHeight?: number | string\n maxHeight?: number | string\n objectFit?:\n | '-moz-initial'\n | 'contain'\n | 'cover'\n | 'fill'\n | 'inherit'\n | 'initial'\n | 'none'\n | 'revert-layer'\n | 'revert'\n | 'scale-down'\n | 'unset'\n variant?: 'outlined' | 'plain' | 'soft' | 'solid' | string\n color?: 'danger' | 'info' | 'neutral' | 'primary' | 'success' | 'warning'\n}\n\n// Use to control the width of the content, usually in a flexbox row container\nconst AspectRatioRoot = styled('div', {\n name: 'MuiAspectRatio',\n slot: 'Root',\n shouldForwardProp(propName) {\n return propName !== 'ratio'\n },\n})<{ ownerState: AspectRatioOwnerState }>(({ ownerState }) => {\n const minHeight =\n typeof ownerState.minHeight === 'number'\n ? `${ownerState.minHeight}px`\n : ownerState.minHeight\n const maxHeight =\n typeof ownerState.maxHeight === 'number'\n ? `${ownerState.maxHeight}px`\n : ownerState.maxHeight\n return {\n // a context variable for any child component\n '--AspectRatio-paddingBottom':\n minHeight || maxHeight\n ? `clamp(${minHeight || '0px'}, calc(100% / (${ownerState.ratio})), ${\n maxHeight || '9999px'\n })`\n : `calc(100% / (${ownerState.ratio}))`,\n borderRadius: 'var(--AspectRatio-radius)',\n flexDirection: 'column',\n margin: 'var(--AspectRatio-margin)',\n }\n})\n\nconst AspectRatioContent = styled('div', {\n name: 'MuiAspectRatio',\n slot: 'Content',\n overridesResolver: (props, styles) => styles['content'],\n})<{ ownerState: AspectRatioOwnerState }>(({ theme, ownerState }) => [\n {\n flex: 1,\n position: 'relative',\n borderRadius: 'inherit',\n height: 0,\n paddingBottom: 'var(--AspectRatio-paddingBottom)',\n overflow: 'hidden',\n // use data-attribute instead of :first-child to support zero config SSR (emotion)\n // use nested selector for integrating with nextjs image `fill` layout (spans are inserted on top of the img)\n '& [data-first-child]': {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n boxSizing: 'border-box',\n position: 'absolute',\n width: '100%',\n height: '100%',\n objectFit: ownerState.objectFit,\n margin: 0,\n padding: 0,\n '& > img': {\n // support art-direction that uses <picture><img /></picture>\n width: '100%',\n height: '100%',\n objectFit: ownerState.objectFit,\n },\n },\n },\n (theme.palette as unknown as Record<string, Record<string, unknown>>)[\n ownerState.variant!\n ]?.[ownerState.color!],\n])\n\nexport interface AspectRatioProps extends AspectRatioOwnerState {\n children?: JSX.Children\n}\n\nexport const AspectRatio = forwardRef<any, AspectRatioProps>((inProps, ref) => {\n const props = useThemeProps<any, typeof inProps & AspectRatioProps, any>({\n props: inProps,\n name: 'MuiAspectRatio',\n })\n\n const {\n children,\n ratio = '16 / 9',\n minHeight,\n maxHeight,\n objectFit = 'cover',\n color: colorProp = 'neutral',\n variant = 'soft',\n ...other\n } = props\n // const { getColor } = useColorInversion(variant)\n // const color = getColor(inProps.color, colorProp)\n\n const ownerState = {\n ...props,\n minHeight,\n maxHeight,\n objectFit,\n ratio,\n // color,\n variant,\n }\n\n const classes = useUtilityClasses(ownerState)\n\n return (\n <AspectRatioRoot\n className={classes.root}\n ownerState={ownerState}\n {...(other as any)}\n >\n <AspectRatioContent\n className={classes.content}\n ownerState={ownerState}\n {...(other as any)}\n >\n {Children.map(children, (child, index) =>\n index === 0 && isValidElement(child)\n ? cloneElement(child, { 'data-first-child': '' } as Record<\n string,\n string\n >)\n : child,\n )}\n </AspectRatioContent>\n </AspectRatioRoot>\n )\n})\nAspectRatio.displayName = 'AspectRatio'\n\nexport default AspectRatio\n"],"names":["unstable_composeClasses","composeClasses","capitalize","generateUtilityClass","generateUtilityClasses","useThemeProps","styled","Children","cloneElement","forwardRef","isValidElement","getAspectRatioUtilityClass","slot","aspectRatioClasses","useUtilityClasses","ownerState","variant","color","slots","root","content","AspectRatioRoot","name","shouldForwardProp","propName","minHeight","maxHeight","ratio","borderRadius","flexDirection","margin","AspectRatioContent","overridesResolver","props","styles","theme","flex","position","height","paddingBottom","overflow","display","justifyContent","alignItems","boxSizing","width","objectFit","padding","palette","AspectRatio","inProps","ref","children","colorProp","other","classes","className","map","child","index","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/aspect-ratio.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 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 { unstable_composeClasses as composeClasses } from '@mui/utils'\nimport {\n capitalize,\n generateUtilityClass,\n generateUtilityClasses,\n useThemeProps,\n} from '@mui/material'\nimport { styled } from '@mui/material/styles'\nimport { Children, cloneElement, forwardRef, isValidElement } from 'react'\n\nexport interface AspectRatioClasses {\n /** Styles applied to the root element. */\n root: string\n /** Styles applied to the content element. */\n content: string\n /** Styles applied to the content element if `color=\"primary\"`. */\n colorPrimary: string\n /** Styles applied to the content element if `color=\"neutral\"`. */\n colorNeutral: string\n /** Styles applied to the content element if `color=\"danger\"`. */\n colorDanger: string\n /** Styles applied to the content element if `color=\"info\"`. */\n colorInfo: string\n /** Styles applied to the content element if `color=\"success\"`. */\n colorSuccess: string\n /** Styles applied to the content element if `color=\"warning\"`. */\n colorWarning: string\n /** Styles applied to the root element when color inversion is triggered. */\n colorContext: string\n /** Styles applied to the content element if `variant=\"plain\"`. */\n variantPlain: string\n /** Styles applied to the content element if `variant=\"outlined\"`. */\n variantOutlined: string\n /** Styles applied to the content element if `variant=\"soft\"`. */\n variantSoft: string\n /** Styles applied to the content element if `variant=\"solid\"`. */\n variantSolid: string\n}\n\nexport type AspectRatioClassKey = keyof AspectRatioClasses\n\nexport function getAspectRatioUtilityClass(slot: string): string {\n return generateUtilityClass('MuiAspectRatio', slot)\n}\n\nconst aspectRatioClasses: AspectRatioClasses = generateUtilityClasses(\n 'MuiAspectRatio',\n [\n 'root',\n 'content',\n 'colorPrimary',\n 'colorNeutral',\n 'colorDanger',\n 'colorInfo',\n 'colorSuccess',\n 'colorWarning',\n 'colorContext',\n 'variantPlain',\n 'variantOutlined',\n 'variantSoft',\n 'variantSolid',\n ],\n)\n\nconst useUtilityClasses = (ownerState: AspectRatioOwnerState) => {\n const { variant, color } = ownerState\n const slots = {\n root: ['root'],\n content: [\n 'content',\n variant && `variant${capitalize(variant)}`,\n color && `color${capitalize(color)}`,\n ],\n }\n\n return composeClasses(slots, getAspectRatioUtilityClass, {})\n}\n\ntype AspectRatioOwnerState = {\n ratio?: number | string\n minHeight?: number | string\n maxHeight?: number | string\n objectFit?:\n | '-moz-initial'\n | 'contain'\n | 'cover'\n | 'fill'\n | 'inherit'\n | 'initial'\n | 'none'\n | 'revert-layer'\n | 'revert'\n | 'scale-down'\n | 'unset'\n variant?: 'outlined' | 'plain' | 'soft' | 'solid' | string\n color?: 'danger' | 'info' | 'neutral' | 'primary' | 'success' | 'warning'\n}\n\n// Use to control the width of the content, usually in a flexbox row container\nconst AspectRatioRoot = styled('div', {\n name: 'MuiAspectRatio',\n slot: 'Root',\n shouldForwardProp(propName) {\n return propName !== 'ratio'\n },\n})<{ ownerState: AspectRatioOwnerState }>(({ ownerState }) => {\n const minHeight =\n typeof ownerState.minHeight === 'number'\n ? `${ownerState.minHeight}px`\n : ownerState.minHeight\n const maxHeight =\n typeof ownerState.maxHeight === 'number'\n ? `${ownerState.maxHeight}px`\n : ownerState.maxHeight\n return {\n // a context variable for any child component\n '--AspectRatio-paddingBottom':\n minHeight || maxHeight\n ? `clamp(${minHeight || '0px'}, calc(100% / (${ownerState.ratio})), ${\n maxHeight || '9999px'\n })`\n : `calc(100% / (${ownerState.ratio}))`,\n borderRadius: 'var(--AspectRatio-radius)',\n flexDirection: 'column',\n margin: 'var(--AspectRatio-margin)',\n }\n})\n\nconst AspectRatioContent = styled('div', {\n name: 'MuiAspectRatio',\n slot: 'Content',\n overridesResolver: (props, styles) => styles['content'],\n})<{ ownerState: AspectRatioOwnerState }>(({ theme, ownerState }) => [\n {\n flex: 1,\n position: 'relative',\n borderRadius: 'inherit',\n height: 0,\n paddingBottom: 'var(--AspectRatio-paddingBottom)',\n overflow: 'hidden',\n // use data-attribute instead of :first-child to support zero config SSR (emotion)\n // use nested selector for integrating with nextjs image `fill` layout (spans are inserted on top of the img)\n '& [data-first-child]': {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n boxSizing: 'border-box',\n position: 'absolute',\n width: '100%',\n height: '100%',\n objectFit: ownerState.objectFit,\n margin: 0,\n padding: 0,\n '& > img': {\n // support art-direction that uses <picture><img /></picture>\n width: '100%',\n height: '100%',\n objectFit: ownerState.objectFit,\n },\n },\n },\n (theme.palette as unknown as Record<string, Record<string, unknown>>)[\n ownerState.variant!\n ]?.[ownerState.color!],\n])\n\nexport interface AspectRatioProps extends AspectRatioOwnerState {\n children?: JSX.Children\n}\n\nexport const AspectRatio = forwardRef<any, AspectRatioProps>((inProps, ref) => {\n const props = useThemeProps<any, typeof inProps & AspectRatioProps, any>({\n props: inProps,\n name: 'MuiAspectRatio',\n })\n\n const {\n children,\n ratio = '16 / 9',\n minHeight,\n maxHeight,\n objectFit = 'cover',\n color: colorProp = 'neutral',\n variant = 'soft',\n ...other\n } = props\n // const { getColor } = useColorInversion(variant)\n // const color = getColor(inProps.color, colorProp)\n\n const ownerState = {\n ...props,\n minHeight,\n maxHeight,\n objectFit,\n ratio,\n // color,\n variant,\n }\n\n const classes = useUtilityClasses(ownerState)\n\n return (\n <AspectRatioRoot\n className={classes.root}\n ownerState={ownerState}\n {...(other as any)}\n >\n <AspectRatioContent\n className={classes.content}\n ownerState={ownerState}\n {...(other as any)}\n >\n {Children.map(children, (child, index) =>\n index === 0 && isValidElement(child)\n ? cloneElement(child, { 'data-first-child': '' } as Record<\n string,\n string\n >)\n : child,\n )}\n </AspectRatioContent>\n </AspectRatioRoot>\n )\n})\nAspectRatio.displayName = 'AspectRatio'\n\nexport default AspectRatio\n"],"names":["unstable_composeClasses","composeClasses","capitalize","generateUtilityClass","generateUtilityClasses","useThemeProps","styled","Children","cloneElement","forwardRef","isValidElement","getAspectRatioUtilityClass","slot","aspectRatioClasses","useUtilityClasses","ownerState","variant","color","slots","root","content","AspectRatioRoot","name","shouldForwardProp","propName","minHeight","maxHeight","ratio","borderRadius","flexDirection","margin","AspectRatioContent","overridesResolver","props","styles","theme","flex","position","height","paddingBottom","overflow","display","justifyContent","alignItems","boxSizing","width","objectFit","padding","palette","AspectRatio","inProps","ref","children","colorProp","other","classes","className","map","child","index","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
* What a value HOLDS, as a string: plain data by value, a function by
|
|
19
|
+
* identity, a Set by its members. A value it cannot write down — a cycle, a
|
|
20
|
+
* React element's owner — gets a key of its own, so it only ever equals
|
|
21
|
+
* itself.
|
|
22
|
+
*/
|
|
23
|
+
export declare function contentKey(value: unknown): string;
|
|
24
|
+
/**
|
|
25
|
+
* `value` itself while it holds the same content as last render, else the new
|
|
26
|
+
* one — so a caller that builds a value inline on every render hands on the
|
|
27
|
+
* SAME value while what it holds has not changed (AGL-3423).
|
|
28
|
+
*/
|
|
29
|
+
export declare function useContentStable<T>(value: T): T;
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
*/ import { useRef } from "react";
|
|
17
|
+
/** A stable token per function, so two keys agree only on the same function. */ const functionTokens = new WeakMap();
|
|
18
|
+
let nextFunctionToken = 0;
|
|
19
|
+
let nextUnkeyable = 0;
|
|
20
|
+
/**
|
|
21
|
+
* What a value HOLDS, as a string: plain data by value, a function by
|
|
22
|
+
* identity, a Set by its members. A value it cannot write down — a cycle, a
|
|
23
|
+
* React element's owner — gets a key of its own, so it only ever equals
|
|
24
|
+
* itself.
|
|
25
|
+
*/ export function contentKey(value) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(value, (_key, entry)=>{
|
|
28
|
+
if (typeof entry === 'function') {
|
|
29
|
+
let token = functionTokens.get(entry);
|
|
30
|
+
if (token === undefined) {
|
|
31
|
+
nextFunctionToken += 1;
|
|
32
|
+
token = nextFunctionToken;
|
|
33
|
+
functionTokens.set(entry, token);
|
|
34
|
+
}
|
|
35
|
+
return `ƒ${token}`;
|
|
36
|
+
}
|
|
37
|
+
if (entry instanceof Set) return {
|
|
38
|
+
set: [
|
|
39
|
+
...entry
|
|
40
|
+
].sort()
|
|
41
|
+
};
|
|
42
|
+
return entry;
|
|
43
|
+
});
|
|
44
|
+
} catch (unused) {
|
|
45
|
+
nextUnkeyable += 1;
|
|
46
|
+
return `unkeyable:${nextUnkeyable}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `value` itself while it holds the same content as last render, else the new
|
|
51
|
+
* one — so a caller that builds a value inline on every render hands on the
|
|
52
|
+
* SAME value while what it holds has not changed (AGL-3423).
|
|
53
|
+
*/ export function useContentStable(value) {
|
|
54
|
+
const held = useRef(undefined);
|
|
55
|
+
const key = contentKey(value);
|
|
56
|
+
if (!held.current || held.current.key !== key) held.current = {
|
|
57
|
+
key,
|
|
58
|
+
value
|
|
59
|
+
};
|
|
60
|
+
return held.current.value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=use-content-stable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-content-stable.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 { useRef } from 'react'\n\n/** A stable token per function, so two keys agree only on the same function. */\nconst functionTokens = new WeakMap<object, number>()\nlet nextFunctionToken = 0\nlet nextUnkeyable = 0\n\n/**\n * What a value HOLDS, as a string: plain data by value, a function by\n * identity, a Set by its members. A value it cannot write down — a cycle, a\n * React element's owner — gets a key of its own, so it only ever equals\n * itself.\n */\nexport function contentKey(value: unknown): string {\n try {\n return JSON.stringify(value, (_key, entry: unknown) => {\n if (typeof entry === 'function') {\n let token = functionTokens.get(entry)\n if (token === undefined) {\n nextFunctionToken += 1\n token = nextFunctionToken\n functionTokens.set(entry, token)\n }\n return `ƒ${token}`\n }\n if (entry instanceof Set) return { set: [...entry].sort() }\n return entry\n })\n } catch {\n nextUnkeyable += 1\n return `unkeyable:${nextUnkeyable}`\n }\n}\n\n/**\n * `value` itself while it holds the same content as last render, else the new\n * one — so a caller that builds a value inline on every render hands on the\n * SAME value while what it holds has not changed (AGL-3423).\n */\nexport function useContentStable<T>(value: T): T {\n const held = useRef<{ key: string; value: T } | undefined>(undefined)\n const key = contentKey(value)\n if (!held.current || held.current.key !== key) held.current = { key, value }\n return held.current.value\n}\n"],"names":["useRef","functionTokens","WeakMap","nextFunctionToken","nextUnkeyable","contentKey","value","JSON","stringify","_key","entry","token","get","undefined","set","Set","sort","useContentStable","held","key","current"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,MAAM,QAAQ,QAAO;AAE9B,8EAA8E,GAC9E,MAAMC,iBAAiB,IAAIC;AAC3B,IAAIC,oBAAoB;AACxB,IAAIC,gBAAgB;AAEpB;;;;;CAKC,GACD,OAAO,SAASC,WAAWC,KAAc;IACvC,IAAI;QACF,OAAOC,KAAKC,SAAS,CAACF,OAAO,CAACG,MAAMC;YAClC,IAAI,OAAOA,UAAU,YAAY;gBAC/B,IAAIC,QAAQV,eAAeW,GAAG,CAACF;gBAC/B,IAAIC,UAAUE,WAAW;oBACvBV,qBAAqB;oBACrBQ,QAAQR;oBACRF,eAAea,GAAG,CAACJ,OAAOC;gBAC5B;gBACA,OAAO,CAAC,CAAC,EAAEA,OAAO;YACpB;YACA,IAAID,iBAAiBK,KAAK,OAAO;gBAAED,KAAK;uBAAIJ;iBAAM,CAACM,IAAI;YAAG;YAC1D,OAAON;QACT;IACF,EAAE,eAAM;QACNN,iBAAiB;QACjB,OAAO,CAAC,UAAU,EAAEA,eAAe;IACrC;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASa,iBAAoBX,KAAQ;IAC1C,MAAMY,OAAOlB,OAA8Ca;IAC3D,MAAMM,MAAMd,WAAWC;IACvB,IAAI,CAACY,KAAKE,OAAO,IAAIF,KAAKE,OAAO,CAACD,GAAG,KAAKA,KAAKD,KAAKE,OAAO,GAAG;QAAED;QAAKb;IAAM;IAC3E,OAAOY,KAAKE,OAAO,CAACd,KAAK;AAC3B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
* WHAT A KEYSTROKE ACTUALLY RE-RENDERED, read off React's own commits
|
|
19
|
+
* (AGL-3423).
|
|
20
|
+
*
|
|
21
|
+
* A keystroke that re-renders a whole dialog is cheap to write and expensive
|
|
22
|
+
* to run, and it is the cost that matters: when each keystroke takes long
|
|
23
|
+
* enough, typed input queues up and is processed back to back, and a chip
|
|
24
|
+
* Autocomplete re-rendered by every one of them leaves React's nested-update
|
|
25
|
+
* count climbing until it throws #185. A spec can only hold that line if it
|
|
26
|
+
* can SEE it, so this counts it, the way React DevTools' "why did this
|
|
27
|
+
* render" does: a stand-in devtools hook receives every committed tree, and
|
|
28
|
+
* a walk that skips each subtree React bailed out of counts the components
|
|
29
|
+
* whose functions ran.
|
|
30
|
+
*
|
|
31
|
+
* The hook is read when `react-dom` is first evaluated, so install it, then
|
|
32
|
+
* `jest.resetModules()`, then require React, `react-dom/client` and the
|
|
33
|
+
* component under test from the fresh registry.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* The time a spec built on this needs, per test and per setup hook: each
|
|
37
|
+
* loads React, MUI and the component under test into a fresh registry, which
|
|
38
|
+
* on a loaded two-worker CI shard ran past jest's 30 s default (2026-09-30).
|
|
39
|
+
* Set it file-wide with `jest.setTimeout`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const FRESH_REGISTRY_TIMEOUT_MS = 120000;
|
|
42
|
+
export interface FiberRenderCounter {
|
|
43
|
+
/** How many times each named component rendered since the last reset. */
|
|
44
|
+
rendered(name: string): number;
|
|
45
|
+
/** Every name counted since the last reset, for a failure message. */
|
|
46
|
+
snapshot(): Record<string, number>;
|
|
47
|
+
reset(): void;
|
|
48
|
+
uninstall(): void;
|
|
49
|
+
}
|
|
50
|
+
/** Installs the counting hook; `react-dom` must be required after this. */
|
|
51
|
+
export declare function installFiberRenderCounter(): FiberRenderCounter;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* WHAT A KEYSTROKE ACTUALLY RE-RENDERED, read off React's own commits
|
|
18
|
+
* (AGL-3423).
|
|
19
|
+
*
|
|
20
|
+
* A keystroke that re-renders a whole dialog is cheap to write and expensive
|
|
21
|
+
* to run, and it is the cost that matters: when each keystroke takes long
|
|
22
|
+
* enough, typed input queues up and is processed back to back, and a chip
|
|
23
|
+
* Autocomplete re-rendered by every one of them leaves React's nested-update
|
|
24
|
+
* count climbing until it throws #185. A spec can only hold that line if it
|
|
25
|
+
* can SEE it, so this counts it, the way React DevTools' "why did this
|
|
26
|
+
* render" does: a stand-in devtools hook receives every committed tree, and
|
|
27
|
+
* a walk that skips each subtree React bailed out of counts the components
|
|
28
|
+
* whose functions ran.
|
|
29
|
+
*
|
|
30
|
+
* The hook is read when `react-dom` is first evaluated, so install it, then
|
|
31
|
+
* `jest.resetModules()`, then require React, `react-dom/client` and the
|
|
32
|
+
* component under test from the fresh registry.
|
|
33
|
+
*/ /**
|
|
34
|
+
* The time a spec built on this needs, per test and per setup hook: each
|
|
35
|
+
* loads React, MUI and the component under test into a fresh registry, which
|
|
36
|
+
* on a loaded two-worker CI shard ran past jest's 30 s default (2026-09-30).
|
|
37
|
+
* Set it file-wide with `jest.setTimeout`.
|
|
38
|
+
*/ export const FRESH_REGISTRY_TIMEOUT_MS = 120000;
|
|
39
|
+
/** `PerformedWork` in React's fiber flags: this fiber's function ran. */ const PERFORMED_WORK = 0b1;
|
|
40
|
+
function componentName(type) {
|
|
41
|
+
var _named_render, _named_render1, _named_type, _named_type1;
|
|
42
|
+
if (!type || typeof type === 'string') return undefined;
|
|
43
|
+
const named = type;
|
|
44
|
+
return named.displayName || named.name || ((_named_render = named.render) == null ? void 0 : _named_render.displayName) || ((_named_render1 = named.render) == null ? void 0 : _named_render1.name) || ((_named_type = named.type) == null ? void 0 : _named_type.displayName) || ((_named_type1 = named.type) == null ? void 0 : _named_type1.name) || undefined;
|
|
45
|
+
}
|
|
46
|
+
function countCommit(current, into) {
|
|
47
|
+
const visit = (fiber)=>{
|
|
48
|
+
if (fiber.flags & PERFORMED_WORK) {
|
|
49
|
+
var _into_get;
|
|
50
|
+
const name = componentName(fiber.type);
|
|
51
|
+
if (name) into.set(name, ((_into_get = into.get(name)) != null ? _into_get : 0) + 1);
|
|
52
|
+
}
|
|
53
|
+
// A subtree React bailed out of keeps its old children: nothing in it ran.
|
|
54
|
+
if (fiber.alternate && fiber.alternate.child === fiber.child) return;
|
|
55
|
+
for(let child = fiber.child; child; child = child.sibling)visit(child);
|
|
56
|
+
};
|
|
57
|
+
visit(current);
|
|
58
|
+
}
|
|
59
|
+
/** Installs the counting hook; `react-dom` must be required after this. */ export function installFiberRenderCounter() {
|
|
60
|
+
const scope = globalThis;
|
|
61
|
+
const previous = scope.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
62
|
+
const counts = new Map();
|
|
63
|
+
const noop = ()=>undefined;
|
|
64
|
+
scope.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
|
65
|
+
isDisabled: false,
|
|
66
|
+
supportsFiber: true,
|
|
67
|
+
renderers: new Map(),
|
|
68
|
+
inject: ()=>1,
|
|
69
|
+
onCommitFiberRoot: (_renderer, root)=>countCommit(root.current, counts),
|
|
70
|
+
onCommitFiberUnmount: noop,
|
|
71
|
+
onPostCommitFiberRoot: noop,
|
|
72
|
+
onScheduleFiberRoot: noop,
|
|
73
|
+
setStrictMode: noop,
|
|
74
|
+
checkDCE: noop
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
rendered: (name)=>{
|
|
78
|
+
var _counts_get;
|
|
79
|
+
return (_counts_get = counts.get(name)) != null ? _counts_get : 0;
|
|
80
|
+
},
|
|
81
|
+
snapshot: ()=>Object.fromEntries(counts),
|
|
82
|
+
reset: ()=>counts.clear(),
|
|
83
|
+
uninstall: ()=>{
|
|
84
|
+
scope.__REACT_DEVTOOLS_GLOBAL_HOOK__ = previous;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=fiber-render-counter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/testing/fiber-render-counter.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\n/**\n * WHAT A KEYSTROKE ACTUALLY RE-RENDERED, read off React's own commits\n * (AGL-3423).\n *\n * A keystroke that re-renders a whole dialog is cheap to write and expensive\n * to run, and it is the cost that matters: when each keystroke takes long\n * enough, typed input queues up and is processed back to back, and a chip\n * Autocomplete re-rendered by every one of them leaves React's nested-update\n * count climbing until it throws #185. A spec can only hold that line if it\n * can SEE it, so this counts it, the way React DevTools' \"why did this\n * render\" does: a stand-in devtools hook receives every committed tree, and\n * a walk that skips each subtree React bailed out of counts the components\n * whose functions ran.\n *\n * The hook is read when `react-dom` is first evaluated, so install it, then\n * `jest.resetModules()`, then require React, `react-dom/client` and the\n * component under test from the fresh registry.\n */\n\n/**\n * The time a spec built on this needs, per test and per setup hook: each\n * loads React, MUI and the component under test into a fresh registry, which\n * on a loaded two-worker CI shard ran past jest's 30 s default (2026-09-30).\n * Set it file-wide with `jest.setTimeout`.\n */\nexport const FRESH_REGISTRY_TIMEOUT_MS = 120_000\n\ninterface Fiber {\n type: unknown\n flags: number\n child: Fiber | null\n sibling: Fiber | null\n alternate: Fiber | null\n}\n\n/** `PerformedWork` in React's fiber flags: this fiber's function ran. */\nconst PERFORMED_WORK = 0b1\n\nfunction componentName(type: unknown): string | undefined {\n if (!type || typeof type === 'string') return undefined\n const named = type as {\n displayName?: string\n name?: string\n render?: { displayName?: string; name?: string }\n type?: { displayName?: string; name?: string }\n }\n return (\n named.displayName ||\n named.name ||\n named.render?.displayName ||\n named.render?.name ||\n named.type?.displayName ||\n named.type?.name ||\n undefined\n )\n}\n\nfunction countCommit(current: Fiber, into: Map<string, number>): void {\n const visit = (fiber: Fiber) => {\n if (fiber.flags & PERFORMED_WORK) {\n const name = componentName(fiber.type)\n if (name) into.set(name, (into.get(name) ?? 0) + 1)\n }\n // A subtree React bailed out of keeps its old children: nothing in it ran.\n if (fiber.alternate && fiber.alternate.child === fiber.child) return\n for (let child = fiber.child; child; child = child.sibling) visit(child)\n }\n visit(current)\n}\n\nexport interface FiberRenderCounter {\n /** How many times each named component rendered since the last reset. */\n rendered(name: string): number\n /** Every name counted since the last reset, for a failure message. */\n snapshot(): Record<string, number>\n reset(): void\n uninstall(): void\n}\n\n/** Installs the counting hook; `react-dom` must be required after this. */\nexport function installFiberRenderCounter(): FiberRenderCounter {\n const scope = globalThis as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown }\n const previous = scope.__REACT_DEVTOOLS_GLOBAL_HOOK__\n const counts = new Map<string, number>()\n const noop = () => undefined\n scope.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {\n isDisabled: false,\n supportsFiber: true,\n renderers: new Map(),\n inject: () => 1,\n onCommitFiberRoot: (_renderer: number, root: { current: Fiber }) =>\n countCommit(root.current, counts),\n onCommitFiberUnmount: noop,\n onPostCommitFiberRoot: noop,\n onScheduleFiberRoot: noop,\n setStrictMode: noop,\n checkDCE: noop,\n }\n return {\n rendered: (name) => counts.get(name) ?? 0,\n snapshot: () => Object.fromEntries(counts),\n reset: () => counts.clear(),\n uninstall: () => {\n scope.__REACT_DEVTOOLS_GLOBAL_HOOK__ = previous\n },\n }\n}\n"],"names":["FRESH_REGISTRY_TIMEOUT_MS","PERFORMED_WORK","componentName","type","named","undefined","displayName","name","render","countCommit","current","into","visit","fiber","flags","set","get","alternate","child","sibling","installFiberRenderCounter","scope","globalThis","previous","__REACT_DEVTOOLS_GLOBAL_HOOK__","counts","Map","noop","isDisabled","supportsFiber","renderers","inject","onCommitFiberRoot","_renderer","root","onCommitFiberUnmount","onPostCommitFiberRoot","onScheduleFiberRoot","setStrictMode","checkDCE","rendered","snapshot","Object","fromEntries","reset","clear","uninstall"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;CAiBC,GAED;;;;;CAKC,GACD,OAAO,MAAMA,4BAA4B,OAAO;AAUhD,uEAAuE,GACvE,MAAMC,iBAAiB;AAEvB,SAASC,cAAcC,IAAa;QAWhCC,eACAA,gBACAA,aACAA;IAbF,IAAI,CAACD,QAAQ,OAAOA,SAAS,UAAU,OAAOE;IAC9C,MAAMD,QAAQD;IAMd,OACEC,MAAME,WAAW,IACjBF,MAAMG,IAAI,MACVH,gBAAAA,MAAMI,MAAM,qBAAZJ,cAAcE,WAAW,OACzBF,iBAAAA,MAAMI,MAAM,qBAAZJ,eAAcG,IAAI,OAClBH,cAAAA,MAAMD,IAAI,qBAAVC,YAAYE,WAAW,OACvBF,eAAAA,MAAMD,IAAI,qBAAVC,aAAYG,IAAI,KAChBF;AAEJ;AAEA,SAASI,YAAYC,OAAc,EAAEC,IAAyB;IAC5D,MAAMC,QAAQ,CAACC;QACb,IAAIA,MAAMC,KAAK,GAAGb,gBAAgB;gBAENU;YAD1B,MAAMJ,OAAOL,cAAcW,MAAMV,IAAI;YACrC,IAAII,MAAMI,KAAKI,GAAG,CAACR,MAAM,EAACI,YAAAA,KAAKK,GAAG,CAACT,iBAATI,YAAkB,KAAK;QACnD;QACA,2EAA2E;QAC3E,IAAIE,MAAMI,SAAS,IAAIJ,MAAMI,SAAS,CAACC,KAAK,KAAKL,MAAMK,KAAK,EAAE;QAC9D,IAAK,IAAIA,QAAQL,MAAMK,KAAK,EAAEA,OAAOA,QAAQA,MAAMC,OAAO,CAAEP,MAAMM;IACpE;IACAN,MAAMF;AACR;AAWA,yEAAyE,GACzE,OAAO,SAASU;IACd,MAAMC,QAAQC;IACd,MAAMC,WAAWF,MAAMG,8BAA8B;IACrD,MAAMC,SAAS,IAAIC;IACnB,MAAMC,OAAO,IAAMtB;IACnBgB,MAAMG,8BAA8B,GAAG;QACrCI,YAAY;QACZC,eAAe;QACfC,WAAW,IAAIJ;QACfK,QAAQ,IAAM;QACdC,mBAAmB,CAACC,WAAmBC,OACrCzB,YAAYyB,KAAKxB,OAAO,EAAEe;QAC5BU,sBAAsBR;QACtBS,uBAAuBT;QACvBU,qBAAqBV;QACrBW,eAAeX;QACfY,UAAUZ;IACZ;IACA,OAAO;QACLa,UAAU,CAACjC;gBAASkB;oBAAAA,cAAAA,OAAOT,GAAG,CAACT,iBAAXkB,cAAoB;;QACxCgB,UAAU,IAAMC,OAAOC,WAAW,CAAClB;QACnCmB,OAAO,IAAMnB,OAAOoB,KAAK;QACzBC,WAAW;YACTzB,MAAMG,8BAA8B,GAAGD;QACzC;IACF;AACF"}
|