dinocollab-core 2.2.31 → 2.2.33
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/dist/src/data-surface/index.create.js +1 -1
- package/dist/src/data-surface/index.create.js.map +1 -1
- package/dist/src/data-surface/ui.units.js +1 -1
- package/dist/src/data-surface/ui.units.js.map +1 -1
- package/dist/src/data-surface/view-grid/index.js +1 -1
- package/dist/src/data-surface/view-grid/index.js.map +1 -1
- package/dist/src/data-surface/view-list/index.js +1 -1
- package/dist/src/data-surface/view-list/index.js.map +1 -1
- package/dist/src/filter-bar/convert-to-graphql.js +1 -1
- package/dist/src/filter-bar/convert-to-graphql.js.map +1 -1
- package/dist/src/filter-bar/index.create.js +1 -1
- package/dist/src/filter-bar/index.create.js.map +1 -1
- package/dist/src/filter-bar/local-filter-builder.js +1 -1
- package/dist/src/filter-bar/local-filter-builder.js.map +1 -1
- package/dist/src/filter-bar/menu/create-form-field-number.js +1 -1
- package/dist/src/filter-bar/menu/create-form-field-number.js.map +1 -1
- package/dist/src/filter-bar/menu/create-form-field-select-multiple.js +1 -1
- package/dist/src/filter-bar/menu/create-form-field-select-multiple.js.map +1 -1
- package/dist/src/filter-bar/menu/create-form-field-select.js +1 -1
- package/dist/src/filter-bar/menu/create-form-field-select.js.map +1 -1
- package/dist/src/filter-bar/menu/create-form-field-string.js +1 -1
- package/dist/src/filter-bar/menu/create-form-field-string.js.map +1 -1
- package/dist/src/filter-bar/types.js.map +1 -1
- package/dist/types/filter-bar/index.create.d.ts +1 -1
- package/dist/types/filter-bar/menu/create-form-field-select.d.ts +2 -0
- package/dist/types/filter-bar/menu/types.d.ts +1 -1
- package/dist/types/filter-bar/types.d.ts +2 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-form-field-number.js","sources":["../../../../src/filter-bar/menu/create-form-field-number.tsx"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n// imports\r\nimport { createRef, useMemo, useState } from 'react'\r\nimport { Box, Button, styled, TextField, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'\r\nimport { createChipViewers } from '../components/chip-viewer'\r\nimport { getErrorMessage } from '../../form/helpers'\r\nimport { ButtonBack, FilterLogicToggle } from '../components/ui.units'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\n// types\r\nimport type { FC } from 'react'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { TChipViewerGroup } from '../components/chip-viewer'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic, TNumberOperator } from '../types'\r\n\r\n// ---------------------------------------------------------------------------\r\n// Constants\r\n// ---------------------------------------------------------------------------\r\n\r\nconst OPERATOR_SYMBOLS: Record<TNumberOperator, string> = {\r\n eq: '=',\r\n lt: '<',\r\n lte: '≤',\r\n gt: '>',\r\n gte: '≥'\r\n}\r\n\r\nconst OPERATORS: TNumberOperator[] = ['eq', 'lt', 'lte', 'gt', 'gte']\r\n\r\n// ---------------------------------------------------------------------------\r\n// Encode / decode helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Encodes operator + number into a single string stored in `values[]`. e.g. `\"gt:18\"` */\r\nexport function encodeNumberValue(operator: TNumberOperator, num: number): string {\r\n return `${operator}:${num}`\r\n}\r\n\r\n/** Decodes an encoded number value back into its parts. Returns `null` if invalid. */\r\nexport function decodeNumberValue(encoded: TFieldValid): { operator: TNumberOperator; num: number } | null {\r\n if (typeof encoded !== 'string') return null\r\n const colonIdx = encoded.indexOf(':')\r\n if (colonIdx === -1) return null\r\n const op = encoded.slice(0, colonIdx) as TNumberOperator\r\n const num = parseFloat(encoded.slice(colonIdx + 1))\r\n if (!OPERATORS.includes(op) || isNaN(num)) return null\r\n return { operator: op, num }\r\n}\r\n\r\n/** Returns a human-readable chip label for an encoded number value, e.g. `\"> 18\"`. */\r\nexport function formatNumberChipLabel(encoded: TFieldValid): string {\r\n const decoded = decodeNumberValue(encoded)\r\n if (!decoded) return String(encoded)\r\n return `${OPERATOR_SYMBOLS[decoded.operator]} ${decoded.num}`\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public interfaces\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Props for the `FormFieldNumber` component returned by `createFormFieldNumber`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldNumberProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** Parameters passed to `createFormFieldNumber` to configure the generated component. */\r\nexport interface IFormFieldNumberParam<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n /** Default operator shown when the menu first opens. @default 'eq' */\r\n defaultOperator?: TNumberOperator\r\n /** Step for the number input. @default 1 */\r\n step?: number\r\n /** Min allowed value */\r\n min?: number\r\n /** Max allowed value */\r\n max?: number\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Factory\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Factory function that creates a `FormFieldNumber` filter-menu component.\r\n *\r\n * The generated component renders a number input with a comparison-operator\r\n * toggle (`=`, `<`, `≤`, `>`, `≥`) inside a popper/menu panel. It supports:\r\n * - OR / AND logic toggle when more than one value is applied\r\n * - Chip viewers showing the currently applied values with operator labels\r\n * - Auto-focus and input reset after each successful submission\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * Each submitted value is encoded as `\"<operator>:<number>\"` (e.g. `\"gt:18\"`)\r\n * and stored in `TFieldValue.values[]`. Use `decodeNumberValue` / `formatNumberChipLabel`\r\n * to decode them when building query logic.\r\n *\r\n * @param params - Static configuration (optional field config override, default operator, step, min, max)\r\n * @returns A React FC ready to be used as a number filter-menu field component\r\n */\r\nfunction createFormFieldNumber<T>(params?: IFormFieldNumberParam<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n\r\n const FormFieldNumber: FC<IFormFieldNumberProps<T>> = (props) => {\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const refInput = createRef<HTMLInputElement>()\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'and' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n const [operator, setOperator] = useState<TNumberOperator>(params?.defaultOperator ?? 'eq')\r\n const [inputValue, setInputValue] = useState('')\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const hasLogicChange = value.values.length >= 2 && filterLogic !== value.logic\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n\r\n const rawNum = parseFloat(refInput.current?.value ?? '')\r\n\r\n // Submit logic change only (no new value)\r\n if (isNaN(rawNum)) {\r\n if (hasLogicChange) {\r\n handleSubmit({ values: value.values, logic: filterLogic })\r\n }\r\n return\r\n }\r\n\r\n const obj = { [mergedConfig.field]: rawNum } as Partial<TFieldModelValid<T>>\r\n const validationError = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(validationError || {})\r\n\r\n if (!validationError || Object.keys(validationError).length === 0) {\r\n const encoded = encodeNumberValue(operator, rawNum)\r\n const newValue: TFieldValue = { values: [encoded], logic: filterLogic }\r\n handleSubmit(newValue)\r\n\r\n if (refInput.current) {\r\n refInput.current.blur()\r\n refInput.current.value = ''\r\n }\r\n setInputValue('')\r\n }\r\n }\r\n\r\n const error = getErrorMessage(errorData, mergedConfig.field)\r\n\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return {\r\n field: mergedConfig.field,\r\n items: items.map((v) => ({ value: v, label: formatNumberChipLabel(v) }))\r\n }\r\n }, [mergedConfig.field, value])\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n }\r\n\r\n const hasValidInput = inputValue.trim() !== '' && !isNaN(parseFloat(inputValue))\r\n const isApplyDisabled = !hasValidInput && !hasLogicChange\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return null\r\n if (!value.values || value.values.length < 2) return null\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={filterLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ display: 'block', mb: 1 }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <ToggleButtonGroup\r\n exclusive\r\n size='small'\r\n value={operator}\r\n onChange={(_, val) => {\r\n if (val) setOperator(val)\r\n }}\r\n sx={{ mb: 1, width: '100%', '.MuiToggleButton-root': { flex: 1, py: 0.25, fontSize: '0.8rem' } }}\r\n >\r\n {OPERATORS.map((op) => (\r\n <ToggleButton key={op} value={op}>\r\n {OPERATOR_SYMBOLS[op]}\r\n </ToggleButton>\r\n ))}\r\n </ToggleButtonGroup>\r\n <TextField\r\n inputRef={refInput}\r\n autoFocus\r\n type='number'\r\n name={mergedConfig.field.toString()}\r\n size='small'\r\n fullWidth\r\n placeholder='Enter number'\r\n error={error.error}\r\n helperText={error.message}\r\n inputProps={{ step: params?.step ?? 1, min: params?.min, max: params?.max }}\r\n onChange={(e) => setInputValue(e.target.value)}\r\n sx={{ '.MuiInputBase-root': { minHeight: '42px' } }}\r\n />\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained' disabled={isApplyDisabled}>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldNumber\r\n}\r\n\r\nexport default createFormFieldNumber\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n }\r\n})\r\n\r\n// ---------------------------------------------------------------------------\r\n// Utilities\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Creates a labelFormatter for use in summaryConfig.\r\n * Formats encoded number values (e.g. `\"gt:18\"`) to a human-readable label (e.g. `\"> 18\"`).\r\n *\r\n * @example\r\n * summaryConfig: {\r\n * labelFormatter: { age: formatterNumber() }\r\n * }\r\n */\r\nexport function formatterNumber() {\r\n return (value: TFieldValid): string | undefined => {\r\n return formatNumberChipLabel(value)\r\n }\r\n}\r\n"],"names":["OPERATOR_SYMBOLS","eq","lt","lte","gt","gte","OPERATORS","encodeNumberValue","operator","num","concat","decodeNumberValue","encoded","colonIdx","indexOf","op","slice","parseFloat","includes","isNaN","formatNumberChipLabel","decoded","String","createFormFieldNumber","params","ChipViewers","createChipViewers","props","_mergedConfig$default","_params$defaultOperat","_mergedConfig$label","_params$step","mergedConfig","useMemo","Object","assign","currentConfig","config","refInput","createRef","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","_useState3","defaultOperator","_useState4","setOperator","_useState5","_useState6","inputValue","setInputValue","label","field","toString","_useState7","_useState8","errorData","setErrorData","handleSubmit","newValue","onSubmit","hasLogicChange","length","error","getErrorMessage","filterViewerValue","items","Array","isArray","map","v","isApplyDisabled","trim","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_refInput$current$val","_refInput$current","_props$validator","preventDefault","stopPropagation","rawNum","current","obj","_defineProperty","validationError","validator","run","keys","blur","children","_jsxs","PopperContent","title","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","FilterLogicToggle","sx","ml","onChange","_","nVal","PopperBody","description","Typography","variant","color","display","mb","borderBottom","placement","enableMinimalesticView","onRemove","ToggleButtonGroup","exclusive","val","width","flex","py","fontSize","ToggleButton","TextField","inputRef","autoFocus","type","name","fullWidth","placeholder","helperText","message","inputProps","step","min","max","e","target","minHeight","PopperFooter","Button","disabled","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","styled","position","content","inset","backgroundColor","filter","zIndex","opacity","transition","visibility","pointerEvents","formatterNumber"],"mappings":"8nBAoBA,IAAMA,EAAoD,CACxDC,GAAI,IACJC,GAAI,IACJC,IAAK,IACLC,GAAI,IACJC,IAAK,KAGDC,EAA+B,CAAC,KAAM,KAAM,MAAO,KAAM,OAO/C,SAAAC,EAAkBC,EAA2BC,GAC3D,MAAA,GAAAC,OAAUF,EAAQE,KAAAA,OAAID,EACxB,CAGM,SAAUE,EAAkBC,GAChC,GAAuB,iBAAZA,EAAsB,OAAO,KACxC,IAAMC,EAAWD,EAAQE,QAAQ,KACjC,IAAmB,IAAfD,EAAiB,OAAO,KAC5B,IAAME,EAAKH,EAAQI,MAAM,EAAGH,GACtBJ,EAAMQ,WAAWL,EAAQI,MAAMH,EAAW,IAChD,OAAKP,EAAUY,SAASH,IAAOI,MAAMV,GAAa,KAC3C,CAAED,SAAUO,EAAIN,IAAAA,EACzB,CAGM,SAAUW,EAAsBR,GACpC,IAAMS,EAAUV,EAAkBC,GAClC,OAAKS,EACL,GAAAX,OAAUV,EAAiBqB,EAAQb,UAASE,KAAAA,OAAIW,EAAQZ,KADnCa,OAAOV,EAE9B,CA6CA,SAASW,EAAyBC,GAChC,IAAMC,EAAcC,IA2JpB,OAzJsD,SAACC,GAAS,IAAAC,EAAAC,EAAAC,EAAAC,EACxDC,EAAeC,EAAQ,WAAA,OAAMC,OAAOC,OAAO,GAAIR,EAAMS,cAAeZ,eAAAA,EAAQa,OAAO,EAAE,CAACb,aAAAA,EAAAA,EAAQa,OAAQV,EAAMS,gBAE5GE,EAAWC,IACjBC,EAA+Eb,EAAvEc,MAAAA,OAAQ,IAAHD,EAAG,CAAEE,OAAQ,GAAIC,MAAiC,QAA5Bf,EAAEI,aAAY,EAAZA,EAAcY,oBAAY,IAAAhB,EAAAA,EAAI,OAAOY,EAC1EK,EAAsCC,EAAiBL,EAAME,OAAOI,EAAAC,EAAAH,EAAA,GAA7DI,EAAWF,EAAA,GAAEG,EAAcH,EAAA,GAClCI,EAAgCL,EAAiDjB,QAAzCA,EAAkBL,eAAAA,EAAQ4B,2BAAevB,EAAAA,EAAI,MAAKwB,EAAAL,EAAAG,EAAA,GAAnF3C,EAAQ6C,EAAA,GAAEC,EAAWD,EAAA,GAC5BE,EAAoCT,EAAS,IAAGU,EAAAR,EAAAO,EAAA,GAAzCE,EAAUD,EAAA,GAAEE,EAAaF,EAAA,GAE1BG,UAAK7B,EAAGE,aAAAA,EAAAA,EAAc2B,aAAK,IAAA7B,EAAAA,EAAIE,EAAa4B,MAAMC,WAExDC,EAAkChB,EAA6C,IAAGiB,EAAAf,EAAAc,EAAA,GAA3EE,EAASD,EAAA,GAAEE,EAAYF,EAAA,GAExBG,EAAe,SAACC,GACpBxC,EAAMyC,SAASpC,EAAa4B,MAAOO,EAAUnC,EAC9C,EAEKqC,EAAiB5B,EAAMC,OAAO4B,QAAU,GAAKrB,IAAgBR,EAAME,MAkCnE4B,GAAQC,EAAgBR,EAAWhC,EAAa4B,OAEhDa,GAAoBxC,EAA6B,WACrD,IAAMyC,EAAQC,MAAMC,QAAQnC,EAAMC,QAAUD,EAAMC,OAAS,CAACD,EAAMC,QAClE,MAAO,CACLkB,MAAO5B,EAAa4B,MACpBc,MAAOA,EAAMG,IAAI,SAACC,GAAC,MAAM,CAAErC,MAAOqC,EAAGnB,MAAOvC,EAAsB0D,GAAK,GAE1E,EAAE,CAAC9C,EAAa4B,MAAOnB,IAOlBsC,KADsC,KAAtBtB,EAAWuB,SAAkB7D,MAAMF,WAAWwC,OACzBY,EAarCY,GAAwB,GAG9B,OAFItD,EAAMuD,WAAWD,GAAYE,KAAK,YAGpCC,EAACC,EAAU,CAACC,UAAWL,GAAYM,KAAK,KAAMC,cAAWpB,SAhElC,SAACqB,GAA2C,IAAAC,EAAAC,EAAAC,EACnEH,EAAMI,iBACNJ,EAAMK,kBAEN,IAAMC,EAAS9E,WAAkC,QAAxByE,UAAAC,EAACrD,EAAS0D,eAAO,IAAAL,OAAA,EAAhBA,EAAkBlD,aAAKiD,IAAAA,EAAAA,EAAI,IAGrD,GAAIvE,MAAM4E,GACJ1B,GACFH,EAAa,CAAExB,OAAQD,EAAMC,OAAQC,MAAOM,QAFhD,CAOA,IAAMgD,EAAGC,EAAA,CAAA,EAAMlE,EAAa4B,MAAQmC,GAC9BI,EAAiCP,QAAlBA,EAAGjE,EAAMyE,qBAASR,SAAfA,EAAiBS,IAAIJ,GAI7C,GAFAhC,EAAakC,GAAmB,KAE3BA,GAA2D,IAAxCjE,OAAOoE,KAAKH,GAAiB7B,OAAc,CACjE,IAAM1D,EAAUL,EAAkBC,EAAUuF,GAE5C7B,EAD8B,CAAExB,OAAQ,CAAC9B,GAAU+B,MAAOM,IAGtDX,EAAS0D,UACX1D,EAAS0D,QAAQO,OACjBjE,EAAS0D,QAAQvD,MAAQ,IAE3BiB,EAAc,GACf,CAjBA,CAkBF,EAkCoF8C,SACjFC,EAACC,EAAa,CACZC,MAAKjG,aAAAA,OAAeiD,GACpBiD,QAASjF,EAAMiF,QACfC,MAAO,CACLC,YAAa1B,EAAC2B,EAAU,CAACC,KAAK,QAAQC,QAAStF,EAAMuF,SACrDC,WAfFnF,EAAaoF,cACZ3E,EAAMC,QAAUD,EAAMC,OAAO4B,OAAS,EADN,KAE9Bc,EAACiC,EAAkB,CAAAC,GAAI,CAAEC,GAAI,GAAK9E,MAAOQ,EAAauE,SAAU,SAACC,EAAGC,GAd3ExE,EAcsGwE,EAAK,KAgBvGlB,SAAA,CAAAC,EAACkB,EACE,CAAAnB,SAAA,CAAAxE,EAAa4F,aACZxC,EAACyC,GAAWC,QAAQ,UAAUC,MAAM,iBAAiBT,GAAI,CAAEU,QAAS,QAASC,GAAI,GAAGzB,SACjFxE,EAAa4F,cAGlBxC,EAAC3D,EAAW,CACV6F,GAAI,CAAEW,GAAI,EAAGC,aAAc,kBAC3BvE,MAAM,UACNwE,UAAU,aACVC,0BACA3F,MAAOgC,GACP4D,SAAU1G,EAAM0G,WAElBjD,EAACkD,EAAiB,CAChBC,WACA,EAAAvB,KAAK,QACLvE,MAAOjC,EACPgH,SAAU,SAACC,EAAGe,GACRA,GAAKlF,EAAYkF,EACtB,EACDlB,GAAI,CAAEW,GAAI,EAAGQ,MAAO,OAAQ,wBAAyB,CAAEC,KAAM,EAAGC,GAAI,IAAMC,SAAU,WAEnFpC,SAAAlG,EAAUuE,IAAI,SAAC9D,GAAE,OAChBqE,EAACyD,EAAY,CAAUpG,MAAO1B,EAAEyF,SAC7BxG,EAAiBe,IADDA,EAGpB,KAEHqE,EAAC0D,EAAS,CACRC,SAAUzG,EACV0G,WAAS,EACTC,KAAK,SACLC,KAAMlH,EAAa4B,MAAMC,WACzBmD,KAAK,QACLmC,WACA,EAAAC,YAAY,eACZ7E,MAAOA,GAAMA,MACb8E,WAAY9E,GAAM+E,QAClBC,WAAY,CAAEC,KAAkBzH,QAAdA,EAAEP,aAAM,EAANA,EAAQgI,YAAIzH,IAAAA,EAAAA,EAAI,EAAG0H,IAAKjI,aAAAA,EAAAA,EAAQiI,IAAKC,IAAKlI,aAAM,EAANA,EAAQkI,KACtElC,SAAU,SAACmC,GAAC,OAAKjG,EAAciG,EAAEC,OAAOnH,MAAM,EAC9C6E,GAAI,CAAE,qBAAsB,CAAEuC,UAAW,cAG7CpD,EAACqD,EACC,CAAAtD,SAAA,CAAApB,EAAC2E,EAAM,CAAC/C,KAAK,QAAQe,MAAM,QAAQD,QAAQ,OAAOkC,UAAWvH,EAAMC,QAAkC,IAAxBD,EAAMC,OAAO4B,OAAc2C,QArEzF,WAAK,IAAAgD,EAAAC,UAC1BD,EAAAtI,EAAMwI,qBAAa,IAAAF,GAAnBA,EAAAG,KAAAzI,EAAsBK,EAAa4B,QACK,KAApCpC,SAAc,QAAR0I,EAAN1I,EAAQa,cAAR6H,IAAcA,OAAdA,EAAAA,EAAgBG,kBAA2B1I,EAAMiF,SACtD,EAoEgBJ,SAAA,cACTpB,EAACkF,EAAI,CAAAhD,GAAI,CAAEoB,KAAM,KACjBtD,EAAC2E,EAAO,CAAA/C,KAAK,QAAQe,MAAM,UAAUD,QAAQ,OAAOb,QAAStF,EAAMiF,QAE1DJ,SAAA,WACTpB,EAAC2E,EAAO,CAAA/C,KAAK,QAAQiC,KAAK,SAASlB,MAAM,UAAUD,QAAQ,YAAYkC,SAAUjF,GAExEyB,SAAA,iBAKlB,CAGH,CAIA,IAAMnB,EAAakF,EAAO,OAAPA,CAAe,CAChCC,SAAU,WACV,WAAY,CACVC,QAAS,KACTzC,QAAS,QACTwC,SAAU,WACVE,MAAO,EACPC,gBAAiB,qBACjBC,OAAQ,YACRC,QAAU,EACVC,QAAS,EACTC,WAAY,eACZC,WAAY,UAEd,aAAc,CACZC,cAAe,OACf,WAAY,CACVJ,OAAQ,EACRC,QAAS,EACTE,WAAY,uBAkBFE,IACd,OAAO,SAACzI,GACN,OAAOrB,EAAsBqB,EAC9B,CACH"}
|
|
1
|
+
{"version":3,"file":"create-form-field-number.js","sources":["../../../../src/filter-bar/menu/create-form-field-number.tsx"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n// imports\r\nimport { createRef, useMemo, useState } from 'react'\r\nimport { Box, Button, styled, TextField, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'\r\nimport { createChipViewers } from '../components/chip-viewer'\r\nimport { getErrorMessage } from '../../form/helpers'\r\nimport { ButtonBack, FilterLogicToggle } from '../components/ui.units'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\n// types\r\nimport type { FC } from 'react'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { TChipViewerGroup } from '../components/chip-viewer'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic, TNumberOperator } from '../types'\r\n\r\n// ---------------------------------------------------------------------------\r\n// Constants\r\n// ---------------------------------------------------------------------------\r\n\r\nconst OPERATOR_SYMBOLS: Record<TNumberOperator, string> = {\r\n eq: '=',\r\n lt: '<',\r\n lte: '≤',\r\n gt: '>',\r\n gte: '≥'\r\n}\r\n\r\nconst OPERATORS: TNumberOperator[] = ['eq', 'lt', 'lte', 'gt', 'gte']\r\n\r\n// ---------------------------------------------------------------------------\r\n// Encode / decode helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Encodes operator + number into a single string stored in `values[]`. e.g. `\"gt:18\"` */\r\nexport function encodeNumberValue(operator: TNumberOperator, num: number): string {\r\n return `${operator}:${num}`\r\n}\r\n\r\n/** Decodes an encoded number value back into its parts. Returns `null` if invalid. */\r\nexport function decodeNumberValue(encoded: TFieldValid): { operator: TNumberOperator; num: number } | null {\r\n if (typeof encoded !== 'string') return null\r\n const colonIdx = encoded.indexOf(':')\r\n if (colonIdx === -1) return null\r\n const op = encoded.slice(0, colonIdx) as TNumberOperator\r\n const num = parseFloat(encoded.slice(colonIdx + 1))\r\n if (!OPERATORS.includes(op) || isNaN(num)) return null\r\n return { operator: op, num }\r\n}\r\n\r\n/** Returns a human-readable chip label for an encoded number value, e.g. `\"> 18\"`. */\r\nexport function formatNumberChipLabel(encoded: TFieldValid): string {\r\n const decoded = decodeNumberValue(encoded)\r\n if (!decoded) return String(encoded)\r\n return `${OPERATOR_SYMBOLS[decoded.operator]} ${decoded.num}`\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public interfaces\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Props for the `FormFieldNumber` component returned by `createFormFieldNumber`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldNumberProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** Parameters passed to `createFormFieldNumber` to configure the generated component. */\r\nexport interface IFormFieldNumberParam<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n /** Default operator shown when the menu first opens. @default 'eq' */\r\n defaultOperator?: TNumberOperator\r\n /** Step for the number input. @default 1 */\r\n step?: number\r\n /** Min allowed value */\r\n min?: number\r\n /** Max allowed value */\r\n max?: number\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Factory\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Factory function that creates a `FormFieldNumber` filter-menu component.\r\n *\r\n * The generated component renders a number input with a comparison-operator\r\n * toggle (`=`, `<`, `≤`, `>`, `≥`) inside a popper/menu panel. It supports:\r\n * - OR / AND logic toggle when more than one value is applied\r\n * - Chip viewers showing the currently applied values with operator labels\r\n * - Auto-focus and input reset after each successful submission\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * Each submitted value is encoded as `\"<operator>:<number>\"` (e.g. `\"gt:18\"`)\r\n * and stored in `TFieldValue.values[]`. Use `decodeNumberValue` / `formatNumberChipLabel`\r\n * to decode them when building query logic.\r\n *\r\n * @param params - Static configuration (optional field config override, default operator, step, min, max)\r\n * @returns A React FC ready to be used as a number filter-menu field component\r\n */\r\nfunction createFormFieldNumber<T>(params?: IFormFieldNumberParam<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n\r\n const FormFieldNumber: FC<IFormFieldNumberProps<T>> = (props) => {\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const refInput = createRef<HTMLInputElement>()\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'or' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n const [operator, setOperator] = useState<TNumberOperator>(params?.defaultOperator ?? 'eq')\r\n const [inputValue, setInputValue] = useState('')\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const hasLogicChange = filterLogic !== value.logic\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n\r\n const rawNum = parseFloat(refInput.current?.value ?? '')\r\n\r\n // Submit logic change only (no new value)\r\n if (isNaN(rawNum)) {\r\n if (hasLogicChange) {\r\n handleSubmit({ values: value.values, logic: filterLogic })\r\n }\r\n return\r\n }\r\n\r\n const obj = { [mergedConfig.field]: rawNum } as Partial<TFieldModelValid<T>>\r\n const validationError = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(validationError || {})\r\n\r\n if (!validationError || Object.keys(validationError).length === 0) {\r\n const encoded = encodeNumberValue(operator, rawNum)\r\n const newValue: TFieldValue = { values: [encoded], logic: filterLogic }\r\n handleSubmit(newValue)\r\n\r\n if (refInput.current) {\r\n refInput.current.blur()\r\n refInput.current.value = ''\r\n }\r\n setInputValue('')\r\n }\r\n }\r\n\r\n const error = getErrorMessage(errorData, mergedConfig.field)\r\n\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return {\r\n field: mergedConfig.field,\r\n items: items.map((v) => ({ value: v, label: formatNumberChipLabel(v) }))\r\n }\r\n }, [mergedConfig.field, value])\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n }\r\n\r\n const hasValidInput = inputValue.trim() !== '' && !isNaN(parseFloat(inputValue))\r\n const isApplyDisabled = !hasValidInput && !hasLogicChange\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return null\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={filterLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ display: 'block', mb: 1 }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <ToggleButtonGroup\r\n exclusive\r\n size='small'\r\n value={operator}\r\n onChange={(_, val) => {\r\n if (val) setOperator(val)\r\n }}\r\n sx={{ mb: 1, width: '100%', '.MuiToggleButton-root': { flex: 1, py: 0.25, fontSize: '0.8rem' } }}\r\n >\r\n {OPERATORS.map((op) => (\r\n <ToggleButton key={op} value={op}>\r\n {OPERATOR_SYMBOLS[op]}\r\n </ToggleButton>\r\n ))}\r\n </ToggleButtonGroup>\r\n <TextField\r\n inputRef={refInput}\r\n autoFocus\r\n type='number'\r\n name={mergedConfig.field.toString()}\r\n size='small'\r\n fullWidth\r\n placeholder='Enter number'\r\n error={error.error}\r\n helperText={error.message}\r\n inputProps={{ step: params?.step ?? 1, min: params?.min, max: params?.max }}\r\n onChange={(e) => setInputValue(e.target.value)}\r\n sx={{ '.MuiInputBase-root': { minHeight: '42px' } }}\r\n />\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained' disabled={isApplyDisabled}>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldNumber\r\n}\r\n\r\nexport default createFormFieldNumber\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n }\r\n})\r\n\r\n// ---------------------------------------------------------------------------\r\n// Utilities\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Creates a labelFormatter for use in summaryConfig.\r\n * Formats encoded number values (e.g. `\"gt:18\"`) to a human-readable label (e.g. `\"> 18\"`).\r\n *\r\n * @example\r\n * summaryConfig: {\r\n * labelFormatter: { age: formatterNumber() }\r\n * }\r\n */\r\nexport function formatterNumber() {\r\n return (value: TFieldValid): string | undefined => {\r\n return formatNumberChipLabel(value)\r\n }\r\n}\r\n"],"names":["OPERATOR_SYMBOLS","eq","lt","lte","gt","gte","OPERATORS","encodeNumberValue","operator","num","concat","decodeNumberValue","encoded","colonIdx","indexOf","op","slice","parseFloat","includes","isNaN","formatNumberChipLabel","decoded","String","createFormFieldNumber","params","ChipViewers","createChipViewers","props","_mergedConfig$default","_params$defaultOperat","_mergedConfig$label","_params$step","mergedConfig","useMemo","Object","assign","currentConfig","config","refInput","createRef","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","_useState3","defaultOperator","_useState4","setOperator","_useState5","_useState6","inputValue","setInputValue","label","field","toString","_useState7","_useState8","errorData","setErrorData","handleSubmit","newValue","onSubmit","hasLogicChange","error","getErrorMessage","filterViewerValue","items","Array","isArray","map","v","isApplyDisabled","trim","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_refInput$current$val","_refInput$current","_props$validator","preventDefault","stopPropagation","rawNum","current","obj","_defineProperty","validationError","validator","run","keys","length","blur","children","_jsxs","PopperContent","title","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","FilterLogicToggle","sx","ml","onChange","_","nVal","PopperBody","description","Typography","variant","color","display","mb","borderBottom","placement","enableMinimalesticView","onRemove","ToggleButtonGroup","exclusive","val","width","flex","py","fontSize","ToggleButton","TextField","inputRef","autoFocus","type","name","fullWidth","placeholder","helperText","message","inputProps","step","min","max","e","target","minHeight","PopperFooter","Button","disabled","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","styled","position","content","inset","backgroundColor","filter","zIndex","opacity","transition","visibility","pointerEvents","formatterNumber"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{slicedToArray as
|
|
1
|
+
import{slicedToArray as e,defineProperty as l,toConsumableArray as o}from"../../../_virtual/_rollupPluginBabelHelpers.js";import{jsx as i,jsxs as n}from"react/jsx-runtime";import{useMemo as r,useState as a}from"react";import{styled as t,Typography as u,FormGroup as s,FormControlLabel as c,Checkbox as d,Button as v,Box as m}from"@mui/material";import"../../form/validator.js";import"../../form/dino-form.js";import{getErrorMessage as f}from"../../form/helpers.js";import"../../../_virtual/Reflect.js";import"../../form/decorator.form.js";import"../../form/create.form-grid-layout.units.js";import{createChipViewers as p}from"../components/chip-viewer.js";import{PopperContent as b,PopperBody as g,PopperFooter as h}from"../components/popper-custom.js";import{ButtonBack as x,ChipDark as y,FilterLogicToggle as C}from"../components/ui.units.js";function j(t){var j=p(),V=(t||{}).options,k=void 0===V?[]:V;return function(p){var V,z,S,L=r(function(){return Object.assign({},p.currentConfig,null==t?void 0:t.config)},[null==t?void 0:t.config,p.currentConfig]),R=p.value,w=void 0===R?{values:[],logic:null!==(V=null==L?void 0:L.defaultLogic)&&void 0!==V?V:"or"}:R,B=a(w.logic),T=e(B,2),_=T[0],F=T[1],I=null!==(z=null==t?void 0:t.forceLogic)&&void 0!==z?z:_,M=null!==(S=null==L?void 0:L.label)&&void 0!==S?S:L.field.toString(),N=r(function(){var e=Array.isArray(w.values)?w.values:[w.values];return k.filter(function(l){return e.includes(l.value)}).map(function(e){return e.value})},[]),O=a(N),P=e(O,2),D=P[0],E=P[1],H=Array.isArray(w.values)?w.values:[],U=_!==w.logic,q=D.length!==H.length||D.some(function(e){return!H.includes(e)}),G=!q&&!U,J=a({}),K=e(J,2),Q=K[0],W=K[1],X=function(e){p.onSubmit(L.field,e,L)},Y=f(Q,L.field),Z=r(function(){var e=Array.isArray(w.values)?w.values:[w.values];return{field:L.field,items:e.map(function(e){var l;return{value:e,label:null===(l=k.find(function(l){return l.value===e}))||void 0===l?void 0:l.label}})}},[L.field,w]),$=null!=(null==t?void 0:t.maxValueCount)&&D.length>t.maxValueCount,ee=null!=(null==t?void 0:t.maxValueCount)&&D.length>=t.maxValueCount,le=[];return p.isLoading&&le.push("disabled"),i(A,{className:le.join(" "),noValidate:!0,onSubmit:function(e){var o;if(e.preventDefault(),q){if(!$){var i=l({},L.field,D),n=null===(o=p.validator)||void 0===o?void 0:o.run(i);if(W(n||{}),!n||0===Object.keys(n).length)X({values:D,logic:I})}}else U&&X({values:w.values,logic:I})},children:n(b,{title:"Filter by ".concat(M),onClose:p.onClose,slots:{beforeTitle:i(x,{size:"small",onClick:p.onBack}),afterTitle:L.singleValue?i(y,{sx:{ml:1.5},size:"small",label:"Last value only"}):null!=t&&t.forceLogic?null:i(C,{sx:{ml:1},value:I,onChange:function(e,l){F(l)}})},children:[n(g,{children:[L.description&&i(u,{variant:"caption",color:"text.secondary",sx:{mb:1,display:"block"},children:L.description}),null!=(null==t?void 0:t.maxValueCount)&&i(u,{variant:"caption",color:$?"warning.main":"text.secondary",sx:{mb:.5,display:"block"},children:null!=t&&t.maxValueCount?$?"Maximum ".concat(t.maxValueCount," value").concat(t.maxValueCount>1?"s":""," selected (limit reached)"):"Up to ".concat(t.maxValueCount," value").concat(t.maxValueCount>1?"s":""," can be selected"):""}),i(j,{sx:{mb:1,borderBottom:"none!important"},label:"Applied",placement:"horizontal",enableMinimalesticView:!0,value:Z,onRemove:p.onRemove}),i(s,{className:Y.error?"error":"",children:k.map(function(e,l){var n,r=D.includes(e.value),a=Z.items.some(function(l){return l.value===e.value})&&!0===(null==t?void 0:t.disabledAfterSubmit),u=!r&ⅇreturn i(c,{value:e.value,disabled:a||u,label:null!==(n=e.label)&&void 0!==n?n:e.value,control:i(d,{name:L.field.toString(),checked:r,onChange:function(l){return i=e.value,n=l.target.checked,void E(function(e){return n?[].concat(o(e),[i]):e.filter(function(e){return e!==i})});var i,n}})},e.value.toString()+l)})})]}),n(h,{children:[i(v,{size:"small",color:"error",variant:"text",disabled:!w.values||0===w.values.length,onClick:function(){var e,l;null===(e=p.onRemoveField)||void 0===e||e.call(p,L.field),!1!==(null==t||null===(l=t.config)||void 0===l?void 0:l.closeAfterClear)&&p.onClose()},children:"Clear All"}),i(m,{sx:{flex:1}}),i(v,{size:"small",color:"inherit",variant:"text",onClick:p.onClose,children:"Cancel"}),i(v,{size:"small",type:"submit",color:"primary",variant:"contained",disabled:G,children:"Apply"})]})]})})}}var A=t("form")({position:"relative","&::after":{content:'""',display:"block",position:"absolute",inset:0,backgroundColor:"rgba(0, 0, 0, 0.2)",filter:"blur(2px)",zIndex:-1,opacity:0,transition:"opacity 0.3s",visibility:"hidden"},"&.disabled":{pointerEvents:"none","&::after":{zIndex:1,opacity:1,visibility:"visible"}}});export{j as default};
|
|
2
2
|
//# sourceMappingURL=create-form-field-select-multiple.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-form-field-select-multiple.js","sources":["../../../../src/filter-bar/menu/create-form-field-select-multiple.tsx"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n// imports\r\nimport { useMemo, useState } from 'react'\r\nimport { Box, Button, Checkbox, FormControlLabel, FormGroup, formGroupClasses, styled, Typography } from '@mui/material'\r\nimport { getErrorMessage } from '../../form'\r\nimport { createChipViewers, TChipViewerGroup } from '../components/chip-viewer'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\nimport { ButtonBack, ChipDark, FilterLogicToggle } from '../components/ui.units'\r\n// types\r\nimport type { FC } from 'react'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { IFieldSelectOption } from './create-form-field-select'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic } from '../types'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\n\r\n/** Props for the `FormFieldSelectMultiple` component returned by `createFormFieldSelectMultiple`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldSelectMultipleProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** Parameters passed to `createFormFieldSelectMultiple` to configure the generated component. */\r\nexport interface IFormFieldSelectMultipleParam<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n /** List of options for the select field */\r\n options: IFieldSelectOption[]\r\n /** If true, disables the field after submission. @default false */\r\n disabledAfterSubmit?: boolean\r\n /** Force a fixed logic value, hiding the logic toggle */\r\n forceLogic?: TLogic\r\n /** Maximum number of values that can be selected */\r\n maxValueCount?: number\r\n}\r\n\r\n/**\r\n * Factory function that creates a `FormFieldSelectMultiple` filter-menu component.\r\n *\r\n * The generated component renders a checkbox list of options inside a\r\n * popper/menu panel, allowing the user to select **multiple values** at once.\r\n * It supports:\r\n * - Controlled checkbox state to prevent uncontrolled→controlled React warnings\r\n * - OR / AND logic toggle when more than one value is applied\r\n * - Chip viewers showing the currently applied values\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * @param params - Static configuration (option list, optional field config override)\r\n * @returns A React FC ready to be used as a multi-select filter-menu field component\r\n */\r\nfunction createFormFieldSelectMultiple<T>(params?: IFormFieldSelectMultipleParam<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n const { options = [] } = params || {}\r\n\r\n const FormFieldSelectMultiple: FC<IFormFieldSelectMultipleProps<T>> = (props) => {\r\n /** Merge `props.currentConfig` with `params.config` (if provided).\r\n * Fields from `params.config` override the corresponding keys in `props.currentConfig`.\r\n * Any keys not present in `params.config` are preserved from `props.currentConfig`.\r\n */\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'and' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n const effectiveLogic = params?.forceLogic ?? filterLogic\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n // Track checked values as controlled state to avoid the uncontrolled→controlled MUI warning\r\n const initialChecked = useMemo<TFieldValid[]>(() => {\r\n const values = Array.isArray(value.values) ? value.values : [value.values]\r\n return options.filter((opt) => values.includes(opt.value)).map((opt) => opt.value)\r\n }, [])\r\n const [checkedValues, setCheckedValues] = useState<TFieldValid[]>(initialChecked)\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n\r\n const handleCheckboxChange = (optionValue: TFieldValid, checked: boolean) => {\r\n setCheckedValues((prev) => (checked ? [...prev, optionValue] : prev.filter((v) => v !== optionValue)))\r\n }\r\n\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault()\r\n if (isMaxReached) return\r\n const obj = { [mergedConfig.field]: checkedValues } as Partial<TFieldModelValid<T>>\r\n let errorData = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(errorData || {})\r\n\r\n if (!errorData || Object.keys(errorData).length === 0) {\r\n const newValue: TFieldValue = { values: checkedValues, logic: effectiveLogic }\r\n handleSubmit(newValue)\r\n }\r\n }\r\n\r\n const errorResult = getErrorMessage(errorData, mergedConfig.field)\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return {\r\n field: mergedConfig.field,\r\n items: items.map((v) => ({ value: v, label: options.find((o) => o.value === v)?.label }))\r\n }\r\n }, [mergedConfig.field, value])\r\n\r\n const isMaxReached = params?.maxValueCount != null && checkedValues.length > params.maxValueCount\r\n const isMaxReachedValid = params?.maxValueCount != null && checkedValues.length >= params.maxValueCount\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n const newValue: TFieldValue = { values: value.values, logic: newLogic }\r\n handleSubmit(newValue)\r\n }\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const getMaxReachedText = () => {\r\n if (!params?.maxValueCount) return ''\r\n if (isMaxReached) {\r\n return `Maximum ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} selected (limit reached)`\r\n } else {\r\n return `Up to ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} can be selected`\r\n }\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return <ChipDark sx={{ ml: 1.5 }} size='small' label='Last value only' />\r\n if (params?.forceLogic || !value.values || value.values.length < 2) return null\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={effectiveLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ mb: 1, display: 'block' }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n {params?.maxValueCount != null && (\r\n <Typography variant='caption' color={isMaxReached ? 'warning.main' : 'text.secondary'} sx={{ mb: 0.5, display: 'block' }}>\r\n {getMaxReachedText()}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <FormGroup className={errorResult.error ? 'error' : ''}>\r\n {options.map((x, i) => {\r\n const isChecked = checkedValues.includes(x.value as TFieldValid)\r\n const isSelected = filterViewerValue.items.some((item) => item.value === x.value)\r\n const disabled = isSelected && params?.disabledAfterSubmit === true\r\n const disableDueToMax = !isChecked && isMaxReachedValid\r\n return (\r\n <FormControlLabel\r\n key={x.value.toString() + i}\r\n value={x.value}\r\n disabled={disabled || disableDueToMax}\r\n label={x.label ?? x.value}\r\n control={\r\n <Checkbox\r\n name={mergedConfig.field.toString()}\r\n checked={isChecked}\r\n onChange={(e) => handleCheckboxChange(x.value as TFieldValid, e.target.checked)}\r\n />\r\n }\r\n />\r\n )\r\n })}\r\n </FormGroup>\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained'>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldSelectMultiple\r\n}\r\n\r\nexport default createFormFieldSelectMultiple\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0, // top: 0, left: 0, right: 0, bottom: 0\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n }\r\n // [`.${formGroupClasses.root}`]: {}\r\n})\r\n"],"names":["createFormFieldSelectMultiple","params","ChipViewers","createChipViewers","_ref$options","options","props","_mergedConfig$default","_params$forceLogic","_mergedConfig$label","mergedConfig","useMemo","Object","assign","currentConfig","config","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","effectiveLogic","forceLogic","label","field","toString","initialChecked","Array","isArray","filter","opt","includes","map","_useState3","_useState4","checkedValues","setCheckedValues","_useState5","_useState6","errorData","setErrorData","handleSubmit","newValue","onSubmit","errorResult","getErrorMessage","filterViewerValue","items","v","_options$find","find","o","isMaxReached","maxValueCount","length","isMaxReachedValid","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_props$validator","preventDefault","obj","_defineProperty","validator","run","keys","children","_jsxs","PopperContent","title","concat","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","ChipDark","sx","ml","FilterLogicToggle","onChange","_","nVal","newLogic","handleChangeLogic","PopperBody","description","Typography","variant","color","mb","display","borderBottom","placement","enableMinimalesticView","onRemove","FormGroup","error","x","i","_x$label","isChecked","disabled","some","item","disabledAfterSubmit","disableDueToMax","FormControlLabel","control","Checkbox","name","checked","e","optionValue","target","prev","_toConsumableArray","PopperFooter","Button","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","flex","type","styled","position","content","inset","backgroundColor","zIndex","opacity","transition","visibility","pointerEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-form-field-select-multiple.js","sources":["../../../../src/filter-bar/menu/create-form-field-select-multiple.tsx"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n// imports\r\nimport { useMemo, useState } from 'react'\r\nimport { Box, Button, Checkbox, FormControlLabel, FormGroup, formGroupClasses, styled, Typography } from '@mui/material'\r\nimport { getErrorMessage } from '../../form'\r\nimport { createChipViewers, TChipViewerGroup } from '../components/chip-viewer'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\nimport { ButtonBack, ChipDark, FilterLogicToggle } from '../components/ui.units'\r\n// types\r\nimport type { FC } from 'react'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { IFieldSelectOption } from './create-form-field-select'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic } from '../types'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\n\r\n/** Props for the `FormFieldSelectMultiple` component returned by `createFormFieldSelectMultiple`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldSelectMultipleProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** Parameters passed to `createFormFieldSelectMultiple` to configure the generated component. */\r\nexport interface IFormFieldSelectMultipleParam<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n /** List of options for the select field */\r\n options: IFieldSelectOption[]\r\n /** If true, disables the field after submission. @default false */\r\n disabledAfterSubmit?: boolean\r\n /** Force a fixed logic value, hiding the logic toggle */\r\n forceLogic?: TLogic\r\n /** Maximum number of values that can be selected */\r\n maxValueCount?: number\r\n}\r\n\r\n/**\r\n * Factory function that creates a `FormFieldSelectMultiple` filter-menu component.\r\n *\r\n * The generated component renders a checkbox list of options inside a\r\n * popper/menu panel, allowing the user to select **multiple values** at once.\r\n * It supports:\r\n * - Controlled checkbox state to prevent uncontrolled→controlled React warnings\r\n * - OR / AND logic toggle when more than one value is applied\r\n * - Chip viewers showing the currently applied values\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * @param params - Static configuration (option list, optional field config override)\r\n * @returns A React FC ready to be used as a multi-select filter-menu field component\r\n */\r\nfunction createFormFieldSelectMultiple<T>(params?: IFormFieldSelectMultipleParam<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n const { options = [] } = params || {}\r\n\r\n const FormFieldSelectMultiple: FC<IFormFieldSelectMultipleProps<T>> = (props) => {\r\n /** Merge `props.currentConfig` with `params.config` (if provided).\r\n * Fields from `params.config` override the corresponding keys in `props.currentConfig`.\r\n * Any keys not present in `params.config` are preserved from `props.currentConfig`.\r\n */\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'or' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n const effectiveLogic = params?.forceLogic ?? filterLogic\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n // Track checked values as controlled state to avoid the uncontrolled→controlled MUI warning\r\n const initialChecked = useMemo<TFieldValid[]>(() => {\r\n const values = Array.isArray(value.values) ? value.values : [value.values]\r\n return options.filter((opt) => values.includes(opt.value)).map((opt) => opt.value)\r\n }, [])\r\n const [checkedValues, setCheckedValues] = useState<TFieldValid[]>(initialChecked)\r\n\r\n const appliedValues = Array.isArray(value.values) ? value.values : []\r\n const hasLogicChange = filterLogic !== value.logic\r\n const hasDataChange = checkedValues.length !== appliedValues.length || checkedValues.some(v => !appliedValues.includes(v))\r\n const isApplyDisabled = !hasDataChange && !hasLogicChange\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n\r\n const handleCheckboxChange = (optionValue: TFieldValid, checked: boolean) => {\r\n setCheckedValues((prev) => (checked ? [...prev, optionValue] : prev.filter((v) => v !== optionValue)))\r\n }\r\n\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault()\r\n\r\n if (!hasDataChange) {\r\n if (hasLogicChange) {\r\n handleSubmit({ values: value.values, logic: effectiveLogic })\r\n }\r\n return\r\n }\r\n\r\n if (isMaxReached) return\r\n\r\n const obj = { [mergedConfig.field]: checkedValues } as Partial<TFieldModelValid<T>>\r\n let errorData = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(errorData || {})\r\n\r\n if (!errorData || Object.keys(errorData).length === 0) {\r\n const newValue: TFieldValue = { values: checkedValues, logic: effectiveLogic }\r\n handleSubmit(newValue)\r\n }\r\n }\r\n\r\n const errorResult = getErrorMessage(errorData, mergedConfig.field)\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return {\r\n field: mergedConfig.field,\r\n items: items.map((v) => ({ value: v, label: options.find((o) => o.value === v)?.label }))\r\n }\r\n }, [mergedConfig.field, value])\r\n\r\n const isMaxReached = params?.maxValueCount != null && checkedValues.length > params.maxValueCount\r\n const isMaxReachedValid = params?.maxValueCount != null && checkedValues.length >= params.maxValueCount\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n }\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const getMaxReachedText = () => {\r\n if (!params?.maxValueCount) return ''\r\n if (isMaxReached) {\r\n return `Maximum ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} selected (limit reached)`\r\n } else {\r\n return `Up to ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} can be selected`\r\n }\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return <ChipDark sx={{ ml: 1.5 }} size='small' label='Last value only' />\r\n if (params?.forceLogic) return null\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={effectiveLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ mb: 1, display: 'block' }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n {params?.maxValueCount != null && (\r\n <Typography variant='caption' color={isMaxReached ? 'warning.main' : 'text.secondary'} sx={{ mb: 0.5, display: 'block' }}>\r\n {getMaxReachedText()}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <FormGroup className={errorResult.error ? 'error' : ''}>\r\n {options.map((x, i) => {\r\n const isChecked = checkedValues.includes(x.value as TFieldValid)\r\n const isSelected = filterViewerValue.items.some((item) => item.value === x.value)\r\n const disabled = isSelected && params?.disabledAfterSubmit === true\r\n const disableDueToMax = !isChecked && isMaxReachedValid\r\n return (\r\n <FormControlLabel\r\n key={x.value.toString() + i}\r\n value={x.value}\r\n disabled={disabled || disableDueToMax}\r\n label={x.label ?? x.value}\r\n control={\r\n <Checkbox\r\n name={mergedConfig.field.toString()}\r\n checked={isChecked}\r\n onChange={(e) => handleCheckboxChange(x.value as TFieldValid, e.target.checked)}\r\n />\r\n }\r\n />\r\n )\r\n })}\r\n </FormGroup>\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained' disabled={isApplyDisabled}>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldSelectMultiple\r\n}\r\n\r\nexport default createFormFieldSelectMultiple\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0, // top: 0, left: 0, right: 0, bottom: 0\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n }\r\n // [`.${formGroupClasses.root}`]: {}\r\n})\r\n"],"names":["createFormFieldSelectMultiple","params","ChipViewers","createChipViewers","_ref$options","options","props","_mergedConfig$default","_params$forceLogic","_mergedConfig$label","mergedConfig","useMemo","Object","assign","currentConfig","config","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","effectiveLogic","forceLogic","label","field","toString","initialChecked","Array","isArray","filter","opt","includes","map","_useState3","_useState4","checkedValues","setCheckedValues","appliedValues","hasLogicChange","hasDataChange","length","some","v","isApplyDisabled","_useState5","_useState6","errorData","setErrorData","handleSubmit","newValue","onSubmit","errorResult","getErrorMessage","filterViewerValue","items","_options$find","find","o","isMaxReached","maxValueCount","isMaxReachedValid","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_props$validator","preventDefault","obj","_defineProperty","validator","run","keys","children","_jsxs","PopperContent","title","concat","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","ChipDark","sx","ml","FilterLogicToggle","onChange","_","nVal","PopperBody","description","Typography","variant","color","mb","display","borderBottom","placement","enableMinimalesticView","onRemove","FormGroup","error","x","i","_x$label","isChecked","disabled","item","disabledAfterSubmit","disableDueToMax","FormControlLabel","control","Checkbox","name","checked","e","optionValue","target","prev","_toConsumableArray","PopperFooter","Button","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","flex","type","styled","position","content","inset","backgroundColor","zIndex","opacity","transition","visibility","pointerEvents"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{defineProperty as
|
|
1
|
+
import{defineProperty as l,slicedToArray as e}from"../../../_virtual/_rollupPluginBabelHelpers.js";import{jsx as o,jsxs as i}from"react/jsx-runtime";import{useMemo as n,useState as r}from"react";import{styled as a,radioGroupClasses as t,Typography as u,RadioGroup as c,FormControlLabel as s,Radio as d,Button as m,Box as v}from"@mui/material";import{getErrorMessage as f}from"../../form/helpers.js";import{createChipViewers as p}from"../components/chip-viewer.js";import{ButtonBack as b,ChipDark as g,FilterLogicToggle as x}from"../components/ui.units.js";import{PopperContent as C,PopperBody as h,PopperFooter as y}from"../components/popper-custom.js";function V(a){var t=p(),V=a.options;return function(p){var j,z,A,L=n(function(){return Object.assign({},p.currentConfig,null==a?void 0:a.config)},[null==a?void 0:a.config,p.currentConfig]),M=p.value,S=void 0===M?{values:[],logic:null!==(j=null==L?void 0:L.defaultLogic)&&void 0!==j?j:"or"}:M,F=r(S.logic),R=e(F,2),w=R[0],B=R[1],T=null!==(z=a.forceLogic)&&void 0!==z?z:w,I=r(null),N=e(I,2),O=N[0],P=N[1],_=w!==S.logic,D=null!==O,E=!D&&!_,H=null!==(A=null==L?void 0:L.label)&&void 0!==A?A:L.field.toString(),U=r({}),q=e(U,2),G=q[0],J=q[1],K=function(l){p.onSubmit(L.field,l,L)},Q=f(G,L.field),W=n(function(){var l=Array.isArray(S.values)?S.values:[S.values];return{field:L.field,items:l.map(function(l){var e;return{value:l,label:null===(e=V.find(function(e){return e.value===l}))||void 0===e?void 0:e.label}})}},[L.field,S]),X=null!=a.maxValueCount&&W.items.length>=a.maxValueCount,Y=[];return p.isLoading&&Y.push("disabled"),o(k,{className:Y.join(" "),noValidate:!0,onSubmit:function(e){var o;if(e.preventDefault(),D){if(!X){var i=l({},L.field,O),n=null===(o=p.validator)||void 0===o?void 0:o.run(i);if(J(n||{}),!n||0===Object.keys(n).length)K({values:[O],logic:T}),P(null)}}else _&&K({values:S.values,logic:T})},children:i(C,{title:"Filter by ".concat(H),onClose:p.onClose,slots:{beforeTitle:o(b,{size:"small",onClick:p.onBack}),afterTitle:L.singleValue?o(g,{sx:{ml:1.5},size:"small",label:"Last value only"}):a.forceLogic?null:o(x,{sx:{ml:1},value:T,onChange:function(l,e){B(e)}})},children:[i(h,{children:[L.description&&o(u,{variant:"caption",color:"text.secondary",sx:{mb:1,display:"block"},children:L.description}),null!=a.maxValueCount&&o(u,{variant:"caption",color:X?"warning.main":"text.secondary",sx:{mb:.5,display:"block"},children:a.maxValueCount?X?"Maximum ".concat(a.maxValueCount," value").concat(a.maxValueCount>1?"s":""," selected (limit reached)"):"Up to ".concat(a.maxValueCount," value").concat(a.maxValueCount>1?"s":""," can be selected"):""}),o(t,{sx:{mb:1,borderBottom:"none!important"},label:"Applied",placement:"horizontal",enableMinimalesticView:!0,value:W,onRemove:p.onRemove}),o(c,{sx:{mx:-1},name:L.field.toString(),value:null!=O?O:"",onChange:function(l){return P(l.target.value)},className:Q.error?"error":"",children:V.map(function(l,e){var i,n=W.items.some(function(e){return e.value===l.value});return o(s,{disabled:n||X,value:l.value,control:o(d,{size:"small"}),label:o(u,{variant:"body2",children:null!==(i=l.label)&&void 0!==i?i:l.value})},l.value.toString()+e)})}),Q.error&&o(u,{variant:"caption",color:"error",sx:{mt:.5},children:Q.message})]}),i(y,{children:[o(m,{size:"small",color:"error",variant:"text",disabled:!S.values||0===S.values.length,onClick:function(){var l,e;null===(l=p.onRemoveField)||void 0===l||l.call(p,L.field),!1!==(null==a||null===(e=a.config)||void 0===e?void 0:e.closeAfterClear)&&p.onClose()},children:"Clear All"}),o(v,{sx:{flex:1}}),o(m,{size:"small",color:"inherit",variant:"text",onClick:p.onClose,children:"Cancel"}),o(m,{size:"small",type:"submit",color:"primary",variant:"contained",disabled:E,children:"Apply"})]})]})})}}var k=a("form")(l({position:"relative","&::after":{content:'""',display:"block",position:"absolute",inset:0,backgroundColor:"rgba(0, 0, 0, 0.2)",filter:"blur(2px)",zIndex:-1,opacity:0,transition:"opacity 0.3s",visibility:"hidden"},"&.disabled":{pointerEvents:"none","&::after":{zIndex:1,opacity:1,visibility:"visible"}}},".".concat(t.root),{"&.error .MuiRadio-root":{color:"#d32f2f"},".MuiFormControlLabel-root":{margin:0},".MuiFormControlLabel-root:hover":{backgroundColor:"rgba(25, 118, 210, 0.04)"}}));export{V as default};
|
|
2
2
|
//# sourceMappingURL=create-form-field-select.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-form-field-select.js","sources":["../../../../src/filter-bar/menu/create-form-field-select.tsx"],"sourcesContent":["import { FC, useMemo, useState } from 'react'\r\nimport { Box, Button, FormControlLabel, Radio, RadioGroup, radioGroupClasses, styled, Typography } from '@mui/material'\r\nimport { convertFormDataToJson, getErrorMessage } from '../../form/helpers'\r\nimport { createChipViewers, TChipViewerGroup } from '../components/chip-viewer'\r\nimport { ButtonBack, ChipDark, FilterLogicToggle } from '../components/ui.units'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic } from '../types'\r\n\r\n/** Props for the `FormFieldSelect` component returned by `createFormFieldSelect`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldSelectProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** A single option item rendered as a radio button inside the select filter menu. */\r\nexport interface IFieldSelectOption {\r\n value: TFieldValid\r\n label?: string\r\n}\r\n\r\n/** Parameters passed to `createFormFieldSelect` to configure the generated component. */\r\nexport interface IFormFieldSelectParams<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n /** List of options for the select field */\r\n options: IFieldSelectOption[]\r\n forceLogic?: TLogic\r\n maxValueCount?: number\r\n}\r\n\r\n/**\r\n * Factory function that creates a `FormFieldSelect` filter-menu component.\r\n *\r\n * The generated component renders a radio-button list of options inside a\r\n * popper/menu panel. It supports:\r\n * - Single or multi-value selection (controlled by `config.singleValue`)\r\n * - OR / AND logic toggle when more than one value is selected\r\n * - Chip viewers showing the currently applied values\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * @param params - Static configuration (option list, optional field config override)\r\n * @returns A React FC ready to be used as a filter-menu field component\r\n */\r\nfunction createFormFieldSelect<T>(params: IFormFieldSelectParams<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n const { options } = params\r\n\r\n const FormFieldSelect: FC<IFormFieldSelectProps<T>> = (props) => {\r\n /** Merge `props.currentConfig` with `params.config` (if provided).\r\n * Fields from `params.config` override the corresponding keys in `props.currentConfig`.\r\n * Any keys not present in `params.config` are preserved from `props.currentConfig`.\r\n */\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'and' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n const effectiveLogic = params.forceLogic ?? filterLogic\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault()\r\n if (isMaxReached) return\r\n const formData = new FormData(event.currentTarget)\r\n const obj = convertFormDataToJson<TFieldModelValid<T>>(formData)\r\n let errorData = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(errorData || {})\r\n\r\n if (!errorData || Object.keys(errorData).length === 0) {\r\n const { field } = mergedConfig\r\n const newValues = (Array.isArray(obj[field]) ? obj[field] : [obj[field]]) as TFieldValid[]\r\n const newValue: TFieldValue = { values: newValues, logic: effectiveLogic }\r\n handleSubmit(newValue)\r\n }\r\n }\r\n\r\n const errorResult = getErrorMessage(errorData, mergedConfig.field)\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return {\r\n field: mergedConfig.field,\r\n items: items.map((v) => ({ value: v, label: options.find((o) => o.value === v)?.label }))\r\n }\r\n }, [mergedConfig.field, value])\r\n\r\n const isMaxReached = params.maxValueCount != null && filterViewerValue.items.length >= params.maxValueCount\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n const newValue: TFieldValue = { values: value.values, logic: newLogic }\r\n handleSubmit(newValue)\r\n }\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return <ChipDark sx={{ ml: 1.5 }} size='small' label='Last value only' />\r\n if (params.forceLogic || !value.values || value.values.length < 2) return null\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={effectiveLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const getMaxReachedText = () => {\r\n if (!params.maxValueCount) return ''\r\n if (isMaxReached) {\r\n return `Maximum ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} selected (limit reached)`\r\n } else {\r\n return `Up to ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} can be selected`\r\n }\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ mb: 1, display: 'block' }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n {params.maxValueCount != null && (\r\n <Typography variant='caption' color={isMaxReached ? 'warning.main' : 'text.secondary'} sx={{ mb: 0.5, display: 'block' }}>\r\n {getMaxReachedText()}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <RadioGroup sx={{ mx: -1 }} name={mergedConfig.field.toString()} className={errorResult.error ? 'error' : ''}>\r\n {options.map((x, i) => {\r\n const isSelected = filterViewerValue.items.some((item) => item.value === x.value)\r\n const disabled = isSelected || isMaxReached\r\n return (\r\n <FormControlLabel\r\n disabled={disabled}\r\n key={x.value.toString() + i}\r\n value={x.value}\r\n control={<Radio size='small' />}\r\n label={<Typography variant='body2'>{x.label ?? x.value}</Typography>}\r\n />\r\n )\r\n })}\r\n </RadioGroup>\r\n {errorResult.error && (\r\n <Typography variant='caption' color='error' sx={{ mt: 0.5 }}>\r\n {errorResult.message}\r\n </Typography>\r\n )}\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained'>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldSelect\r\n}\r\n\r\nexport default createFormFieldSelect\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0, // top: 0, left: 0, right: 0, bottom: 0\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n },\r\n [`.${radioGroupClasses.root}`]: {\r\n '&.error .MuiRadio-root': { color: '#d32f2f' },\r\n '.MuiFormControlLabel-root': { margin: 0 },\r\n '.MuiFormControlLabel-root:hover': { backgroundColor: 'rgba(25, 118, 210, 0.04)' }\r\n }\r\n})\r\n"],"names":["createFormFieldSelect","params","ChipViewers","createChipViewers","options","props","_mergedConfig$default","_params$forceLogic","_mergedConfig$label","mergedConfig","useMemo","Object","assign","currentConfig","config","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","effectiveLogic","forceLogic","label","field","toString","_useState3","_useState4","errorData","setErrorData","handleSubmit","newValue","onSubmit","errorResult","getErrorMessage","filterViewerValue","items","Array","isArray","map","v","_options$find","find","o","isMaxReached","maxValueCount","length","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_props$validator","preventDefault","formData","FormData","currentTarget","obj","convertFormDataToJson","validator","run","keys","newValues","children","_jsxs","PopperContent","title","concat","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","ChipDark","sx","ml","FilterLogicToggle","onChange","_","nVal","newLogic","handleChangeLogic","PopperBody","description","Typography","variant","color","mb","display","borderBottom","placement","enableMinimalesticView","onRemove","RadioGroup","mx","name","error","x","i","_x$label","isSelected","some","item","FormControlLabel","disabled","control","Radio","mt","message","PopperFooter","Button","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","flex","type","styled","_defineProperty","position","content","inset","backgroundColor","filter","zIndex","opacity","transition","visibility","pointerEvents","radioGroupClasses","root","margin"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-form-field-select.js","sources":["../../../../src/filter-bar/menu/create-form-field-select.tsx"],"sourcesContent":["import { FC, useMemo, useState } from 'react'\r\nimport { Box, Button, FormControlLabel, Radio, RadioGroup, radioGroupClasses, styled, Typography } from '@mui/material'\r\nimport { getErrorMessage } from '../../form/helpers'\r\nimport { createChipViewers, TChipViewerGroup } from '../components/chip-viewer'\r\nimport { ButtonBack, ChipDark, FilterLogicToggle } from '../components/ui.units'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic } from '../types'\r\n\r\n/** Props for the `FormFieldSelect` component returned by `createFormFieldSelect`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldSelectProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** A single option item rendered as a radio button inside the select filter menu. */\r\nexport interface IFieldSelectOption {\r\n value: TFieldValid\r\n label?: string\r\n}\r\n\r\n/** Parameters passed to `createFormFieldSelect` to configure the generated component. */\r\nexport interface IFormFieldSelectParams<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n /** List of options for the select field */\r\n options: IFieldSelectOption[]\r\n /** Force a specific logic (and/or) for this field, overriding the default or user-selected logic */\r\n forceLogic?: TLogic\r\n /** Maximum number of values that can be selected for this field */\r\n maxValueCount?: number\r\n}\r\n\r\n/**\r\n * Factory function that creates a `FormFieldSelect` filter-menu component.\r\n *\r\n * The generated component renders a radio-button list of options inside a\r\n * popper/menu panel. It supports:\r\n * - Single or multi-value selection (controlled by `config.singleValue`)\r\n * - OR / AND logic toggle when more than one value is selected\r\n * - Chip viewers showing the currently applied values\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * @param params - Static configuration (option list, optional field config override)\r\n * @returns A React FC ready to be used as a filter-menu field component\r\n */\r\nfunction createFormFieldSelect<T>(params: IFormFieldSelectParams<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n const { options } = params\r\n\r\n const FormFieldSelect: FC<IFormFieldSelectProps<T>> = (props) => {\r\n /** Merge `props.currentConfig` with `params.config` (if provided).\r\n * Fields from `params.config` override the corresponding keys in `props.currentConfig`.\r\n * Any keys not present in `params.config` are preserved from `props.currentConfig`.\r\n */\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'or' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n const effectiveLogic = params.forceLogic ?? filterLogic\r\n const [selectedValue, setSelectedValue] = useState<TFieldValid | null>(null)\r\n const hasLogicChange = filterLogic !== value.logic\r\n const hasDataChange = selectedValue !== null\r\n const isApplyDisabled = !hasDataChange && !hasLogicChange\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault()\r\n\r\n if (!hasDataChange) {\r\n if (hasLogicChange) {\r\n handleSubmit({ values: value.values, logic: effectiveLogic })\r\n }\r\n return\r\n }\r\n\r\n if (isMaxReached) return\r\n\r\n const obj = { [mergedConfig.field]: selectedValue } as Partial<TFieldModelValid<T>>\r\n let errorData = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(errorData || {})\r\n\r\n if (!errorData || Object.keys(errorData).length === 0) {\r\n const newValue: TFieldValue = { values: [selectedValue!] as TFieldValid[], logic: effectiveLogic }\r\n handleSubmit(newValue)\r\n setSelectedValue(null)\r\n }\r\n }\r\n\r\n const errorResult = getErrorMessage(errorData, mergedConfig.field)\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return {\r\n field: mergedConfig.field,\r\n items: items.map((v) => ({ value: v, label: options.find((o) => o.value === v)?.label }))\r\n }\r\n }, [mergedConfig.field, value])\r\n\r\n const isMaxReached = params.maxValueCount != null && filterViewerValue.items.length >= params.maxValueCount\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n }\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return <ChipDark sx={{ ml: 1.5 }} size='small' label='Last value only' />\r\n if (params.forceLogic) return null\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={effectiveLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const getMaxReachedText = () => {\r\n if (!params.maxValueCount) return ''\r\n if (isMaxReached) {\r\n return `Maximum ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} selected (limit reached)`\r\n } else {\r\n return `Up to ${params.maxValueCount} value${params.maxValueCount > 1 ? 's' : ''} can be selected`\r\n }\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ mb: 1, display: 'block' }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n {params.maxValueCount != null && (\r\n <Typography variant='caption' color={isMaxReached ? 'warning.main' : 'text.secondary'} sx={{ mb: 0.5, display: 'block' }}>\r\n {getMaxReachedText()}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <RadioGroup sx={{ mx: -1 }} name={mergedConfig.field.toString()} value={selectedValue ?? ''} onChange={(e) => setSelectedValue(e.target.value as TFieldValid)} className={errorResult.error ? 'error' : ''}>\r\n {options.map((x, i) => {\r\n const isSelected = filterViewerValue.items.some((item) => item.value === x.value)\r\n const disabled = isSelected || isMaxReached\r\n return (\r\n <FormControlLabel\r\n disabled={disabled}\r\n key={x.value.toString() + i}\r\n value={x.value}\r\n control={<Radio size='small' />}\r\n label={<Typography variant='body2'>{x.label ?? x.value}</Typography>}\r\n />\r\n )\r\n })}\r\n </RadioGroup>\r\n {errorResult.error && (\r\n <Typography variant='caption' color='error' sx={{ mt: 0.5 }}>\r\n {errorResult.message}\r\n </Typography>\r\n )}\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained' disabled={isApplyDisabled}>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldSelect\r\n}\r\n\r\nexport default createFormFieldSelect\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0, // top: 0, left: 0, right: 0, bottom: 0\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n },\r\n [`.${radioGroupClasses.root}`]: {\r\n '&.error .MuiRadio-root': { color: '#d32f2f' },\r\n '.MuiFormControlLabel-root': { margin: 0 },\r\n '.MuiFormControlLabel-root:hover': { backgroundColor: 'rgba(25, 118, 210, 0.04)' }\r\n }\r\n})\r\n"],"names":["createFormFieldSelect","params","ChipViewers","createChipViewers","options","props","_mergedConfig$default","_params$forceLogic","_mergedConfig$label","mergedConfig","useMemo","Object","assign","currentConfig","config","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","effectiveLogic","forceLogic","_useState3","_useState4","selectedValue","setSelectedValue","hasLogicChange","hasDataChange","isApplyDisabled","label","field","toString","_useState5","_useState6","errorData","setErrorData","handleSubmit","newValue","onSubmit","errorResult","getErrorMessage","filterViewerValue","items","Array","isArray","map","v","_options$find","find","o","isMaxReached","maxValueCount","length","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_props$validator","preventDefault","obj","_defineProperty","validator","run","keys","children","_jsxs","PopperContent","title","concat","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","ChipDark","sx","ml","FilterLogicToggle","onChange","_","nVal","PopperBody","description","Typography","variant","color","mb","display","borderBottom","placement","enableMinimalesticView","onRemove","RadioGroup","mx","name","e","target","error","x","i","_x$label","isSelected","some","item","FormControlLabel","disabled","control","Radio","mt","message","PopperFooter","Button","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","flex","type","styled","position","content","inset","backgroundColor","filter","zIndex","opacity","transition","visibility","pointerEvents","radioGroupClasses","root","margin"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{slicedToArray as e}from"../../../_virtual/_rollupPluginBabelHelpers.js";import{jsx as
|
|
1
|
+
import{slicedToArray as e}from"../../../_virtual/_rollupPluginBabelHelpers.js";import{jsx as i,jsxs as l}from"react/jsx-runtime";import{useMemo as o,useState as r,createRef as n}from"react";import{styled as t,Typography as a,TextField as s,Button as u,Box as c}from"@mui/material";import{createChipViewers as d}from"../components/chip-viewer.js";import{getErrorMessage as m,convertFormDataToJson as v}from"../../form/helpers.js";import{ButtonBack as p,ChipDark as f,FilterLogicToggle as b}from"../components/ui.units.js";import{PopperContent as g,PopperBody as h,PopperFooter as y}from"../components/popper-custom.js";function x(t){var x=d();return function(d){var j,z,A=o(function(){return Object.assign({},d.currentConfig,null==t?void 0:t.config)},[null==t?void 0:t.config,d.currentConfig]),k=n(),T=d.value,B=void 0===T?{values:[],logic:null!==(j=null==A?void 0:A.defaultLogic)&&void 0!==j?j:"or"}:T,F=r(B.logic),R=e(F,2),S=R[0],w=R[1],I=r(""),L=e(I,2),V=L[0],D=L[1],E=null!==(z=null==A?void 0:A.label)&&void 0!==z?z:A.field.toString(),H=r({}),M=e(H,2),O=M[0],P=M[1],_=function(e){d.onSubmit(A.field,e,A)},N=S!==B.logic,W=!V.trim()&&!N,q=m(O,A.field),G=o(function(){var e=Array.isArray(B.values)?B.values:[B.values];return{field:A.field,items:e.map(function(e){return{value:e}})}},[A.field,B]),J=[];return d.isLoading&&J.push("disabled"),i(C,{className:J.join(" "),noValidate:!0,onSubmit:function(e){var i;if(e.preventDefault(),e.stopPropagation(),V.trim()){var l=new FormData(e.currentTarget),o=v(l),r=null===(i=d.validator)||void 0===i?void 0:i.run(o);if(P(r||{}),!r||0===Object.keys(r).length){var n=A.field,t=Array.isArray(o[n])?o[n]:[o[n]];_({values:t,logic:S}),k.current&&(k.current.blur(),k.current.value=""),D("")}}else N&&_({values:B.values,logic:S})},children:l(g,{title:"Filter by ".concat(E),onClose:d.onClose,slots:{beforeTitle:i(p,{size:"small",onClick:d.onBack}),afterTitle:A.singleValue?i(f,{sx:{ml:1.5},size:"small",label:"Last value only"}):i(b,{sx:{ml:1},value:S,onChange:function(e,i){w(i)}})},children:[l(h,{children:[A.description&&i(a,{variant:"caption",color:"text.secondary",sx:{display:"block",mb:1},children:A.description}),i(x,{sx:{mb:1,borderBottom:"none!important"},label:"Applied",placement:"horizontal",enableMinimalesticView:!0,value:G,onRemove:d.onRemove}),i(s,{inputRef:k,autoFocus:!0,name:A.field.toString(),size:"small",fullWidth:!0,placeholder:"Enter value",error:q.error,helperText:q.message,onChange:function(e){return D(e.target.value)},sx:{".MuiInputBase-root":{minHeight:"42px"}}})]}),l(y,{children:[i(u,{size:"small",color:"error",variant:"text",disabled:!B.values||0===B.values.length,onClick:function(){var e,i;null===(e=d.onRemoveField)||void 0===e||e.call(d,A.field),!1!==(null==t||null===(i=t.config)||void 0===i?void 0:i.closeAfterClear)&&d.onClose()},children:"Clear All"}),i(c,{sx:{flex:1}}),i(u,{size:"small",color:"inherit",variant:"text",onClick:d.onClose,children:"Cancel"}),i(u,{size:"small",type:"submit",color:"primary",variant:"contained",disabled:W,children:"Apply"})]})]})})}}var C=t("form")({position:"relative","&::after":{content:'""',display:"block",position:"absolute",inset:0,backgroundColor:"rgba(0, 0, 0, 0.2)",filter:"blur(2px)",zIndex:-1,opacity:0,transition:"opacity 0.3s",visibility:"hidden"},"&.disabled":{pointerEvents:"none","&::after":{zIndex:1,opacity:1,visibility:"visible"}}});export{x as default};
|
|
2
2
|
//# sourceMappingURL=create-form-field-string.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-form-field-string.js","sources":["../../../../src/filter-bar/menu/create-form-field-string.tsx"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n// imports\r\nimport { createRef, useMemo, useState } from 'react'\r\nimport { Box, Button, styled, TextField, Typography } from '@mui/material'\r\nimport { createChipViewers } from '../components/chip-viewer'\r\nimport { convertFormDataToJson, getErrorMessage } from '../../form/helpers'\r\nimport { ButtonBack, ChipDark, FilterLogicToggle } from '../components/ui.units'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\n// types\r\nimport type { FC } from 'react'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { TChipViewerGroup } from '../components/chip-viewer'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic } from '../types'\r\n\r\n/** Props for the `FormFieldString` component returned by `createFormFieldString`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldStringProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** Parameters passed to `createFormFieldString` to configure the generated component. */\r\nexport interface IFormFieldStringParam<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n}\r\n\r\n/**\r\n * Factory function that creates a `FormFieldString` filter-menu component.\r\n *\r\n * The generated component renders a free-text input inside a popper/menu panel,\r\n * letting the user type arbitrary string values as filter criteria. It supports:\r\n * - OR / AND logic toggle when more than one value is applied\r\n * - Chip viewers showing the currently applied values\r\n * - Auto-focus and input reset after each successful submission\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * @param params - Static configuration (optional field config override)\r\n * @returns A React FC ready to be used as a free-text filter-menu field component\r\n */\r\nfunction createFormFieldString<T>(params?: IFormFieldStringParam<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n\r\n const FormFieldString: FC<IFormFieldStringProps<T>> = (props) => {\r\n /** Merge `props.currentConfig` with `params.config` (if provided).\r\n * Fields from `params.config` override the corresponding keys in `props.currentConfig`.\r\n * Any keys not present in `params.config` are preserved from `props.currentConfig`.\r\n */\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const refInput = createRef<HTMLInputElement>()\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'and' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault() // Prevent default form submission behavior\r\n event.stopPropagation() // Stop the event from bubbling up to parent elements (like Popper) which might cause it to close prematurely\r\n const formData = new FormData(event.currentTarget)\r\n const obj = convertFormDataToJson<TFieldModelValid<T>>(formData)\r\n let errorData = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(errorData || {})\r\n\r\n if (!errorData || Object.keys(errorData).length === 0) {\r\n const { field } = mergedConfig\r\n const newValues = (Array.isArray(obj[field]) ? obj[field] : [obj[field]]) as TFieldValid[]\r\n const newValue: TFieldValue = { values: newValues, logic: filterLogic }\r\n handleSubmit(newValue)\r\n\r\n if (refInput.current) {\r\n refInput.current.blur()\r\n refInput.current.value = ''\r\n }\r\n }\r\n }\r\n\r\n const error = getErrorMessage(errorData, mergedConfig.field)\r\n\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return { field: mergedConfig.field, items: items.map((v) => ({ value: v })) }\r\n }, [mergedConfig.field, value])\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n const newValue: TFieldValue = { values: value.values, logic: newLogic }\r\n handleSubmit(newValue)\r\n }\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return <ChipDark sx={{ ml: 1.5 }} size='small' label='Last value only' />\r\n if (!value.values || value.values.length < 2) return null\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={filterLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ display: 'block', mb: 1 }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <TextField\r\n inputRef={refInput}\r\n autoFocus\r\n name={mergedConfig.field.toString()}\r\n size='small'\r\n fullWidth\r\n placeholder='Enter value'\r\n error={error.error}\r\n helperText={error.message}\r\n sx={{ '.MuiInputBase-root': { minHeight: '42px' } }}\r\n />\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained'>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldString\r\n}\r\n\r\nexport default createFormFieldString\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0, // top: 0, left: 0, right: 0, bottom: 0\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n }\r\n})\r\n"],"names":["createFormFieldString","params","ChipViewers","createChipViewers","props","_mergedConfig$default","_mergedConfig$label","mergedConfig","useMemo","Object","assign","currentConfig","config","refInput","createRef","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","label","field","toString","_useState3","_useState4","errorData","setErrorData","handleSubmit","newValue","onSubmit","error","getErrorMessage","filterViewerValue","items","Array","isArray","map","v","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_props$validator","preventDefault","stopPropagation","formData","FormData","currentTarget","obj","convertFormDataToJson","validator","run","keys","length","newValues","current","blur","children","_jsxs","PopperContent","title","concat","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","ChipDark","sx","ml","FilterLogicToggle","onChange","_","nVal","newLogic","handleChangeLogic","PopperBody","description","Typography","variant","color","display","mb","borderBottom","placement","enableMinimalesticView","onRemove","TextField","inputRef","autoFocus","name","fullWidth","placeholder","helperText","message","minHeight","PopperFooter","Button","disabled","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","flex","type","styled","position","content","inset","backgroundColor","filter","zIndex","opacity","transition","visibility","pointerEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-form-field-string.js","sources":["../../../../src/filter-bar/menu/create-form-field-string.tsx"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n// imports\r\nimport { createRef, useMemo, useState } from 'react'\r\nimport { Box, Button, styled, TextField, Typography } from '@mui/material'\r\nimport { createChipViewers } from '../components/chip-viewer'\r\nimport { convertFormDataToJson, getErrorMessage } from '../../form/helpers'\r\nimport { ButtonBack, ChipDark, FilterLogicToggle } from '../components/ui.units'\r\nimport { PopperBody, PopperContent, PopperFooter } from '../components/popper-custom'\r\n// types\r\nimport type { FC } from 'react'\r\nimport type { IPartialError } from '../../form/validator'\r\nimport type { TChipViewerGroup } from '../components/chip-viewer'\r\nimport type { IFieldMenuConfig, IFilterMenuFormProps } from './types'\r\nimport type { TFieldModelValid, TFieldValid, TFieldValue, TLogic } from '../types'\r\n\r\n/** Props for the `FormFieldString` component returned by `createFormFieldString`. Extends the base filter-menu form props. */\r\nexport interface IFormFieldStringProps<T> extends IFilterMenuFormProps<T> {}\r\n\r\n/** Parameters passed to `createFormFieldString` to configure the generated component. */\r\nexport interface IFormFieldStringParam<T> {\r\n /** Optional configuration for the form field */\r\n config?: IFieldMenuConfig<T>\r\n}\r\n\r\n/**\r\n * Factory function that creates a `FormFieldString` filter-menu component.\r\n *\r\n * The generated component renders a free-text input inside a popper/menu panel,\r\n * letting the user type arbitrary string values as filter criteria. It supports:\r\n * - OR / AND logic toggle when more than one value is applied\r\n * - Chip viewers showing the currently applied values\r\n * - Auto-focus and input reset after each successful submission\r\n * - Built-in validation via an optional `validator` prop\r\n * - A loading overlay that disables interaction while `isLoading` is true\r\n *\r\n * @param params - Static configuration (optional field config override)\r\n * @returns A React FC ready to be used as a free-text filter-menu field component\r\n */\r\nfunction createFormFieldString<T>(params?: IFormFieldStringParam<T>) {\r\n const ChipViewers = createChipViewers<T>()\r\n\r\n const FormFieldString: FC<IFormFieldStringProps<T>> = (props) => {\r\n /** Merge `props.currentConfig` with `params.config` (if provided).\r\n * Fields from `params.config` override the corresponding keys in `props.currentConfig`.\r\n * Any keys not present in `params.config` are preserved from `props.currentConfig`.\r\n */\r\n const mergedConfig = useMemo(() => Object.assign({}, props.currentConfig, params?.config), [params?.config, props.currentConfig])\r\n\r\n const refInput = createRef<HTMLInputElement>()\r\n const { value = { values: [], logic: mergedConfig?.defaultLogic ?? 'or' } } = props\r\n const [filterLogic, setFilterLogic] = useState<TLogic>(value.logic!)\r\n const [inputValue, setInputValue] = useState('')\r\n\r\n const label = mergedConfig?.label ?? mergedConfig.field.toString()\r\n\r\n const [errorData, setErrorData] = useState<IPartialError<TFieldModelValid<T>>>({})\r\n\r\n const handleSubmit = (newValue: TFieldValue) => {\r\n props.onSubmit(mergedConfig.field, newValue, mergedConfig)\r\n }\r\n\r\n const hasLogicChange = filterLogic !== value.logic\r\n const isApplyDisabled = !inputValue.trim() && !hasLogicChange\r\n\r\n const handleSubmitForm = (event: React.FormEvent<HTMLFormElement>) => {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n\r\n if (!inputValue.trim()) {\r\n if (hasLogicChange) {\r\n handleSubmit({ values: value.values, logic: filterLogic })\r\n }\r\n return\r\n }\r\n\r\n const formData = new FormData(event.currentTarget)\r\n const obj = convertFormDataToJson<TFieldModelValid<T>>(formData)\r\n let errorData = props.validator?.run(obj) as IPartialError<TFieldModelValid<T>>\r\n\r\n setErrorData(errorData || {})\r\n\r\n if (!errorData || Object.keys(errorData).length === 0) {\r\n const { field } = mergedConfig\r\n const newValues = (Array.isArray(obj[field]) ? obj[field] : [obj[field]]) as TFieldValid[]\r\n const newValue: TFieldValue = { values: newValues, logic: filterLogic }\r\n handleSubmit(newValue)\r\n\r\n if (refInput.current) {\r\n refInput.current.blur()\r\n refInput.current.value = ''\r\n }\r\n setInputValue('')\r\n }\r\n }\r\n\r\n const error = getErrorMessage(errorData, mergedConfig.field)\r\n\r\n const filterViewerValue = useMemo<TChipViewerGroup<T>>(() => {\r\n const items = Array.isArray(value.values) ? value.values : [value.values]\r\n return { field: mergedConfig.field, items: items.map((v) => ({ value: v })) }\r\n }, [mergedConfig.field, value])\r\n\r\n const handleChangeLogic = (newLogic: TLogic) => {\r\n setFilterLogic(newLogic)\r\n }\r\n\r\n const handleClearAll = () => {\r\n props.onRemoveField?.(mergedConfig.field)\r\n if (params?.config?.closeAfterClear !== false) props.onClose()\r\n }\r\n\r\n const renderAfterTitle = () => {\r\n if (mergedConfig.singleValue) return <ChipDark sx={{ ml: 1.5 }} size='small' label='Last value only' />\r\n return <FilterLogicToggle sx={{ ml: 1 }} value={filterLogic} onChange={(_, nVal) => handleChangeLogic(nVal)} />\r\n }\r\n\r\n const rootClasses: string[] = []\r\n if (props.isLoading) rootClasses.push('disabled')\r\n\r\n return (\r\n <RootStyled className={rootClasses.join(' ')} noValidate onSubmit={handleSubmitForm}>\r\n <PopperContent\r\n title={`Filter by ${label}`}\r\n onClose={props.onClose}\r\n slots={{\r\n beforeTitle: <ButtonBack size='small' onClick={props.onBack} />,\r\n afterTitle: renderAfterTitle()\r\n }}\r\n >\r\n <PopperBody>\r\n {mergedConfig.description && (\r\n <Typography variant='caption' color='text.secondary' sx={{ display: 'block', mb: 1 }}>\r\n {mergedConfig.description}\r\n </Typography>\r\n )}\r\n <ChipViewers\r\n sx={{ mb: 1, borderBottom: 'none!important' }}\r\n label='Applied'\r\n placement='horizontal'\r\n enableMinimalesticView\r\n value={filterViewerValue}\r\n onRemove={props.onRemove}\r\n />\r\n <TextField\r\n inputRef={refInput}\r\n autoFocus\r\n name={mergedConfig.field.toString()}\r\n size='small'\r\n fullWidth\r\n placeholder='Enter value'\r\n error={error.error}\r\n helperText={error.message}\r\n onChange={(e) => setInputValue(e.target.value)}\r\n sx={{ '.MuiInputBase-root': { minHeight: '42px' } }}\r\n />\r\n </PopperBody>\r\n <PopperFooter>\r\n <Button size='small' color='error' variant='text' disabled={!value.values || value.values.length === 0} onClick={handleClearAll}>\r\n Clear All\r\n </Button>\r\n <Box sx={{ flex: 1 }} />\r\n <Button size='small' color='inherit' variant='text' onClick={props.onClose}>\r\n Cancel\r\n </Button>\r\n <Button size='small' type='submit' color='primary' variant='contained' disabled={isApplyDisabled}>\r\n Apply\r\n </Button>\r\n </PopperFooter>\r\n </PopperContent>\r\n </RootStyled>\r\n )\r\n }\r\n\r\n return FormFieldString\r\n}\r\n\r\nexport default createFormFieldString\r\n\r\nconst RootStyled = styled('form')({\r\n position: 'relative',\r\n '&::after': {\r\n content: '\"\"',\r\n display: 'block',\r\n position: 'absolute',\r\n inset: 0, // top: 0, left: 0, right: 0, bottom: 0\r\n backgroundColor: 'rgba(0, 0, 0, 0.2)',\r\n filter: 'blur(2px)',\r\n zIndex: -1,\r\n opacity: 0,\r\n transition: 'opacity 0.3s',\r\n visibility: 'hidden'\r\n },\r\n '&.disabled': {\r\n pointerEvents: 'none',\r\n '&::after': {\r\n zIndex: 1,\r\n opacity: 1,\r\n visibility: 'visible'\r\n }\r\n }\r\n})\r\n"],"names":["createFormFieldString","params","ChipViewers","createChipViewers","props","_mergedConfig$default","_mergedConfig$label","mergedConfig","useMemo","Object","assign","currentConfig","config","refInput","createRef","_props$value","value","values","logic","defaultLogic","_useState","useState","_useState2","_slicedToArray","filterLogic","setFilterLogic","_useState3","_useState4","inputValue","setInputValue","label","field","toString","_useState5","_useState6","errorData","setErrorData","handleSubmit","newValue","onSubmit","hasLogicChange","isApplyDisabled","trim","error","getErrorMessage","filterViewerValue","items","Array","isArray","map","v","rootClasses","isLoading","push","_jsx","RootStyled","className","join","noValidate","event","_props$validator","preventDefault","stopPropagation","formData","FormData","currentTarget","obj","convertFormDataToJson","validator","run","keys","length","newValues","current","blur","children","_jsxs","PopperContent","title","concat","onClose","slots","beforeTitle","ButtonBack","size","onClick","onBack","afterTitle","singleValue","ChipDark","sx","ml","FilterLogicToggle","onChange","_","nVal","PopperBody","description","Typography","variant","color","display","mb","borderBottom","placement","enableMinimalesticView","onRemove","TextField","inputRef","autoFocus","name","fullWidth","placeholder","helperText","message","e","target","minHeight","PopperFooter","Button","disabled","_props$onRemoveField","_params$config","onRemoveField","call","closeAfterClear","Box","flex","type","styled","position","content","inset","backgroundColor","filter","zIndex","opacity","transition","visibility","pointerEvents"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../../src/filter-bar/types.ts"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n/**\r\n * @en\r\n * - Allowed primitive values for filter fields.\r\n * - Runtime types: string, number, boolean.\r\n * - Use ISO date strings or a discriminated union if date/serialization handling is needed.\r\n * @vi\r\n * - Gia tri nguyen thuy hop le cho cac field filter.\r\n * - Kieu runtime: string, number, boolean.\r\n * - Neu can xu ly Date qua mang/serialize thi dung chuoi ISO hoac discriminated union.\r\n */\r\nexport type TFieldValid = string | number | boolean\r\n/**\r\n * @en\r\n * - Special UI-only filter keys (e.g. quickSearch).\r\n * - Kept as const so `TypeScript` preserves literal types.\r\n * @vi\r\n * - Khoa filter dac biet cho UI (vi dụ: quickSearch).\r\n * - Giu const de `TypeScript` giu literal types.\r\n */\r\nexport const KeySpecial = {\r\n quickSearch: 'quickSearch',\r\n sortShuffle: 'sortShuffle'\r\n} as const\r\nexport type KeySpecial = keyof typeof KeySpecial\r\n/**\r\n * @en\r\n * - Union of model field keys and special filter keys used by the filter system.\r\n * - Purpose: allow filters to reference either a model property or a special UI key.\r\n * @vi\r\n * - Tap hop cac khoa cua model va cac khoa filter dac biet cho he thong filter.\r\n * - Muc dich: cho phep filter tham chieu truong model hoac key UI dac biet.\r\n */\r\nexport type TFieldType<T> = keyof T | keyof typeof KeySpecial\r\n/**\r\n * @en\r\n * - Merge external model with special filter keys so filters can reference both.\r\n * - Purpose: extend a model type with UI special keys (e.g. quickSearch) for safe typing.\r\n * @vi\r\n * - Gop model ngoai voi cac key filter dac biet de filter co the tham chieu ca hai.\r\n * - Muc dich: mo rong kieu model de chua cac key UI (vd quickSearch) de type an toan.\r\n */\r\nexport type TFieldModelValid<T> = T & Record<keyof typeof KeySpecial, any>\r\n\r\n/**\r\n * @en Sorting direction for fields.\r\n * @vi Huong sap xep cho cac truong.\r\n */\r\nexport type TDirection = 'asc' | 'desc'\r\n/**\r\n * @en Interface for defining sorting behavior on a field.\r\n * @vi Giao dien de dinh nghia cach sap xep tren mot truong.\r\n */\r\nexport type TFieldSort<T> = {\r\n field: TFieldType<T>\r\n /** Sorting direction. Optional. Default is 'desc'. */\r\n direction?: TDirection\r\n}\r\n/**\r\n * @en Definition of core types for the filter bar system, including filter values, field types, and overall filter state structure.\r\n * @vi Dinh nghia cac kieu co ban cho he thong filter bar, bao gom gia tri filter, kieu field va cau truc tong the cua filter state.\r\n */\r\nexport type TLogic = 'and' | 'or'\r\n\r\nexport type TDateLogic = 'before' | 'after' | 'on' | 'range'\r\n\r\nexport type TNumberOperator = 'eq' | 'lt' | 'lte' | 'gt' | 'gte'\r\n/**\r\n * @en\r\n * - Structure for storing filter values and their logical relationship (AND/OR) for each field.\r\n * @vi\r\n * - Cau truc de luu tru cac gia tri filter va moi quan he logic (AND/OR) cho moi field.\r\n */\r\nexport type TFieldValue = {\r\n /** Logical operator for combining values within this field.
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../src/filter-bar/types.ts"],"sourcesContent":["// Copyright (c) 2024-present, Dinocollab Technologies, Inc. and its affiliates. All rights reserved.\r\n\r\n/**\r\n * @en\r\n * - Allowed primitive values for filter fields.\r\n * - Runtime types: string, number, boolean.\r\n * - Use ISO date strings or a discriminated union if date/serialization handling is needed.\r\n * @vi\r\n * - Gia tri nguyen thuy hop le cho cac field filter.\r\n * - Kieu runtime: string, number, boolean.\r\n * - Neu can xu ly Date qua mang/serialize thi dung chuoi ISO hoac discriminated union.\r\n */\r\nexport type TFieldValid = string | number | boolean\r\n/**\r\n * @en\r\n * - Special UI-only filter keys (e.g. quickSearch).\r\n * - Kept as const so `TypeScript` preserves literal types.\r\n * @vi\r\n * - Khoa filter dac biet cho UI (vi dụ: quickSearch).\r\n * - Giu const de `TypeScript` giu literal types.\r\n */\r\nexport const KeySpecial = {\r\n quickSearch: 'quickSearch',\r\n sortShuffle: 'sortShuffle'\r\n} as const\r\nexport type KeySpecial = keyof typeof KeySpecial\r\n/**\r\n * @en\r\n * - Union of model field keys and special filter keys used by the filter system.\r\n * - Purpose: allow filters to reference either a model property or a special UI key.\r\n * @vi\r\n * - Tap hop cac khoa cua model va cac khoa filter dac biet cho he thong filter.\r\n * - Muc dich: cho phep filter tham chieu truong model hoac key UI dac biet.\r\n */\r\nexport type TFieldType<T> = keyof T | keyof typeof KeySpecial\r\n/**\r\n * @en\r\n * - Merge external model with special filter keys so filters can reference both.\r\n * - Purpose: extend a model type with UI special keys (e.g. quickSearch) for safe typing.\r\n * @vi\r\n * - Gop model ngoai voi cac key filter dac biet de filter co the tham chieu ca hai.\r\n * - Muc dich: mo rong kieu model de chua cac key UI (vd quickSearch) de type an toan.\r\n */\r\nexport type TFieldModelValid<T> = T & Record<keyof typeof KeySpecial, any>\r\n\r\n/**\r\n * @en Sorting direction for fields.\r\n * @vi Huong sap xep cho cac truong.\r\n */\r\nexport type TDirection = 'asc' | 'desc'\r\n/**\r\n * @en Interface for defining sorting behavior on a field.\r\n * @vi Giao dien de dinh nghia cach sap xep tren mot truong.\r\n */\r\nexport type TFieldSort<T> = {\r\n field: TFieldType<T>\r\n /** Sorting direction. Optional. Default is 'desc'. */\r\n direction?: TDirection\r\n}\r\n/**\r\n * @en Definition of core types for the filter bar system, including filter values, field types, and overall filter state structure.\r\n * @vi Dinh nghia cac kieu co ban cho he thong filter bar, bao gom gia tri filter, kieu field va cau truc tong the cua filter state.\r\n */\r\nexport type TLogic = 'and' | 'or'\r\n\r\nexport type TDateLogic = 'before' | 'after' | 'on' | 'range'\r\n\r\nexport type TNumberOperator = 'eq' | 'lt' | 'lte' | 'gt' | 'gte'\r\n/**\r\n * @en\r\n * - Structure for storing filter values and their logical relationship (AND/OR) for each field.\r\n * @vi\r\n * - Cau truc de luu tru cac gia tri filter va moi quan he logic (AND/OR) cho moi field.\r\n */\r\nexport type TFieldValue = {\r\n /** Logical operator for combining values within this field. @default 'or' */\r\n logic?: TLogic\r\n /** Logical operator for date-specific filtering. Optional. */\r\n dateLogic?: TDateLogic\r\n values: TFieldValid[]\r\n}\r\n/**\r\n * @en\r\n * - Type for storing active filters in the filter state, mapping field keys to their filter values.\r\n * @vi\r\n * - Kieu de luu tru cac filter dang hoat dong trong filter state, map cac khoa field voi gia tri filter cua chung.\r\n */\r\nexport type TFieldStore<T> = Partial<Record<TFieldType<T>, TFieldValue>>\r\n/**\r\n * @en\r\n * - Overall filter state structure, including the logical operator for combining filters and optional storage for active filters and sorting.\r\n * @vi\r\n * - Cau truc tong the cho filter state, bao gom toan tu logic de ket hop cac filter va luu tru tuy chon cho cac filter dang hoat dong va sap xep.\r\n */\r\nexport type TFilterState<T> = {\r\n /** Logical operator for combining filters. Default is 'or'. */\r\n filterLogic?: TLogic\r\n storeFilter?: TFieldStore<T>\r\n storeSort?: TFieldSort<T>\r\n}\r\n"],"names":["KeySpecial","quickSearch","sortShuffle"],"mappings":"AAqBO,IAAMA,EAAa,CACxBC,YAAa,cACbC,YAAa"}
|
|
@@ -9,7 +9,7 @@ export interface IFilterBarConfigs<T> {
|
|
|
9
9
|
/** Debounce delay for filter input changes in milliseconds @default 300 */
|
|
10
10
|
debounceDelay?: number;
|
|
11
11
|
defaultFilterState?: TFilterState<T>;
|
|
12
|
-
/** Default filter logic, can be overridden by individual filters @default "
|
|
12
|
+
/** Default filter logic, can be overridden by individual filters @default "or" */
|
|
13
13
|
defaultFilterLogic?: 'and' | 'or';
|
|
14
14
|
InputComponent?: ComponentType<IFilterInputProps<T>>;
|
|
15
15
|
inputConfig?: IFilterInputConfig<T>;
|
|
@@ -15,7 +15,9 @@ export interface IFormFieldSelectParams<T> {
|
|
|
15
15
|
config?: IFieldMenuConfig<T>;
|
|
16
16
|
/** List of options for the select field */
|
|
17
17
|
options: IFieldSelectOption[];
|
|
18
|
+
/** Force a specific logic (and/or) for this field, overriding the default or user-selected logic */
|
|
18
19
|
forceLogic?: TLogic;
|
|
20
|
+
/** Maximum number of values that can be selected for this field */
|
|
19
21
|
maxValueCount?: number;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
@@ -17,7 +17,7 @@ export interface IFieldMenuConfig<T> {
|
|
|
17
17
|
closeAfterSubmit?: boolean;
|
|
18
18
|
/** If true, the menu will close after clearing a value @default true */
|
|
19
19
|
closeAfterClear?: boolean;
|
|
20
|
-
/** Default logic for combining multiple values for the field */
|
|
20
|
+
/** Default logic for combining multiple values for the field @default 'or' */
|
|
21
21
|
defaultLogic?: TLogic;
|
|
22
22
|
/** Tooltip for the field */
|
|
23
23
|
tooltip?: string | ((values?: TFieldValid[]) => string);
|
|
@@ -68,7 +68,7 @@ export type TNumberOperator = 'eq' | 'lt' | 'lte' | 'gt' | 'gte';
|
|
|
68
68
|
* - Cau truc de luu tru cac gia tri filter va moi quan he logic (AND/OR) cho moi field.
|
|
69
69
|
*/
|
|
70
70
|
export type TFieldValue = {
|
|
71
|
-
/** Logical operator for combining values within this field.
|
|
71
|
+
/** Logical operator for combining values within this field. @default 'or' */
|
|
72
72
|
logic?: TLogic;
|
|
73
73
|
/** Logical operator for date-specific filtering. Optional. */
|
|
74
74
|
dateLogic?: TDateLogic;
|
|
@@ -88,7 +88,7 @@ export type TFieldStore<T> = Partial<Record<TFieldType<T>, TFieldValue>>;
|
|
|
88
88
|
* - Cau truc tong the cho filter state, bao gom toan tu logic de ket hop cac filter va luu tru tuy chon cho cac filter dang hoat dong va sap xep.
|
|
89
89
|
*/
|
|
90
90
|
export type TFilterState<T> = {
|
|
91
|
-
/** Logical operator for combining filters. Default is '
|
|
91
|
+
/** Logical operator for combining filters. Default is 'or'. */
|
|
92
92
|
filterLogic?: TLogic;
|
|
93
93
|
storeFilter?: TFieldStore<T>;
|
|
94
94
|
storeSort?: TFieldSort<T>;
|