@aglyn/shared-ui-jsx-forms 1.0.0-beta.163 → 1.0.0-beta.165

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-ui-jsx-forms",
3
- "version": "1.0.0-beta.163",
3
+ "version": "1.0.0-beta.165",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,13 +25,13 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-enums": "1.0.0-beta.163",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.163",
30
- "@aglyn/shared-ui-color-picker": "1.0.0-beta.163",
31
- "@aglyn/shared-ui-jsx": "1.0.0-beta.163",
32
- "@aglyn/shared-ui-theme": "1.0.0-beta.163",
33
- "@aglyn/shared-util-tools": "1.0.0-beta.163",
34
- "@aglyn/shared-util-vendor": "1.0.0-beta.163",
28
+ "@aglyn/shared-data-enums": "1.0.0-beta.165",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.165",
30
+ "@aglyn/shared-ui-color-picker": "1.0.0-beta.165",
31
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.165",
32
+ "@aglyn/shared-ui-theme": "1.0.0-beta.165",
33
+ "@aglyn/shared-util-tools": "1.0.0-beta.165",
34
+ "@aglyn/shared-util-vendor": "1.0.0-beta.165",
35
35
  "@data-driven-forms/common": "^4.2.0",
36
36
  "@data-driven-forms/react-form-renderer": "^4.2.0",
37
37
  "@swc/helpers": "0.5.23",
@@ -23,7 +23,7 @@ import { GridList } from "@aglyn/shared-ui-jsx/components/grid-list";
23
23
  import { generateComponentClassKeys, styled } from "@aglyn/shared-ui-theme";
24
24
  import { useDebouncedCallback } from "@aglyn/shared-util-vendor/use-debounce";
25
25
  import { Box, Button, ButtonBase, Collapse, Grid, Link as MuiLink, TextField as MuiTextField, Tooltip, Typography } from "@mui/material";
26
- import { forwardRef, useCallback, useEffect, useMemo, useState } from "react";
26
+ import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
27
27
  import FormFieldGrid, { buildFieldClear } from "../mapper/form-field-grid.js";
28
28
  import { validationMessage } from "../utils/validation-message.js";
29
29
  import { useFieldApi } from "../vendor/data-driven-forms.js";
@@ -31,6 +31,17 @@ const iconUnset = _extends({}, DEFAULT_ICON, {
31
31
  id: null,
32
32
  name: '(none)'
33
33
  });
34
+ /**
35
+ * What the element carries when the catalog has no icon under that id — a
36
+ * different thing from carrying nothing, and it has to READ differently
37
+ * (AGL-3266). The bundled set is one MDI release, so an id minted against a
38
+ * newer one (`creation-outline`, on aglyn.com's Aglyn AI card) resolves to
39
+ * nothing here. Naming it `(none)` hid that the element had an icon at all,
40
+ * and every pick then replaced a working icon the author could not see.
41
+ */ const iconUnknown = (id)=>_extends({}, DEFAULT_ICON, {
42
+ id,
43
+ name: id
44
+ });
34
45
  const classKeys = generateComponentClassKeys('AglynIconSelect', [
35
46
  'root',
36
47
  'button',
@@ -113,19 +124,45 @@ const GridListWrapper = styled('div', {
113
124
  const visibleIcons = useMemo(()=>icons.length > MAX_RENDERED_ICONS ? icons.slice(0, MAX_RENDERED_ICONS) : icons, [
114
125
  icons
115
126
  ]);
127
+ /**
128
+ * A new result set starts at its own top (AGL-3266).
129
+ *
130
+ * The grid is virtualized and keeps its scroll offset when `items`
131
+ * changes, so once the author had scrolled, every later search painted
132
+ * the MIDDLE of the new results. Searching `bullhorn` while scrolled
133
+ * showed Cellphone Play, Cellphone Settings… and the four Bullhorn
134
+ * icons — ranked first, correctly — sat off-screen above. It reads
135
+ * exactly like a search that ignores the query, which is how it was
136
+ * reported.
137
+ */ const gridRef = useRef(null);
138
+ useEffect(()=>{
139
+ var _gridRef_current_scrollToIndex, _gridRef_current;
140
+ (_gridRef_current = gridRef.current) == null ? void 0 : (_gridRef_current_scrollToIndex = _gridRef_current.scrollToIndex) == null ? void 0 : _gridRef_current_scrollToIndex.call(_gridRef_current, {
141
+ index: 0
142
+ });
143
+ }, [
144
+ visibleIcons
145
+ ]);
116
146
  const [currentIcon, selectedIcon] = useMemo(()=>{
117
- const findIcon = (id)=>allIcons.find((icon)=>icon.id === id);
118
- const currentIcon = currentValue && findIcon(currentValue) || iconUnset;
119
- const selectedIcon = selected && findIcon(selected) || iconUnset;
147
+ const resolve = (id)=>{
148
+ var _allIcons_find;
149
+ if (!id) return iconUnset;
150
+ // The catalog arrives async, so "not found yet" is not "not in the
151
+ // set" — keep saying `(none)` until it has loaded, or the opener
152
+ // flashes the raw id on every mount.
153
+ if (allIcons.length === 0) return iconUnset;
154
+ return (_allIcons_find = allIcons.find((icon)=>icon.id === id)) != null ? _allIcons_find : iconUnknown(id);
155
+ };
120
156
  return [
121
- currentIcon,
122
- selectedIcon
157
+ resolve(currentValue),
158
+ resolve(selected)
123
159
  ];
124
160
  }, [
125
161
  currentValue,
126
162
  selected,
127
163
  allIcons
128
164
  ]);
165
+ /** True once the catalog is in and still has nothing under that id. */ const currentIsUnknown = Boolean(currentValue) && currentIcon.id === currentValue && !allIcons.some((icon)=>icon.id === currentValue);
129
166
  const handleButtonClick = useCallback(()=>{
130
167
  setOpen((prev)=>!prev);
131
168
  }, [
@@ -323,7 +360,14 @@ const GridListWrapper = styled('div', {
323
360
  variant: "caption",
324
361
  color: "text.secondary",
325
362
  children: hasPendingPick ? `Not applied yet — press Choose to replace ${currentIcon.name}.` : 'Pick an icon, then press Choose to apply it.'
326
- })
363
+ }),
364
+ currentIsUnknown ? /*#__PURE__*/ _jsx(Typography, {
365
+ "data-aglyn-icon-unknown": "",
366
+ component: "div",
367
+ variant: "caption",
368
+ color: "text.secondary",
369
+ children: `This element carries ${currentValue}, which is not in the icon library — it renders on the site but has no preview here. Choosing another icon replaces it.`
370
+ }) : null
327
371
  ]
328
372
  }),
329
373
  /*#__PURE__*/ _jsx(Grid, {
@@ -347,6 +391,7 @@ const GridListWrapper = styled('div', {
347
391
  /*#__PURE__*/ _jsxs(GridListWrapper, {
348
392
  children: [
349
393
  /*#__PURE__*/ _jsx(GridList, _extends({
394
+ ref: gridRef,
350
395
  GridContainerProps: {
351
396
  spacing: 1
352
397
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx-forms/src/lib/components/icon-select.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n DEFAULT_ICON,\n type Icon,\n mdiCheck,\n mdiChevronDown,\n mdiChevronUp,\n} from '@aglyn/shared-data-mdi'\nimport { CardListItem, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useMdiIconsFuzzy } from '@aglyn/shared-ui-jsx/hooks/mdi-icon/use-mdi-icons-fuzzy'\nimport { GridList } from '@aglyn/shared-ui-jsx/components/grid-list'\nimport { generateComponentClassKeys, styled } from '@aglyn/shared-ui-theme'\nimport { useDebouncedCallback } from '@aglyn/shared-util-vendor/use-debounce'\nimport {\n Box,\n Button,\n ButtonBase,\n Collapse,\n Grid,\n Link as MuiLink,\n TextField as MuiTextField,\n Tooltip,\n Typography,\n} from '@mui/material'\n\nimport {\n forwardRef,\n type HTMLAttributes,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport FormFieldGrid, { buildFieldClear } from '../mapper/form-field-grid'\nimport { validationMessage } from '../utils/validation-message'\n\nimport {\n useFieldApi,\n type UseFieldApiConfig,\n} from '../vendor/data-driven-forms'\n\nconst iconUnset = { ...DEFAULT_ICON, id: null, name: '(none)' }\nconst classKeys = generateComponentClassKeys('AglynIconSelect', [\n 'root',\n 'button',\n 'icon',\n 'label',\n 'selected',\n 'opener',\n 'preview',\n 'collapse',\n 'collapseInner',\n 'gridListWrapper',\n 'gridList',\n])\n\nconst StyledCollapse = styled(Collapse, {\n name: 'AglynIconSelectCollapse',\n})(({ theme }) => ({\n '& .MuiCollapse-wrapper': {\n height: 412,\n },\n '& .MuiCollapse-wrapperInner': {\n paddingTop: theme.spacing(1),\n display: 'flex',\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 0,\n alignSelf: 'stretch',\n },\n}))\n\nconst GridListWrapper = styled('div', {\n name: 'AglynIconSelectListWrapper',\n})(({ theme }) => {\n const tv = (theme as any).vars || theme\n return {\n height: '100%',\n marginTop: theme.spacing(1),\n border: `1px solid ${tv.palette.divider}`,\n borderRadius: theme.shape.borderRadius,\n [`& .${classKeys.gridList}`]: {\n padding: theme.spacing(1),\n },\n }\n})\n\nexport interface IconSelectProps extends HTMLAttributes<HTMLDivElement> {\n initialValue?: string\n classes?: Partial<typeof classKeys>\n}\n\nexport interface IconSelectControlProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Picked icon id, or empty for \"nothing chosen yet\". */\n value?: string\n onChange?: (iconId: string) => void\n label?: JSX.Node\n helperText?: JSX.Node\n GridListProps?: Record<string, unknown>\n classes?: Partial<typeof classKeys>\n}\n\n/**\n * The picker itself, with no form library attached (AGL-1193).\n *\n * Extracted so surfaces outside the designer can offer the SAME picker\n * rather than growing a second one: the console sets a reusable\n * component's icon from a plain dialog and a plain detail form, neither of\n * which runs inside a data-driven-forms tree. The field below is now a\n * thin adapter over this, so the two can't drift.\n */\nexport const IconSelectControl = forwardRef<any, IconSelectControlProps>(\n (props, ref) => {\n const {\n value: currentValue = '',\n onChange,\n label,\n helperText: helperContent,\n GridListProps,\n ...rest\n } = props\n const [open, setOpen] = useState(false)\n const [icons, allIcons, applyFilter, clearFilter] = useMdiIconsFuzzy()\n /**\n * The DRAFT pick (AGL-2486). Picking is two steps: clicking an icon only\n * moves this, and `Choose` is the one thing that reaches `onChange` and\n * therefore the canvas.\n */\n const [selected, setSelected] = useState(currentValue)\n /**\n * Re-seeded from the live value whenever the panel opens or the value\n * changes underneath. Without this an abandoned pick survived the panel\n * being closed, so the NEXT `Choose` applied an icon the author had\n * already walked away from.\n */\n useEffect(() => {\n setSelected(currentValue)\n }, [currentValue, open])\n\n // Rendering all ~6,600 icons at once froze the panel (AGL-340): cap the\n // grid and let search narrow the rest.\n const MAX_RENDERED_ICONS = 240\n const visibleIcons = useMemo(\n () =>\n icons.length > MAX_RENDERED_ICONS\n ? icons.slice(0, MAX_RENDERED_ICONS)\n : icons,\n [icons],\n )\n\n const [currentIcon, selectedIcon] = useMemo(() => {\n const findIcon = (id: string) => allIcons.find((icon) => icon.id === id)\n const currentIcon = (currentValue && findIcon(currentValue)) || iconUnset\n const selectedIcon = (selected && findIcon(selected)) || iconUnset\n return [currentIcon, selectedIcon]\n }, [currentValue, selected, allIcons])\n\n const handleButtonClick = useCallback(() => {\n setOpen((prev) => !prev)\n }, [setOpen])\n /** Nothing to confirm until the draft differs from what is applied. */\n const hasPendingPick = selected !== currentValue\n const handleCancelButtonClick = useCallback(() => {\n // Closing re-seeds the draft (the effect above), so this only has to\n // close — but it says so explicitly, because \"cancel leaves the canvas\n // alone\" is the guarantee, not an emergent property of an effect.\n setSelected(currentValue)\n setOpen(false)\n }, [currentValue])\n const handleChooseButtonClick = useCallback(() => {\n if (selected === currentValue) return\n onChange?.(selected)\n setOpen(false)\n }, [onChange, selected, currentValue])\n const updateFilter = useDebouncedCallback((value?: string) => {\n if (value) {\n applyFilter(value)\n } else {\n clearFilter()\n }\n }, 300)\n const handleFilterChange = useCallback(\n (e) => {\n const target = e.currentTarget\n if (target && target.value) {\n updateFilter(target.value)\n } else {\n updateFilter()\n }\n },\n [updateFilter],\n )\n const handleItemClick = useCallback(\n (e, item: Icon) => {\n setSelected(item.id)\n },\n [setSelected],\n )\n\n const renderItemContent = useCallback(\n function RenderItemContent(item: Icon, key: number) {\n const isSelected = Boolean(selected) && selected === item.id\n return (\n <Tooltip\n key={item.id ?? key}\n title={item.name}\n disableInteractive\n enterDelay={375}\n enterNextDelay={745}\n >\n <CardListItem\n item={item}\n selected={isSelected}\n // A real toggle, so the pick is announced and not only\n // painted (AGL-2486).\n role=\"button\"\n aria-pressed={isSelected}\n onClick={(e) => handleItemClick(e, item)}\n >\n <div>\n <MdiIcon fontSize=\"medium\" path={item.path} />\n </div>\n {isSelected ? (\n // Belt and braces on the fill: a tick reads as \"picked\"\n // even where the tint is subtle, and gives the state\n // something to assert against.\n <Box\n data-aglyn-icon-selected=\"\"\n sx={{\n position: 'absolute',\n top: 2,\n right: 2,\n lineHeight: 0,\n fontSize: 14,\n borderRadius: '50%',\n bgcolor: 'primary.contrastText',\n color: 'primary.main',\n }}\n >\n <MdiIcon fontSize=\"inherit\" path={mdiCheck.path} />\n </Box>\n ) : null}\n </CardListItem>\n </Tooltip>\n )\n },\n [selected, handleItemClick],\n )\n\n return (\n <Grid ref={ref} container>\n <Grid spacing={2} container sx={{\n alignItems: 'center'\n }}>\n <Grid>\n <ButtonBase\n onClick={handleButtonClick}\n disableRipple\n sx={(theme) => ({ fontSize: theme.typography.pxToRem(38) })}\n title={currentIcon.name || 'Choose icon'}\n >\n <MdiIcon fontSize=\"inherit\" path={currentIcon.path} />\n </ButtonBase>\n </Grid>\n <Grid\n size={{\n sm: \"grow\"\n }}>\n <Typography component=\"div\" variant=\"caption\">\n {label}\n </Typography>\n <MuiLink\n onClick={handleButtonClick}\n variant=\"button\"\n component=\"button\"\n color=\"primary\"\n sx={{\n display: 'flex',\n flexDirection: 'row',\n justifyContent: 'flex-start',\n }}\n >\n <span>\n <MdiIcon\n fontSize=\"inherit\"\n path={open ? mdiChevronUp.path : mdiChevronDown.path}\n />\n </span>\n <span>{currentIcon.name}</span>\n </MuiLink>\n </Grid>\n </Grid>\n <Grid size={12}>\n <StyledCollapse in={open}>\n <Grid spacing={2} container sx={{\n alignItems: \"center\"\n }}>\n <Grid size={12}>\n <MuiTextField\n onChange={handleFilterChange}\n placeholder=\"Search icons...\"\n size=\"small\"\n helperText={helperContent}\n fullWidth\n />\n </Grid>\n <Grid size=\"grow\">\n <Typography component=\"div\" variant=\"body2\">\n Selected icon: <b>{selectedIcon.name}</b>\n </Typography>\n <Typography\n component=\"div\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {hasPendingPick\n ? `Not applied yet — press Choose to replace ${currentIcon.name}.`\n : 'Pick an icon, then press Choose to apply it.'}\n </Typography>\n </Grid>\n <Grid>\n <Button color=\"inherit\" onClick={handleCancelButtonClick}>\n Cancel\n </Button>\n </Grid>\n <Grid>\n <Button\n color=\"primary\"\n disabled={!hasPendingPick}\n onClick={handleChooseButtonClick}\n variant=\"contained\"\n >\n Choose\n </Button>\n </Grid>\n </Grid>\n\n <GridListWrapper>\n <GridList\n GridContainerProps={{ spacing: 1 }}\n GridItemProps={{ size: { xs: 2 } }}\n ListWrapperProps={{ className: classKeys.gridList }}\n items={visibleIcons}\n renderItemContent={renderItemContent}\n {...GridListProps}\n />\n {icons.length > visibleIcons.length ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', px: 1, pb: 1 }}\n >\n {`Showing ${visibleIcons.length} of ${icons.length} icons — search to narrow down.`}\n </Typography>\n ) : null}\n {allIcons.length === 0 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', px: 1, pb: 1 }}\n >\n {'Loading icon catalog…'}\n </Typography>\n ) : null}\n </GridListWrapper>\n </StyledCollapse>\n </Grid>\n </Grid>\n )\n },\n)\nIconSelectControl.displayName = 'IconSelectControl'\n\n/**\n * The data-driven-forms field: everything form-shaped (validation message,\n * helper/description precedence, the final-form input) resolved here, and\n * the picking itself delegated to {@link IconSelectControl}.\n *\n * Sits in the same {@link FormFieldGrid} every other field does, so the corner\n * controls a schema asks for — the help tip, the opt-in clear, a caller's own\n * — are where they are on every other field rather than silently dropped.\n */\nconst IconSelectComponent = forwardRef<any, IconSelectProps>((props, ref) => {\n const {\n input,\n isReadOnly,\n isDisabled,\n placeholder,\n isRequired,\n label,\n helperText,\n description,\n validateOnMount,\n meta,\n inputProps,\n GridListProps,\n help,\n clearable,\n FormFieldGridProps = {},\n ...rest\n } = useFieldApi(props as UseFieldApiConfig)\n\n const invalidMessage = validationMessage(meta, validateOnMount)\n const helperContent = [\n invalidMessage,\n (meta.touched || validateOnMount) && meta.warning,\n helperText,\n description,\n ].find((i) => Boolean(i))\n\n // The picker can only ever choose an icon, so without this nothing takes\n // one back off again.\n const clear = buildFieldClear({\n clearable,\n label,\n hasValue: Boolean(input.value),\n locked: Boolean(isDisabled || isReadOnly),\n onClear: () => input.onChange(''),\n })\n\n return (\n <FormFieldGrid help={help} clear={clear} {...FormFieldGridProps}>\n <IconSelectControl\n ref={ref}\n value={input.value}\n onChange={input.onChange}\n label={label}\n helperText={helperContent}\n GridListProps={GridListProps}\n />\n </FormFieldGrid>\n )\n})\n\nIconSelectComponent.displayName = 'IconSelectComponent'\nIconSelectComponent.aglyn = true\n\nexport default IconSelectComponent\n"],"names":["DEFAULT_ICON","mdiCheck","mdiChevronDown","mdiChevronUp","CardListItem","MdiIcon","useMdiIconsFuzzy","GridList","generateComponentClassKeys","styled","useDebouncedCallback","Box","Button","ButtonBase","Collapse","Grid","Link","MuiLink","TextField","MuiTextField","Tooltip","Typography","forwardRef","useCallback","useEffect","useMemo","useState","FormFieldGrid","buildFieldClear","validationMessage","useFieldApi","iconUnset","id","name","classKeys","StyledCollapse","theme","height","paddingTop","spacing","display","flexDirection","flexGrow","flexShrink","alignSelf","GridListWrapper","tv","vars","marginTop","border","palette","divider","borderRadius","shape","gridList","padding","IconSelectControl","props","ref","value","currentValue","onChange","label","helperText","helperContent","GridListProps","rest","open","setOpen","icons","allIcons","applyFilter","clearFilter","selected","setSelected","MAX_RENDERED_ICONS","visibleIcons","length","slice","currentIcon","selectedIcon","findIcon","find","icon","handleButtonClick","prev","hasPendingPick","handleCancelButtonClick","handleChooseButtonClick","updateFilter","handleFilterChange","e","target","currentTarget","handleItemClick","item","renderItemContent","RenderItemContent","key","isSelected","Boolean","title","disableInteractive","enterDelay","enterNextDelay","role","aria-pressed","onClick","div","fontSize","path","data-aglyn-icon-selected","sx","position","top","right","lineHeight","bgcolor","color","container","alignItems","disableRipple","typography","pxToRem","size","sm","component","variant","justifyContent","span","in","placeholder","fullWidth","b","disabled","GridContainerProps","GridItemProps","xs","ListWrapperProps","className","items","px","pb","displayName","IconSelectComponent","input","isReadOnly","isDisabled","isRequired","description","validateOnMount","meta","inputProps","help","clearable","FormFieldGridProps","invalidMessage","touched","warning","i","clear","hasValue","locked","onClear","aglyn"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx-forms/src/lib/components/icon-select.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n DEFAULT_ICON,\n type Icon,\n mdiCheck,\n mdiChevronDown,\n mdiChevronUp,\n} from '@aglyn/shared-data-mdi'\nimport { CardListItem, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useMdiIconsFuzzy } from '@aglyn/shared-ui-jsx/hooks/mdi-icon/use-mdi-icons-fuzzy'\nimport { GridList } from '@aglyn/shared-ui-jsx/components/grid-list'\nimport { generateComponentClassKeys, styled } from '@aglyn/shared-ui-theme'\nimport { useDebouncedCallback } from '@aglyn/shared-util-vendor/use-debounce'\nimport {\n Box,\n Button,\n ButtonBase,\n Collapse,\n Grid,\n Link as MuiLink,\n TextField as MuiTextField,\n Tooltip,\n Typography,\n} from '@mui/material'\n\nimport {\n forwardRef,\n type HTMLAttributes,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport FormFieldGrid, { buildFieldClear } from '../mapper/form-field-grid'\nimport { validationMessage } from '../utils/validation-message'\n\nimport {\n useFieldApi,\n type UseFieldApiConfig,\n} from '../vendor/data-driven-forms'\n\nconst iconUnset = { ...DEFAULT_ICON, id: null, name: '(none)' }\n/**\n * What the element carries when the catalog has no icon under that id — a\n * different thing from carrying nothing, and it has to READ differently\n * (AGL-3266). The bundled set is one MDI release, so an id minted against a\n * newer one (`creation-outline`, on aglyn.com's Aglyn AI card) resolves to\n * nothing here. Naming it `(none)` hid that the element had an icon at all,\n * and every pick then replaced a working icon the author could not see.\n */\nconst iconUnknown = (id: string) => ({ ...DEFAULT_ICON, id, name: id })\n\n/** The handle {@link GridList} forwards, narrowed to what is used here. */\ntype ScrollableGrid = { scrollToIndex?: (opts: { index: number }) => void }\nconst classKeys = generateComponentClassKeys('AglynIconSelect', [\n 'root',\n 'button',\n 'icon',\n 'label',\n 'selected',\n 'opener',\n 'preview',\n 'collapse',\n 'collapseInner',\n 'gridListWrapper',\n 'gridList',\n])\n\nconst StyledCollapse = styled(Collapse, {\n name: 'AglynIconSelectCollapse',\n})(({ theme }) => ({\n '& .MuiCollapse-wrapper': {\n height: 412,\n },\n '& .MuiCollapse-wrapperInner': {\n paddingTop: theme.spacing(1),\n display: 'flex',\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 0,\n alignSelf: 'stretch',\n },\n}))\n\nconst GridListWrapper = styled('div', {\n name: 'AglynIconSelectListWrapper',\n})(({ theme }) => {\n const tv = (theme as any).vars || theme\n return {\n height: '100%',\n marginTop: theme.spacing(1),\n border: `1px solid ${tv.palette.divider}`,\n borderRadius: theme.shape.borderRadius,\n [`& .${classKeys.gridList}`]: {\n padding: theme.spacing(1),\n },\n }\n})\n\nexport interface IconSelectProps extends HTMLAttributes<HTMLDivElement> {\n initialValue?: string\n classes?: Partial<typeof classKeys>\n}\n\nexport interface IconSelectControlProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Picked icon id, or empty for \"nothing chosen yet\". */\n value?: string\n onChange?: (iconId: string) => void\n label?: JSX.Node\n helperText?: JSX.Node\n GridListProps?: Record<string, unknown>\n classes?: Partial<typeof classKeys>\n}\n\n/**\n * The picker itself, with no form library attached (AGL-1193).\n *\n * Extracted so surfaces outside the designer can offer the SAME picker\n * rather than growing a second one: the console sets a reusable\n * component's icon from a plain dialog and a plain detail form, neither of\n * which runs inside a data-driven-forms tree. The field below is now a\n * thin adapter over this, so the two can't drift.\n */\nexport const IconSelectControl = forwardRef<any, IconSelectControlProps>(\n (props, ref) => {\n const {\n value: currentValue = '',\n onChange,\n label,\n helperText: helperContent,\n GridListProps,\n ...rest\n } = props\n const [open, setOpen] = useState(false)\n const [icons, allIcons, applyFilter, clearFilter] = useMdiIconsFuzzy()\n /**\n * The DRAFT pick (AGL-2486). Picking is two steps: clicking an icon only\n * moves this, and `Choose` is the one thing that reaches `onChange` and\n * therefore the canvas.\n */\n const [selected, setSelected] = useState(currentValue)\n /**\n * Re-seeded from the live value whenever the panel opens or the value\n * changes underneath. Without this an abandoned pick survived the panel\n * being closed, so the NEXT `Choose` applied an icon the author had\n * already walked away from.\n */\n useEffect(() => {\n setSelected(currentValue)\n }, [currentValue, open])\n\n // Rendering all ~6,600 icons at once froze the panel (AGL-340): cap the\n // grid and let search narrow the rest.\n const MAX_RENDERED_ICONS = 240\n const visibleIcons = useMemo(\n () =>\n icons.length > MAX_RENDERED_ICONS\n ? icons.slice(0, MAX_RENDERED_ICONS)\n : icons,\n [icons],\n )\n\n /**\n * A new result set starts at its own top (AGL-3266).\n *\n * The grid is virtualized and keeps its scroll offset when `items`\n * changes, so once the author had scrolled, every later search painted\n * the MIDDLE of the new results. Searching `bullhorn` while scrolled\n * showed Cellphone Play, Cellphone Settings… and the four Bullhorn\n * icons — ranked first, correctly — sat off-screen above. It reads\n * exactly like a search that ignores the query, which is how it was\n * reported.\n */\n const gridRef = useRef<ScrollableGrid>(null)\n useEffect(() => {\n gridRef.current?.scrollToIndex?.({ index: 0 })\n }, [visibleIcons])\n\n const [currentIcon, selectedIcon] = useMemo(() => {\n const resolve = (id: string) => {\n if (!id) return iconUnset\n // The catalog arrives async, so \"not found yet\" is not \"not in the\n // set\" — keep saying `(none)` until it has loaded, or the opener\n // flashes the raw id on every mount.\n if (allIcons.length === 0) return iconUnset\n return allIcons.find((icon) => icon.id === id) ?? iconUnknown(id)\n }\n return [resolve(currentValue), resolve(selected)]\n }, [currentValue, selected, allIcons])\n /** True once the catalog is in and still has nothing under that id. */\n const currentIsUnknown =\n Boolean(currentValue) &&\n currentIcon.id === currentValue &&\n !allIcons.some((icon) => icon.id === currentValue)\n\n const handleButtonClick = useCallback(() => {\n setOpen((prev) => !prev)\n }, [setOpen])\n /** Nothing to confirm until the draft differs from what is applied. */\n const hasPendingPick = selected !== currentValue\n const handleCancelButtonClick = useCallback(() => {\n // Closing re-seeds the draft (the effect above), so this only has to\n // close — but it says so explicitly, because \"cancel leaves the canvas\n // alone\" is the guarantee, not an emergent property of an effect.\n setSelected(currentValue)\n setOpen(false)\n }, [currentValue])\n const handleChooseButtonClick = useCallback(() => {\n if (selected === currentValue) return\n onChange?.(selected)\n setOpen(false)\n }, [onChange, selected, currentValue])\n const updateFilter = useDebouncedCallback((value?: string) => {\n if (value) {\n applyFilter(value)\n } else {\n clearFilter()\n }\n }, 300)\n const handleFilterChange = useCallback(\n (e) => {\n const target = e.currentTarget\n if (target && target.value) {\n updateFilter(target.value)\n } else {\n updateFilter()\n }\n },\n [updateFilter],\n )\n const handleItemClick = useCallback(\n (e, item: Icon) => {\n setSelected(item.id)\n },\n [setSelected],\n )\n\n const renderItemContent = useCallback(\n function RenderItemContent(item: Icon, key: number) {\n const isSelected = Boolean(selected) && selected === item.id\n return (\n <Tooltip\n key={item.id ?? key}\n title={item.name}\n disableInteractive\n enterDelay={375}\n enterNextDelay={745}\n >\n <CardListItem\n item={item}\n selected={isSelected}\n // A real toggle, so the pick is announced and not only\n // painted (AGL-2486).\n role=\"button\"\n aria-pressed={isSelected}\n onClick={(e) => handleItemClick(e, item)}\n >\n <div>\n <MdiIcon fontSize=\"medium\" path={item.path} />\n </div>\n {isSelected ? (\n // Belt and braces on the fill: a tick reads as \"picked\"\n // even where the tint is subtle, and gives the state\n // something to assert against.\n <Box\n data-aglyn-icon-selected=\"\"\n sx={{\n position: 'absolute',\n top: 2,\n right: 2,\n lineHeight: 0,\n fontSize: 14,\n borderRadius: '50%',\n bgcolor: 'primary.contrastText',\n color: 'primary.main',\n }}\n >\n <MdiIcon fontSize=\"inherit\" path={mdiCheck.path} />\n </Box>\n ) : null}\n </CardListItem>\n </Tooltip>\n )\n },\n [selected, handleItemClick],\n )\n\n return (\n <Grid ref={ref} container>\n <Grid spacing={2} container sx={{\n alignItems: 'center'\n }}>\n <Grid>\n <ButtonBase\n onClick={handleButtonClick}\n disableRipple\n sx={(theme) => ({ fontSize: theme.typography.pxToRem(38) })}\n title={currentIcon.name || 'Choose icon'}\n >\n <MdiIcon fontSize=\"inherit\" path={currentIcon.path} />\n </ButtonBase>\n </Grid>\n <Grid\n size={{\n sm: \"grow\"\n }}>\n <Typography component=\"div\" variant=\"caption\">\n {label}\n </Typography>\n <MuiLink\n onClick={handleButtonClick}\n variant=\"button\"\n component=\"button\"\n color=\"primary\"\n sx={{\n display: 'flex',\n flexDirection: 'row',\n justifyContent: 'flex-start',\n }}\n >\n <span>\n <MdiIcon\n fontSize=\"inherit\"\n path={open ? mdiChevronUp.path : mdiChevronDown.path}\n />\n </span>\n <span>{currentIcon.name}</span>\n </MuiLink>\n </Grid>\n </Grid>\n <Grid size={12}>\n <StyledCollapse in={open}>\n <Grid spacing={2} container sx={{\n alignItems: \"center\"\n }}>\n <Grid size={12}>\n <MuiTextField\n onChange={handleFilterChange}\n placeholder=\"Search icons...\"\n size=\"small\"\n helperText={helperContent}\n fullWidth\n />\n </Grid>\n <Grid size=\"grow\">\n <Typography component=\"div\" variant=\"body2\">\n Selected icon: <b>{selectedIcon.name}</b>\n </Typography>\n <Typography\n component=\"div\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {hasPendingPick\n ? `Not applied yet — press Choose to replace ${currentIcon.name}.`\n : 'Pick an icon, then press Choose to apply it.'}\n </Typography>\n {currentIsUnknown ? (\n <Typography\n data-aglyn-icon-unknown=\"\"\n component=\"div\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {`This element carries ${currentValue}, which is not in the icon library — it renders on the site but has no preview here. Choosing another icon replaces it.`}\n </Typography>\n ) : null}\n </Grid>\n <Grid>\n <Button color=\"inherit\" onClick={handleCancelButtonClick}>\n Cancel\n </Button>\n </Grid>\n <Grid>\n <Button\n color=\"primary\"\n disabled={!hasPendingPick}\n onClick={handleChooseButtonClick}\n variant=\"contained\"\n >\n Choose\n </Button>\n </Grid>\n </Grid>\n\n <GridListWrapper>\n <GridList\n ref={gridRef as any}\n GridContainerProps={{ spacing: 1 }}\n GridItemProps={{ size: { xs: 2 } }}\n ListWrapperProps={{ className: classKeys.gridList }}\n items={visibleIcons}\n renderItemContent={renderItemContent}\n {...GridListProps}\n />\n {icons.length > visibleIcons.length ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', px: 1, pb: 1 }}\n >\n {`Showing ${visibleIcons.length} of ${icons.length} icons — search to narrow down.`}\n </Typography>\n ) : null}\n {allIcons.length === 0 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', px: 1, pb: 1 }}\n >\n {'Loading icon catalog…'}\n </Typography>\n ) : null}\n </GridListWrapper>\n </StyledCollapse>\n </Grid>\n </Grid>\n )\n },\n)\nIconSelectControl.displayName = 'IconSelectControl'\n\n/**\n * The data-driven-forms field: everything form-shaped (validation message,\n * helper/description precedence, the final-form input) resolved here, and\n * the picking itself delegated to {@link IconSelectControl}.\n *\n * Sits in the same {@link FormFieldGrid} every other field does, so the corner\n * controls a schema asks for — the help tip, the opt-in clear, a caller's own\n * — are where they are on every other field rather than silently dropped.\n */\nconst IconSelectComponent = forwardRef<any, IconSelectProps>((props, ref) => {\n const {\n input,\n isReadOnly,\n isDisabled,\n placeholder,\n isRequired,\n label,\n helperText,\n description,\n validateOnMount,\n meta,\n inputProps,\n GridListProps,\n help,\n clearable,\n FormFieldGridProps = {},\n ...rest\n } = useFieldApi(props as UseFieldApiConfig)\n\n const invalidMessage = validationMessage(meta, validateOnMount)\n const helperContent = [\n invalidMessage,\n (meta.touched || validateOnMount) && meta.warning,\n helperText,\n description,\n ].find((i) => Boolean(i))\n\n // The picker can only ever choose an icon, so without this nothing takes\n // one back off again.\n const clear = buildFieldClear({\n clearable,\n label,\n hasValue: Boolean(input.value),\n locked: Boolean(isDisabled || isReadOnly),\n onClear: () => input.onChange(''),\n })\n\n return (\n <FormFieldGrid help={help} clear={clear} {...FormFieldGridProps}>\n <IconSelectControl\n ref={ref}\n value={input.value}\n onChange={input.onChange}\n label={label}\n helperText={helperContent}\n GridListProps={GridListProps}\n />\n </FormFieldGrid>\n )\n})\n\nIconSelectComponent.displayName = 'IconSelectComponent'\nIconSelectComponent.aglyn = true\n\nexport default IconSelectComponent\n"],"names":["DEFAULT_ICON","mdiCheck","mdiChevronDown","mdiChevronUp","CardListItem","MdiIcon","useMdiIconsFuzzy","GridList","generateComponentClassKeys","styled","useDebouncedCallback","Box","Button","ButtonBase","Collapse","Grid","Link","MuiLink","TextField","MuiTextField","Tooltip","Typography","forwardRef","useCallback","useEffect","useMemo","useRef","useState","FormFieldGrid","buildFieldClear","validationMessage","useFieldApi","iconUnset","id","name","iconUnknown","classKeys","StyledCollapse","theme","height","paddingTop","spacing","display","flexDirection","flexGrow","flexShrink","alignSelf","GridListWrapper","tv","vars","marginTop","border","palette","divider","borderRadius","shape","gridList","padding","IconSelectControl","props","ref","value","currentValue","onChange","label","helperText","helperContent","GridListProps","rest","open","setOpen","icons","allIcons","applyFilter","clearFilter","selected","setSelected","MAX_RENDERED_ICONS","visibleIcons","length","slice","gridRef","current","scrollToIndex","index","currentIcon","selectedIcon","resolve","find","icon","currentIsUnknown","Boolean","some","handleButtonClick","prev","hasPendingPick","handleCancelButtonClick","handleChooseButtonClick","updateFilter","handleFilterChange","e","target","currentTarget","handleItemClick","item","renderItemContent","RenderItemContent","key","isSelected","title","disableInteractive","enterDelay","enterNextDelay","role","aria-pressed","onClick","div","fontSize","path","data-aglyn-icon-selected","sx","position","top","right","lineHeight","bgcolor","color","container","alignItems","disableRipple","typography","pxToRem","size","sm","component","variant","justifyContent","span","in","placeholder","fullWidth","b","data-aglyn-icon-unknown","disabled","GridContainerProps","GridItemProps","xs","ListWrapperProps","className","items","px","pb","displayName","IconSelectComponent","input","isReadOnly","isDisabled","isRequired","description","validateOnMount","meta","inputProps","help","clearable","FormFieldGridProps","invalidMessage","touched","warning","i","clear","hasValue","locked","onClear","aglyn"],"mappings":"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"}