diff --git a/AI.md b/AI.md index 0c092d8..7f8766f 100644 --- a/AI.md +++ b/AI.md @@ -1,191 +1,186 @@ --- name: ww-input-select description: A customizable dropdown select input field with search, infinite scroll, and custom styling features for selecting predefined options with text or custom layouts. -keywords: - - select - - combobox - - selection - - dropdown select - - multi-select - - multi selection - - select items +keywords: [select, combobox, selection, dropdown select, multi-select, multi selection, select items] --- #### ww-input-select -1. **Component Purpose:** A customizable select element that supports single and multiple selection. - -2. **Properties:** - - - choices: `array<{ label: string, value: any }>` - Array of object (options) to display in the dropdown. Don't forget linked dynamicConfiguration in settings. It can not be an array of primitive values, always an array of objects. - - mappingLabel: `Formula` - A formula to derive the label from each choice item, only text, no img, icons, etc. Always use 'context.mapping...' - - mappingValue: `Formula` - A formula to derive the value from each choice item. Always use 'context.mapping...' - - mappingDisabled: `Formula` - A formula applyed to each choice item to disable or not them. Always use 'context.mapping...' - - initValueSingle: `string | null` - Initial value for single select mode. Default: `null` - - initValueMulti: `Array` - Initial values for multiple select mode. Default: `[]` - - selectType: `'single' | 'multiple'` - Type of selection. Default: `'single'` - - disabled: `boolean` - Whether the select is disabled. Default: `false` - - required: `boolean` - Whether the select is required. Default: `false` - - readonly: `boolean` - Whether the select is read-only. Default: `false` - - closeOnSelect: `boolean` - Whether to close dropdown after selection. Set `true` for single select, `false` for multiple select. Default: `true` - - manualTrigger: `boolean` - Whether to disable automatic trigger opening. Default: `false` - - closeOnClickOutside: `boolean` - Whether to close on outside click. Default: `true` - - selectOnClick: `boolean` - Whether to select option on click. Default: `true` - - unselectOnClick: `boolean` - Whether to unselect option on click. Default: `false` - - showSearch: `boolean` - Whether to show search input. Default: `false` - - searchBy: `Array` - Properties to search by in options - - autoFocus: `boolean` - Whether to focus search input on open. Default: `true` - - virtualScroll: `boolean` - Enable virtual scrolling. Default: `false` - - virtualScrollBuffer: `number` - Buffer size for virtual scroll. Default: `600` - - virtualScrollMinItemSize: `number` - Minimum item size for virtual scroll. Default: `40` - - placeholder: `string | object` - Placeholder text. Default: `{ en: 'Select a value' }` - - emptyStateText: `string | object` - Text to display when search returns no results. Default: `{ en: 'No results found' }` - - searchPlaceholder: `string | object` - Placeholder text for search input. Default: `{ en: 'Search' }` - - fieldName: `string` - Form field name. Default: `""` - - customValidation: `boolean` - Enable custom validation. Default: `false` - - validation: `Formula` - Custom validation formula - - - selectedFontFamily: `string` - Font family of selected value. Default: `'inherit'` - - selectedFontSize: `string` - Font size of selected value. Default: `'14px'` - - selectedFontWeight: `number | null` - Font weight of selected value - - selectedFontColor: `string` - Font color of selected value. Default: `'#333'` - - selectedPadding: `string` - Padding of selected value. Default: `'0px'` - - selectedTextAlign: `'left' | 'center' | 'right'` - Text alignment. Default: `'left'` - - - placeholderFontFamily: `string` - Font family of placeholder. Default: `'inherit'` - - placeholderFontSize: `string` - Font size of placeholder. Default: `'14px'` - - placeholderFontWeight: `number | null` - Font weight of placeholder - - placeholderFontColor: `string` - Font color of placeholder. Default: `'#333'` - - placeholderTextAlign: `'left' | 'center' | 'right'` - Text alignment. Default: `'left'` - - - chipFontSize: `string` - Font size of the chip text. Default: `"14px"`. - - chipFontFamily: `string` - Font family of the chip text. Default: `"inherit"`. - - chipFontWeight: `null | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900` - Font weight of the chip text. Default: `null`. - - chipFontColor: `string` - Color of the chip text. Default: `"white"`. - - chipPadding: `string` - Padding inside the chip. Default: `"2px 8px"`. - - chipBgColor: `string` - Background color of the chip. Default: `"#363636"`. - - chipBorder: `boolean` - Toggle between all borders or split borders. Default: `false`. - - chipBorderAll: `string` - Border style when `chipBorder` is false. - - chipBorderTop: `string` - Top border when `chipBorder` is true. - - chipBorderRight: `string` - Right border when `chipBorder` is true. - - chipBorderBottom: `string` - Bottom border when `chipBorder` is true. - - chipBorderLeft: `string` - Left border when `chipBorder` is true. - - chipBorderRadius: `string` - Border radius of the chip. Default: `"4px"`. - - chipIconUnselect: `string` - Icon used for unselecting the chip. Default: `"wwi wwi-cross"`. - - chipIconColor: `string` - Color of the chip icon. Default: `"white"`. - - chipIconSize: `string` - Size of the chip icon. Default: `"14px"`. - - - triggerHeight: `string` - Height of trigger - - triggerBorder: `boolean` - When false, borders defined with triggerBorderAll, when true, use each triggerBorderTop.. - - triggerBorderAll: `Border` - All borders when not split - - triggerBorderTop: `Border` - Top border when split - - triggerBorderRight: `Border` - Right border when split - - triggerBorderBottom: `Border` - Bottom border when split - - triggerBorderLeft: `Border` - Left border when split - - triggerBorderRadius: `string` - Border radius. Default: `'0px'` - - triggerBgColor: `string` - Background color - - triggerShadows: `string` - Box shadows. Default: `''` - - triggerPadding: `string` - Padding. Default: `'0px'` - - triggerMargin: `string` - Margin. Default: `'0px'` - - triggerIconOpen: `string` - Icon when open. Default: `'fas fa-angle-up'` - - triggerIconClose: `string` - Icon when closed. Default: `'fas fa-angle-down'` - - triggerIconColor: `string` - Icon color. Default: `'black'` - - triggerIconSize: `string` - Icon size. Default: `'16px'` - - - dropdownWidth: `string` - Width of dropdown. Do not touch it if not necessary, default value compute the same width as trigger. - - dropdownMaxHeight: `string` - Maximum height of dropdown - - dropdownBorder: `boolean` - When false, borders defined with dropdownBorderAll, when true, use each borderLeft.. - - dropdownBorderAll: `Border` - All borders when not split - - dropdownBorderTop: `Border` - Top border when split - - dropdownBorderRight: `Border` - Right border when split - - dropdownBorderBottom: `Border` - Bottom border when split - - dropdownBorderLeft: `Border` - Left border when split - - dropdownBorderRadius: `string` - Border radius. Default: `'0px'` - - dropdownBgColor: `string` - Background color - - dropdownShadows: `string` - Box shadows. Default: `''` - - dropdownPadding: `string` - Padding. Default: `'0px'` - - offsetX: Length - Horizontal offset of the dropdown relative to the trigger. Default: '0px'. - - offsetY: Length - Vertical offset of the dropdown relative to the trigger. Default: '8px'. - - - optionFontSize: `string` - Font size. Default: `'14px'` - - optionFontFamily: `string` - Font family. Default: `'inherit'` - - optionFontWeight: `number | null` - Font weight - - optionFontColor: `string` - Text color. Default: `'black'` - - optionSpacing: `string` - Row gap. Default: `'0px'` - - optionPadding: `string` - Padding. Default: `'0px'` - - optionBorder: `string` - Border of the option. Default `'0px solid #000000'` - - optionBorderRadius: `string` - Border radius. Default: `'0px'` - - optionBgColor: `string` - Background color - - optionBgColorFocused: `string` - Background color when focused. Default: `'#f5f5f5'` - - optionBgColorHover: `string` - Background color on hover. Default: `'#f5f5f5'` - - optionCursor: `string` - Cursor type. Default: `'pointer'` - - optionIcon: `string` - Check icon. Default: `'fas fa-check'` - - optionIconColor: `string` - Icon color. Default: `'black'` - - optionIconSize: `string` - Icon size. Default: `'16px'` - - - emptyStateFontFamily: `string` - Font family. Default: `'inherit'` - - emptyStateFontSize: `string` - Font size. Default: `'14px'` - - emptyStateFontWeight: `number | null` - Font weight - - emptyStateFontColor: `string` - Font color. Default: `'black'` - - emptyStatePadding: `string` - Padding. Default: `'0px'` - - emptyStateTextAlign: `'left' | 'center' | 'right'` - Text alignment. Default: `'left'` - - - searchWidth: `string` - Width of search input. Default: `'100%'` - - searchHeight: `string` - Height of search input - - searchBorder: `boolean` - When false, borders defined with searchBorderAll, when true, use each searchBorderTop.. - - searchBorderAll: `Border` - All borders when not split - - searchBorderTop: `Border` - Top border when split - - searchBorderRight: `Border` - Right border when split - - searchBorderBottom: `Border` - Bottom border when split - - searchBorderLeft: `Border` - Left border when split - - searchBorderRadius: `string` - Border radius. Default: `'0px'` - - searchPadding: `string` - Padding. Default: `'0px'` - - searchMargin: `string` - Margin. Default: `'0px'` - - searchOutline: `Border` - Outline style - - searchOutlineOffset: `string` - Outline offset - - searchFontFamily: `string` - Font family - - searchFontSize: `string` - Font size - - searchFontWeight: `number | null` - Font weight - - searchFontColor: `string` - Font color. Default: `'black'` - - searchBgColor: `string` - Background color. Default: `'white'` - - searchPlaceholderColor: `string` - Placeholder color. Default: `'gray'` - - Note: chip properties for multiselect, and selected properties for single select. - -3. **Settings:** - +***Purpose:*** +A customizable select element that supports single and multiple selection with advanced features like search, infinite scroll, and extensive styling options. + +***Features:*** +- Supports both single and multiple selection modes +- Customizable search functionality with configurable search fields +- Virtual scrolling for handling large datasets +- Extensive styling options for all components (trigger, dropdown, options, chips) +- Multi-language support +- Custom validation support +- Configurable borders, colors, fonts, and spacing for all elements +- Contain all logic for the select and all styling is done throught properties defined above. + +***Properties:*** +- choices: array<{ label: string, value: any }> - Array of objects (options) to display in the dropdown. Must be an array of objects, not primitive values. Example: [{"label": "Option 1", "value": "1"}] +- mappingLabel: Formula - Formula to derive the label from each choice item (text only, no HTML). Example: {"js":"context.mapping?.['label']"} +- mappingValue: Formula - Formula to derive the value from each choice item. Example: {"js":"context.mapping?.['value']"} +- mappingDisabled: Formula - Formula to disable/enable choice items. Example: {"js":"context.mapping?.['disabled']"} +- initValueSingle: string|null - Initial value for single select mode. Default: null +- initValueMulti: Array - Initial values for multiple select mode. Default: [] +- selectType: 'single'|'multiple' - Type of selection. Default: 'single' +- disabled: boolean - Whether the select is disabled. Default: false +- required: boolean - Whether the select is required. Default: false +- readonly: boolean - Whether the select is read-only. Default: false +- closeOnSelect: boolean - Whether to close dropdown after selection. Default: true +- manualTrigger: boolean - Whether to disable automatic trigger opening. Default: false +- closeOnClickOutside: boolean - Whether to close on outside click. Default: true +- selectOnClick: boolean - Whether to select option on click. Default: true +- unselectOnClick: boolean - Whether to unselect option on click. Default: false +- showSearch: boolean - Whether to show search input. Default: false +- searchBy: Array - Properties to search by in options +- autoFocus: boolean - Whether to focus search input on open. Default: true +- virtualScroll: boolean - Enable virtual scrolling. Default: false +- virtualScrollBuffer: number - Buffer size for virtual scroll. Default: 600 +- virtualScrollMinItemSize: number - Minimum item size for virtual scroll. Default: 40 +- placeholder: string|object - Placeholder text. Default: { en: 'Select a value' } +- emptyStateText: string|object - Text to display when search returns no results. Default: { en: 'No results found' } +- searchPlaceholder: string|object - Placeholder text for search input. Default: { en: 'Search' } +- fieldName: string - Form field name. Default: "" +- customValidation: boolean - Enable custom validation. Default: false +- validation: Formula - Custom validation formula + +- selectedFontFamily: `string` - Font family of selected value. Default: `'inherit'` +- selectedFontSize: `string` - Font size of selected value. Default: `'14px'` +- selectedFontWeight: `number | null` - Font weight of selected value +- selectedFontColor: `string` - Font color of selected value. Default: `'#333'` +- selectedPadding: `string` - Padding of selected value. Default: `'0px'` +- selectedTextAlign: `'left' | 'center' | 'right'` - Text alignment. Default: `'left'` + +- placeholderFontFamily: `string` - Font family of placeholder. Default: `'inherit'` +- placeholderFontSize: `string` - Font size of placeholder. Default: `'14px'` +- placeholderFontWeight: `number | null` - Font weight of placeholder +- placeholderFontColor: `string` - Font color of placeholder. Default: `'#333'` +- placeholderTextAlign: `'left' | 'center' | 'right'` - Text alignment. Default: `'left'` + +- chipFontSize: `string` - Font size of the chip text. Default: `"14px"`. +- chipFontFamily: `string` - Font family of the chip text. Default: `"inherit"`. +- chipFontWeight: `null | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900` - Font weight of the chip text. Default: `null`. +- chipFontColor: `string` - Color of the chip text. Default: `"white"`. +- chipPadding: `string` - Padding inside the chip. Default: `"2px 8px"`. +- chipBgColor: `string` - Background color of the chip. Default: `"#363636"`. +- chipBorder: `boolean` - Toggle between all borders or split borders. Default: `false`. +- chipBorderAll: `string` - Border style when `chipBorder` is false. +- chipBorderTop: `string` - Top border when `chipBorder` is true. +- chipBorderRight: `string` - Right border when `chipBorder` is true. +- chipBorderBottom: `string` - Bottom border when `chipBorder` is true. +- chipBorderLeft: `string` - Left border when `chipBorder` is true. +- chipBorderRadius: `string` - Border radius of the chip. Default: `"4px"`. +- chipIconUnselect: `string` (optional) - Icon used for unselecting the chip. Displays a cross by default. +- chipIconColor: `string` - Color of the chip icon. Default: `"white"`. +- chipIconSize: `string` - Size of the chip icon. Default: `"14px"`. + +- triggerHeight: `string` - Height of trigger +- triggerBorder: `boolean` - When false, borders defined with triggerBorderAll, when true, use each triggerBorderTop.. +- triggerBorderAll: `Border` - All borders when not split +- triggerBorderTop: `Border` - Top border when split +- triggerBorderRight: `Border` - Right border when split +- triggerBorderBottom: `Border` - Bottom border when split +- triggerBorderLeft: `Border` - Left border when split +- triggerBorderRadius: `string` - Border radius. Default: `'0px'` +- triggerBgColor: `string` - Background color +- triggerShadows: `string` - Box shadows. Default: `''` +- triggerPadding: `string` - Padding. Default: `'0px'` +- triggerMargin: `string` - Margin. Default: `'0px'` +- triggerIconOpen: `string` (optional) - Icon when open. Displays a chevron up by default. +- triggerIconClose: `string` (optional) - Icon when closed. Displays a chevron down by default. +- triggerIconColor: `string` - Icon color. Default: `'black'` +- triggerIconSize: `string` - Icon size. Default: `'16px'` + +- dropdownWidth: `string` - Width of dropdown. Do not touch it if not necessary, default value compute the same width as trigger. +- dropdownMaxHeight: `string` - Maximum height of dropdown +- dropdownBorder: `boolean` - When false, borders defined with dropdownBorderAll, when true, use each borderLeft.. +- dropdownBorderAll: `Border` - All borders when not split +- dropdownBorderTop: `Border` - Top border when split +- dropdownBorderRight: `Border` - Right border when split +- dropdownBorderBottom: `Border` - Bottom border when split +- dropdownBorderLeft: `Border` - Left border when split +- dropdownBorderRadius: `string` - Border radius. Default: `'0px'` +- dropdownBgColor: `string` - Background color +- dropdownShadows: `string` - Box shadows. Default: `''` +- dropdownPadding: `string` - Padding. Default: `'0px'` +- dropdownRowGap: `string` - Row gap. Default: `'0px'` +- offsetX: Length - Horizontal offset of the dropdown relative to the trigger. Default: '0px'. +- offsetY: Length - Vertical offset of the dropdown relative to the trigger. Default: '8px'. + +- optionFontSize: `string` - Font size. Default: `'14px'` +- optionFontFamily: `string` - Font family. Default: `'inherit'` +- optionFontWeight: `number | null` - Font weight +- optionFontColor: `string` - Text color. Default: `'black'` +- optionPadding: `string` - Padding. Default: `'0px'` +- optionBorder: `string` - Border of the option. Default `'0px solid #000000'` +- optionBorderRadius: `string` - Border radius. Default: `'0px'` +- optionBgColor: `string` - Background color +- optionBgColorFocused: `string` - Background color when focused. Default: `'#f5f5f5'` +- optionBgColorHover: `string` - Background color on hover. Default: `'#f5f5f5'` +- optionCursor: `string` - Cursor type. Default: `'pointer'` +- optionIcon: `string` (optional) - Check icon. Displays a check by default. +- optionIconColor: `string` - Icon color. Default: `'black'` +- optionIconSize: `string` - Icon size. Default: `'16px'` + +- emptyStateFontFamily: `string` - Font family. Default: `'inherit'` +- emptyStateFontSize: `string` - Font size. Default: `'14px'` +- emptyStateFontWeight: `number | null` - Font weight +- emptyStateFontColor: `string` - Font color. Default: `'black'` +- emptyStatePadding: `string` - Padding. Default: `'0px'` +- emptyStateTextAlign: `'left' | 'center' | 'right'` - Text alignment. Default: `'left'` + +- searchWidth: `string` - Width of search input. Default: `'100%'` +- searchHeight: `string` - Height of search input +- searchBorder: `boolean` - When false, borders defined with searchBorderAll, when true, use each searchBorderTop.. +- searchBorderAll: `Border` - All borders when not split +- searchBorderTop: `Border` - Top border when split +- searchBorderRight: `Border` - Right border when split +- searchBorderBottom: `Border` - Bottom border when split +- searchBorderLeft: `Border` - Left border when split +- searchBorderRadius: `string` - Border radius. Default: `'0px'` +- searchPadding: `string` - Padding. Default: `'0px'` +- searchMargin: `string` - Margin. Avoid horizontal margin as dropdown padding already give space between search and dropdown. Default: `'0px'` +- searchOutline: `Border` - Outline style +- searchOutlineOffset: `string` - Outline offset +- searchFontFamily: `string` - Font family +- searchFontSize: `string` - Font size +- searchFontWeight: `number | null` - Font weight +- searchFontColor: `string` - Font color. Default: `'black'` +- searchBgColor: `string` - Background color. Default: `'white'` +- searchPlaceholderColor: `string` - Placeholder color. Default: `'gray'` + +Note: chip properties for multiselect, and selected properties for single select. + +***Settings:*** - dynamicConfiguration: object({"content":{"choices":[{"key":"nameOfKey","type"="typeOfKey"}]}}) - Object that describe format of data in choices -4. **Children Components:** - -There is no children. - -5. **Special Features:** - -This select contain all logic for the select and all styling is done throught properties defined above. - -6. **Context:** - -- context.local.data.select: { select: { options: array<{ label: Text, value: value (according to mappingValue), disabled: bool, isSelected: bool, \_data: rawData defined in choices }>, active: { value: variableValue (according to mappingValue), details: { value: variableValue (according to mappingValue), label: variableLabel (according to mappingLabel), disabled: bool, data: full selected data (dynamicConfiguration format) } }, utils: { type: selectType, isOpen, triggerHeight } }, selectTrigger: { placeholder: Text } } - -7. **Events:** +***Exposed Variables:*** +- value: any|array - Current selected value(s). Type depends on selectType (single/multiple). Access using variables['current_element_uid-value'] +***Events:*** - change: Triggered when selection changes. Payload: { value: any } - initValueChange: Triggered when initial value changes. Payload: { value: any } -8. **Variables:** - -- value: any|array - Current selected value(s). Type depends on selectType (single/multiple) +***Context:*** +- context.local.data.select: { select: { options: array<{ label: Text, value: value (according to mappingValue), disabled: bool, isSelected: bool, _data: rawData defined in choices }>, active: { value: variableValue (according to mappingValue), details: { value: variableValue (according to mappingValue), label: variableLabel (according to mappingLabel), disabled: bool, data: full selected data (dynamicConfiguration format) } }, utils: { type: selectType, isOpen, triggerHeight, triggerWidth } }, selectTrigger: { placeholder: Text } } -9. **Exemple:** +***Example:*** - Simple select example -{"uid":"select-car","tag":"ww-input-select","name":"Vehicle Multi Select","settings":{"dynamicConfiguration":{"content":{"choices":[{"key":"label","type":"Text"},{"key":"value","type":"Text"},{"key":"emoji","type":"Text"},{"key":"description","type":"Text"}]}}},"props":{"default":{"side":"bottom","align":"start","choices":[{"emoji":"🚗","label":"Sedan","value":"sedan","description":"Perfect for daily commuting"}],"offsetX":"0px","offsetY":"8px","disabled":false,"readonly":false,"required":true,"autoFocus":true,"fieldName":"","optionIcon":"fas fa-check","selectType":"single","showSearch":true,"validation":"","zIndexOpen":1,"boundOffset":"0px","placeholder":{"en":"Select vehicle types","fr":"Sélectionner un type de véhicule"},"searchWidth":"100%","initialState":"closed","manualToggle":false,"mappingLabel":{"code":"context.mapping?.['emoji']+' '+context.mapping?.['label']","type":"f"},"mappingValue":{"code":"context.mapping?.['value']","type":"f"},"optionCursor":"pointer","searchBorder":false,"searchHeight":"32px","closeOnSelect":true,"dropdownWidth":{"code":"context.local.data?.['select']?.['select']?.['utils']?.['triggerWidth']+'px'","__wwtype":"f"},"manualTrigger":false,"optionPadding":"8px ","searchBgColor":"#F5F5F5","searchPadding":"12px","selectOnClick":true,"triggerBorder":false,"triggerHeight":"40px","triggerMargin":"0px","dropdownBorder":false,"optionFontSize":"14px","optionIconSize":"16px","triggerBgColor":"#FFFFFF","triggerPadding":"12px","triggerShadows":"","dropdownBgColor":"#FFFFFF","dropdownPadding":"12px","dropdownShadows":"0px 3px 5px 2px #6666661C","mappingDisabled":{"code":"false","type":"f"},"optionFontColor":"black","optionIconColor":"#4D4D4D","searchBorderAll":"1px solid #A6A6A6","searchFontColor":"black","triggerIconOpen":"fas fa-angle-up","triggerIconSize":"16px","unselectOnClick":true,"customValidation":false,"selectedFontSize":"14px","triggerBorderAll":"1px solid #BABABA","triggerIconClose":"fas fa-angle-down","triggerIconColor":"#666666","dropdownBorderAll":"1px solid #BABABA","selectedFontColor":"#333","selectedTextAlign":"left","optionBgColorHover":"#f5f5f5","searchBorderRadius":"4px","closeOnClickOutside":true,"placeholderFontSize":"14px","triggerBorderRadius":"8px","virtualScrollBuffer":600,"dropdownBorderRadius":"8px","optionBgColorFocused":"#f5f5f5","placeholderFontColor":"#A1A1A1","searchPlaceholder":{"en":"Search","fr":"Rechercher"},"searchPlaceholderColor":"gray","virtualScrollMinItemSize":40,"optionBorder":"0px solid #000000","optionBorderRadius":"4px","optionSpacing":"4px","searchMargin":"0 0 8px 0","placeholderTextAlign":"left","emptyStateText":{"en":"No results found","fr":"Aucun résultat"}}},"styles":{"default":{"width":"300px","zIndex":1,"display":"block"}}} + +{"uid":"select-car","tag":"ww-input-select","name":"Vehicle Multi Select","settings":{"dynamicConfiguration":{"content":{"choices":[{"key":"label","type":"Text"},{"key":"value","type":"Text"},{"key":"emoji","type":"Text"},{"key":"description","type":"Text"}]}}},"props":{"choices":[{"emoji":"🚗","label":"Sedan","value":"sedan","description":"Perfect for daily commuting"}],"selectType":"single","showSearch":true,"mappingLabel":{"js":"return context.mapping?.['emoji']+' '+context.mapping?.['label']",},"mappingValue":{"js":"return context.mapping?.['value']",}},"events":[{"trigger":"change","name":"Handle change","js":"console.log(event.value)","description":"..."}]} + - Multiselect example (changes from simple select) -{"uid":"multi-select-car","tag":"ww-input-select","name":"Vehicle Multi Select",...,"props":{"chipFontColor":"#FFFFFF","chipBgColor":"#454545","chipPadding":"2px 6px","chipBorderRadius":"8px","chipIconUnselect":"wwi wwi-cross","chipIconSize":"12px","chipIconColor":"#FFFFFF","closeOnSelect":false,...}...} + +{"uid":"multi-select-car","tag":"ww-input-select","name":"Vehicle Multi Select",...,"props":{"chipFontColor":"#FFFFFF","chipBgColor":"#454545","chipPadding":"2px 6px","chipBorderRadius":"8px","chipIconUnselect":"lucide/x","chipIconSize":"12px","chipIconColor":"#FFFFFF","closeOnSelect":false,...}...} + Important for multiselect : "triggerHeight" have to be "unset" to let the component calculate it with the number of chips. @@ -194,10 +189,11 @@ Important for multiselect : "triggerHeight" have to be "unset" to let the compon If user asks for multi lang support, you can use globalContext page lang and set : - choices: [{"emoji": "🇬🇧","value": "uk","label": {"en": "United Kingdom","fr": "Royaume uni"}},{"emoji": "🇨🇳","value": "ch","label": {"en": "China","fr":"Chine"}}] - dynamicConfiguration: {"content": {"choices": [{"key": "emoji","type": "Text"},{"key": "label","type": "RawObject"},{"key": "value","type": "Text"}]}} -- mappingLabel: {"type": "js","code": "return context.mapping?.['emoji']+ ' ' +context.mapping?.['label'] ?.[ globalContext.page?.['lang'] ]"} +- mappingLabel: {"js": "return context.mapping?.['emoji']+ ' ' +context.mapping?.['label'] ?.[ globalContext.page?.['lang'] ]"} -10. **Notes:** +***Notes:*** -**CRITICAL** : mappingLabel + mappingValue are 'Formula' type like : {"type":"js","code":"//code"). It's not bindings ! So do not use __wwtype key. +**CRITICAL** : If choices is an array of *objetcs*, you better use a specific field for mappingLabel and mappingValue like context.mapping?.['id'] etc. Do not use the whole context.mapping in this case. **CRITICAL** : labels are always text, no img, no icons, no html etc. Do never attempts to return html in mappingLabel. **CRITICAL** : You have to perfectly style this select according to the page. +**CRITICAL** : For multiselect, "triggerHeight" should be "unset" to let the component calculate it with the number of chips. diff --git a/src/utils.js b/src/utils.js index 9ceb33c..06d2e2a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -5,3 +5,21 @@ export function debounce(fn, delay) { timeoutId = setTimeout(() => fn(...args), delay); }; } + +export function findValueIndex(array, value) { + if (typeof value === 'object' && value !== null) { + return array.findIndex(item => { + if (typeof item !== 'object' || item === null) return false; + return JSON.stringify(item) === JSON.stringify(value); + }); + } else { + return array.indexOf(value); + } +} + +export function areValuesEqual(value1, value2) { + if (typeof value1 === 'object' && value1 !== null && typeof value2 === 'object' && value2 !== null) { + return JSON.stringify(value1) === JSON.stringify(value2); + } + return value1 === value2; +} \ No newline at end of file diff --git a/src/wwElement_Option.vue b/src/wwElement_Option.vue index 15cc67f..9967e4f 100644 --- a/src/wwElement_Option.vue +++ b/src/wwElement_Option.vue @@ -20,6 +20,7 @@ import { ref, unref, toValue, inject, computed, watch, onBeforeUnmount, watchEff import useAccessibility from './useAccessibility_Option'; /* wwEditor:start */ import useEditorHint from './editor/useEditorHint'; +import { areValuesEqual } from './utils'; /* wwEditor:end */ const SELECT_OPTION_ICON = @@ -114,8 +115,8 @@ export default { const isSelected = computed(() => selectType.value === 'single' - ? selectValue.value === value.value - : Array.isArray(selectValue.value) && selectValue.value.includes(value.value) + ? areValuesEqual(selectValue.value, value.value) + : Array.isArray(selectValue.value) && selectValue.value.some(v => areValuesEqual(v, value.value)) ); const { optionId, handleKeyDown, focusFromOptionId } = useAccessibility({ diff --git a/src/wwElement_Select.vue b/src/wwElement_Select.vue index 20732ce..ce3898f 100644 --- a/src/wwElement_Select.vue +++ b/src/wwElement_Select.vue @@ -67,7 +67,8 @@ import InputSelectSearch from './wwElement_Search.vue'; import { ref, computed, provide, watch, inject, nextTick, toValue, onMounted, onBeforeUnmount, shallowRef } from 'vue'; import useAccessibility from './select/useAccessibility'; import useSearch from './select/useSearch'; -import { debounce } from './utils'; +import { debounce, findValueIndex, areValuesEqual } from './utils'; + /* wwEditor:start */ import useEditorHint from './editor/useEditorHint'; /* wwEditor:end */ @@ -105,18 +106,13 @@ export default { // eslint-disable-next-line no-unreachable return false; }); - const forceOpenInEditor = computed(() => { - /* wwEditor:start */ - return props.wwEditorState.sidepanelContent.forceOpenInEditor; - /* wwEditor:end */ - return false; - }); + const forceOpenInEditor = ref(false); const appDivRef = shallowRef(wwLib.getFrontDocument().querySelector('#app')); const selectType = computed(() => props.content.selectType); const initValue = computed(() => - selectType.value === 'single' ? props.content.initValueSingle ?? null : props.content.initValueMulti || [] + selectType.value === 'single' ? (props.content.initValueSingle ?? null) : props.content.initValueMulti || [] ); const { value: variableValue, setValue } = wwLib.wwVariable.useComponentVariable({ uid: props.uid, @@ -245,7 +241,8 @@ export default { const currentValue = Array.isArray(variableValue.value) ? [...variableValue.value] : []; for (let iValue of value) { - const valueIndex = currentValue.indexOf(iValue); + // Find index using the utility function + const valueIndex = findValueIndex(currentValue, iValue); if (valueIndex === -1) { if (!props.content.limit || currentValue.length < props.content.limit) @@ -279,7 +276,17 @@ export default { } } else { const currentValue = Array.isArray(variableValue.value) ? [...variableValue.value] : []; - const valueIndex = currentValue.indexOf(value); + + // Find the index with proper object comparison if needed + let valueIndex = -1; + if (typeof value === 'object' && value !== null) { + valueIndex = currentValue.findIndex(item => { + if (typeof item !== 'object' || item === null) return false; + return JSON.stringify(item) === JSON.stringify(value); + }); + } else { + valueIndex = currentValue.indexOf(value); + } if (valueIndex >= 0) { // Unelect ? @@ -309,7 +316,9 @@ export default { shouldCloseDropdown.value = false; const currentValue = Array.isArray(variableValue.value) ? [...variableValue.value] : []; - const valueIndex = currentValue.indexOf(valueToRemove); + + // Find index using the utility function + const valueIndex = findValueIndex(currentValue, valueToRemove); if (valueIndex !== -1) { currentValue.splice(valueIndex, 1); @@ -425,29 +434,43 @@ export default { resolveMappingFormula(toValue(mappingValue), option) ?? option.value, option, ]) - ); // Hide optionId - const obj = opt => ({ + ); + + // Format option for display + const formatOption = opt => ({ value: resolveMappingFormula(toValue(mappingValue), opt) ?? opt.value, label: resolveMappingFormula(toValue(mappingLabel), opt) ?? opt.value, disabled: opt.disabled || false, data: opt || {}, }); + // Find option by value using the utility function + const findOptionByValue = value => { + // Use the utility function to find an entry with matching key + const entry = Array.from(_optionsMap.entries()).find(([key, _]) => areValuesEqual(key, value)); + return entry ? entry[1] : null; + }; + + // Handle single select if (selectType.value === 'single') { - const option = _optionsMap.get(variableValue.value); - if (!option) return null; - return obj(option); - } else { + const option = findOptionByValue(variableValue.value); + return option ? formatOption(option) : null; + } + // Handle multiple select + else { const selectedValues = Array.isArray(variableValue.value) ? variableValue.value : []; return selectedValues.map(value => { - const option = _optionsMap.get(value); - if (!option) + const option = findOptionByValue(value); + + if (!option) { return { value, isInOptions: false, info: 'This value is not in the defined options', }; - return obj(option); + } + + return formatOption(option); }); } }); @@ -470,19 +493,25 @@ export default { // Initialize passive event support detection try { - window.addEventListener("test", null, Object.defineProperty({}, 'passive', { - get: function () { supportsPassive = true; } - })); - } catch(e) {} + window.addEventListener( + 'test', + null, + Object.defineProperty({}, 'passive', { + get: function () { + supportsPassive = true; + }, + }) + ); + } catch (e) {} wheelOpt = supportsPassive ? { passive: false } : false; wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; - const preventDefault = (e) => { + const preventDefault = e => { e.preventDefault(); }; - const preventDefaultForScrollKeys = (e) => { + const preventDefaultForScrollKeys = e => { const keys = { 37: 1, 38: 1, 39: 1, 40: 1 }; if (keys[e.keyCode]) { preventDefault(e); @@ -835,6 +864,9 @@ export default { this.resetLastTriggerComponentAction(); this.resetSearch(); }, + toggleForceOpenInEditor() { + this.forceOpenInEditor = !this.forceOpenInEditor; + }, }, }; diff --git a/ww-config.js b/ww-config.js index c597335..3bd2247 100644 --- a/ww-config.js +++ b/ww-config.js @@ -138,7 +138,7 @@ export default { ], ], customSettingsPropertiesOrder: [ - 'forceOpenInEditor', + 'toggleSelect', 'showEmptyStateInEditor', 'choices', 'mappingLabel', @@ -196,11 +196,6 @@ export default { action: 'actionCloseDropdown', args: [], }, - { - label: 'Toggle', - action: 'actionToggleDropdown', - args: [], - }, { label: 'Set value', action: 'actionUpdateValue', @@ -233,8 +228,22 @@ export default { action: 'actionResetSearch', args: [], }, + { + label: 'Toggle force open in editor', + action: 'toggleForceOpenInEditor', + args: [], + }, ], properties: { + toggleSelect: { + type: 'Button', + editorOnly: true, + section: 'settings', + options: { + text: { en: 'Toggle' }, + action: 'toggleForceOpenInEditor', + }, + }, // >>>>>>>>>>> SELECT <<<<<<<<<< choices: { label: { @@ -429,13 +438,6 @@ export default { /* wwEditor:end */ section: 'settings', }, - forceOpenInEditor: { - label: { en: 'Force open in editor' }, - type: 'OnOff', - defaultValue: false, - editorOnly: true, - section: 'settings', - }, initialState: { label: { en: 'Initial state' }, type: 'TextSelect', @@ -808,8 +810,7 @@ export default { bindable: true, responsive: true, propertyHelp: { - tooltip: - 'This should be disabled in some edge cases like in popups, datagrid, etc.', + tooltip: 'This should be disabled in some edge cases like in popups, datagrid, etc.', }, bindingValidation: { type: 'boolean', @@ -2011,7 +2012,7 @@ export default { en: 'background color', }, type: 'Color', - defaultValue: 'white', + defaultValue: undefined, states: true, classes: true, bindable: true,