@trimble-oss/moduswebcomponents-mcp 1.0.1 → 1.2.0

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.
@@ -0,0 +1,415 @@
1
+ {
2
+ "description": "A customizable autocomplete component used to create searchable text inputs. The component supports a `<slot>` for injecting custom content.",
3
+ "properties": [
4
+ {
5
+ "name": "autoComplete",
6
+ "type": "AutocompleteTypes",
7
+ "description": "Hint for form autofill feature.",
8
+ "default": null,
9
+ "mutable": false,
10
+ "end_line": 74
11
+ },
12
+ {
13
+ "name": "bordered",
14
+ "type": "boolean",
15
+ "description": "Indicates that the autocomplete should have a border.",
16
+ "default": "true",
17
+ "mutable": false,
18
+ "end_line": 77
19
+ },
20
+ {
21
+ "name": "customClass",
22
+ "type": "string",
23
+ "description": "Custom CSS class to apply to host element.",
24
+ "default": "''",
25
+ "mutable": false,
26
+ "end_line": 80
27
+ },
28
+ {
29
+ "name": "debounceMs",
30
+ "type": "number",
31
+ "description": "The debounce timeout in milliseconds. Set to 0 to disable debouncing.",
32
+ "default": "300",
33
+ "mutable": false,
34
+ "end_line": 86
35
+ },
36
+ {
37
+ "name": "disabled",
38
+ "type": "boolean",
39
+ "description": "Whether the form control is disabled.",
40
+ "default": "false",
41
+ "mutable": false,
42
+ "end_line": 89
43
+ },
44
+ {
45
+ "name": "feedback",
46
+ "type": "IInputFeedbackProp",
47
+ "description": "Feedback state for the input field.",
48
+ "default": null,
49
+ "mutable": false,
50
+ "end_line": 92
51
+ },
52
+ {
53
+ "name": "includeClear",
54
+ "type": "boolean",
55
+ "description": "Show the clear button within the input field.",
56
+ "default": "false",
57
+ "mutable": false,
58
+ "end_line": 95
59
+ },
60
+ {
61
+ "name": "includeSearch",
62
+ "type": "boolean",
63
+ "description": "Show the search icon within the input field.",
64
+ "default": "false",
65
+ "mutable": false,
66
+ "end_line": 98
67
+ },
68
+ {
69
+ "name": "inputId",
70
+ "type": "string",
71
+ "description": "The ID of the input element.",
72
+ "default": null,
73
+ "mutable": false,
74
+ "end_line": 101
75
+ },
76
+ {
77
+ "name": "inputTabIndex",
78
+ "type": "number",
79
+ "description": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key).",
80
+ "default": null,
81
+ "mutable": false,
82
+ "end_line": 104
83
+ },
84
+ {
85
+ "name": "items",
86
+ "type": "IAutocompleteItem[]",
87
+ "description": "The items to display in the menu. Creating a new array of items will ensure proper component re-render.",
88
+ "default": "[]",
89
+ "mutable": false,
90
+ "end_line": 110
91
+ },
92
+ {
93
+ "name": "label",
94
+ "type": "string",
95
+ "description": "The text to display within the label.",
96
+ "default": null,
97
+ "mutable": false,
98
+ "end_line": 113
99
+ },
100
+ {
101
+ "name": "leaveMenuOpen",
102
+ "type": "boolean",
103
+ "description": "Whether the menu should remain open after an item is selected.",
104
+ "default": "false",
105
+ "mutable": false,
106
+ "end_line": 116
107
+ },
108
+ {
109
+ "name": "minChars",
110
+ "type": "number",
111
+ "description": "The minimum number of characters required to render the menu.",
112
+ "default": "0",
113
+ "mutable": false,
114
+ "end_line": 119
115
+ },
116
+ {
117
+ "name": "multiSelect",
118
+ "type": "boolean",
119
+ "description": "Whether the input allows multiple items to be selected.",
120
+ "default": "false",
121
+ "mutable": false,
122
+ "end_line": 122
123
+ },
124
+ {
125
+ "name": "name",
126
+ "type": "string",
127
+ "description": "Name of the form control. Submitted with the form as part of a name/value pair.",
128
+ "default": null,
129
+ "mutable": false,
130
+ "end_line": 125
131
+ },
132
+ {
133
+ "name": "noResults",
134
+ "type": "IAutocompleteNoResults",
135
+ "description": "The content to display when no results are found.",
136
+ "default": "{ ariaLabel: 'No results found', label: 'No results found', subLabel: 'Check spelling or try a different keyword', }",
137
+ "mutable": false,
138
+ "end_line": 132
139
+ },
140
+ {
141
+ "name": "placeholder",
142
+ "type": "string",
143
+ "description": "Text that appears in the form control when it has no value set.",
144
+ "default": "''",
145
+ "mutable": false,
146
+ "end_line": 135
147
+ },
148
+ {
149
+ "name": "readOnly",
150
+ "type": "boolean",
151
+ "description": "Whether the value is editable.",
152
+ "default": "false",
153
+ "mutable": false,
154
+ "end_line": 138
155
+ },
156
+ {
157
+ "name": "required",
158
+ "type": "boolean",
159
+ "description": "A value is required for the form to be submittable.",
160
+ "default": "false",
161
+ "mutable": false,
162
+ "end_line": 141
163
+ },
164
+ {
165
+ "name": "showMenuOnFocus",
166
+ "type": "boolean",
167
+ "description": "Whether to show the menu whenever the input has focus, regardless of input value.",
168
+ "default": "false",
169
+ "mutable": false,
170
+ "end_line": 144
171
+ },
172
+ {
173
+ "name": "size",
174
+ "type": "ModusSize",
175
+ "description": "The size of the autocomplete (input and menu).",
176
+ "default": "'md'",
177
+ "mutable": false,
178
+ "end_line": 147
179
+ },
180
+ {
181
+ "name": "showSpinner",
182
+ "type": "boolean",
183
+ "description": "A spinner that appears when set to true",
184
+ "default": "false",
185
+ "mutable": false,
186
+ "end_line": 150
187
+ },
188
+ {
189
+ "name": "value",
190
+ "type": "string",
191
+ "description": "The value of the control.",
192
+ "default": "''",
193
+ "mutable": true,
194
+ "end_line": 153
195
+ },
196
+ {
197
+ "name": "maxChips",
198
+ "type": "number",
199
+ "description": "Maximum number of chips to display. When exceeded, shows expand/collapse button. Set to -1 to disable limit.",
200
+ "default": "-1",
201
+ "mutable": false,
202
+ "end_line": 156
203
+ },
204
+ {
205
+ "name": "customItemSelect",
206
+ "type": "(item: IAutocompleteItem)",
207
+ "description": "Custom item selection handler - if provided, overrides default selection logic",
208
+ "default": "> void",
209
+ "mutable": false,
210
+ "end_line": 159
211
+ },
212
+ {
213
+ "name": "customInputChange",
214
+ "type": "(value: string)",
215
+ "description": "Custom input change handler - if provided, overrides default search filtering",
216
+ "default": "> void",
217
+ "mutable": false,
218
+ "end_line": 162
219
+ },
220
+ {
221
+ "name": "customKeyDown",
222
+ "type": "(event: KeyboardEvent)",
223
+ "description": "Custom key down handler - if provided, overrides default keyboard navigation",
224
+ "default": "> void",
225
+ "mutable": false,
226
+ "end_line": 165
227
+ },
228
+ {
229
+ "name": "customBlur",
230
+ "type": "(event: FocusEvent)",
231
+ "description": "Custom blur handler - if provided, overrides default blur behavior",
232
+ "default": "> void",
233
+ "mutable": false,
234
+ "end_line": 168
235
+ },
236
+ {
237
+ "name": "minInputWidth",
238
+ "type": "number",
239
+ "description": "Minimum width for the text input in pixels. When chips would make input smaller, container height increases instead.",
240
+ "default": "10",
241
+ "mutable": false,
242
+ "end_line": 171
243
+ }
244
+ ],
245
+ "events": [
246
+ {
247
+ "name": "chipRemove",
248
+ "detail": "IAutocompleteItem",
249
+ "description": "Event emitted when a selected item chip is removed.",
250
+ "end_line": 174
251
+ },
252
+ {
253
+ "name": "chipsExpansionChange",
254
+ "detail": "{ expanded: boolean }",
255
+ "description": "Event emitted when chips expansion state changes.",
256
+ "end_line": 177
257
+ },
258
+ {
259
+ "name": "clearClick",
260
+ "detail": "void",
261
+ "description": "Event emitted when the clear button is clicked.",
262
+ "end_line": 180
263
+ },
264
+ {
265
+ "name": "inputBlur",
266
+ "detail": "FocusEvent",
267
+ "description": "Event emitted when the input loses focus.",
268
+ "end_line": 183
269
+ },
270
+ {
271
+ "name": "inputChange",
272
+ "detail": "Event",
273
+ "description": "Event emitted when the input value changes. This event is debounced based on the debounceMs prop.",
274
+ "end_line": 189
275
+ },
276
+ {
277
+ "name": "inputFocus",
278
+ "detail": "FocusEvent",
279
+ "description": "Event emitted when the input gains focus.",
280
+ "end_line": 192
281
+ },
282
+ {
283
+ "name": "itemSelect",
284
+ "detail": "IAutocompleteItem",
285
+ "description": "Event emitted when a menu item is selected.",
286
+ "end_line": 195
287
+ }
288
+ ],
289
+ "methods": [
290
+ {
291
+ "name": "selectItem",
292
+ "signature": "(item: IAutocompleteItem | null)",
293
+ "parameters": "item: IAutocompleteItem | null",
294
+ "returnType": "void",
295
+ "description": "Programmatically select an item",
296
+ "end_line": 656
297
+ },
298
+ {
299
+ "name": "openMenu",
300
+ "signature": "()",
301
+ "parameters": "",
302
+ "returnType": "void",
303
+ "description": "Programmatically open the menu",
304
+ "end_line": 679
305
+ },
306
+ {
307
+ "name": "closeMenu",
308
+ "signature": "()",
309
+ "parameters": "",
310
+ "returnType": "void",
311
+ "description": "Programmatically close the menu",
312
+ "end_line": 689
313
+ },
314
+ {
315
+ "name": "toggleMenu",
316
+ "signature": "()",
317
+ "parameters": "",
318
+ "returnType": "void",
319
+ "description": "Programmatically toggle the menu open/closed",
320
+ "end_line": 700
321
+ },
322
+ {
323
+ "name": "focusInput",
324
+ "signature": "()",
325
+ "parameters": "",
326
+ "returnType": "void",
327
+ "description": "Programmatically set focus to input",
328
+ "end_line": 714
329
+ },
330
+ {
331
+ "name": "clearInput",
332
+ "signature": "()",
333
+ "parameters": "",
334
+ "returnType": "void",
335
+ "description": "Clear the input value and reset items",
336
+ "end_line": 726
337
+ }
338
+ ],
339
+ "slots": [
340
+ {
341
+ "name": "menu-items",
342
+ "description": "Slot for menu-items content"
343
+ }
344
+ ],
345
+ "examples": {
346
+ "basic": "<style>\n div[id^='story--components-forms-autocomplete--default'] {\n height: 400px;\n }\n</style>\n<modus-wc-autocomplete\n aria-label=\"Fruit autocomplete\"\n auto-complete=${ifDefined(args['auto-complete'])}\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n .feedback=${ifDefined(args.feedback)}\n ?include-clear=${args['include-clear']}\n ?include-search=${args['include-search']}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${args['min-chars']}\n min-input-width=${ifDefined(args['min-input-width'])}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=${ifDefined(args.placeholder)}\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n ?show-spinner=${args['show-spinner']}\n size=${ifDefined(args.size)}\n value=${args.value}\n></modus-wc-autocomplete>\n<script>\n// Add Autocomplete items\n${Items}\n// Adding this block to show how to set items via JS\n// const autocomplete = document.querySelector('modus-wc-autocomplete');\n// autocomplete.items = autocompleteItems;\n</script>",
347
+ "variations": [],
348
+ "args": {
349
+ "auto-complete": "undefined",
350
+ "bordered": "true",
351
+ "debounce-ms": "300",
352
+ "disabled": "false",
353
+ "include-clear": "false",
354
+ "include-search": "false",
355
+ "items": "items",
356
+ "label": "'Label'",
357
+ "leave-menu-open": "false",
358
+ "max-chips": "4",
359
+ "min-chars": "0",
360
+ "min-input-width": "15",
361
+ "multi-select": "false",
362
+ "show-menu-on-focus": "false",
363
+ "show-spinner": "false",
364
+ "size": "'md'",
365
+ "value": "''"
366
+ },
367
+ "argTypes": {},
368
+ "usage": [],
369
+ "events": [
370
+ "chipRemove",
371
+ "chipsExpansionChange",
372
+ "clearClick",
373
+ "inputBlur",
374
+ "inputChange",
375
+ "inputFocus",
376
+ "itemSelect",
377
+ ""
378
+ ]
379
+ },
380
+ "tag": "modus-wc-autocomplete",
381
+ "storyExample": {
382
+ "template": "<style>\n div[id^='story--components-forms-autocomplete--default'] {\n height: 400px;\n }\n</style>\n<modus-wc-autocomplete\n aria-label=\"Fruit autocomplete\"\n auto-complete=${ifDefined(args['auto-complete'])}\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n .feedback=${ifDefined(args.feedback)}\n ?include-clear=${args['include-clear']}\n ?include-search=${args['include-search']}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${args['min-chars']}\n min-input-width=${ifDefined(args['min-input-width'])}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=${ifDefined(args.placeholder)}\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n ?show-spinner=${args['show-spinner']}\n size=${ifDefined(args.size)}\n value=${args.value}\n></modus-wc-autocomplete>\n<script>\n// Add Autocomplete items\n${Items}\n// Adding this block to show how to set items via JS\n// const autocomplete = document.querySelector('modus-wc-autocomplete');\n// autocomplete.items = autocompleteItems;\n</script>",
383
+ "args": {
384
+ "auto-complete": "undefined",
385
+ "bordered": "true",
386
+ "debounce-ms": "300",
387
+ "disabled": "false",
388
+ "include-clear": "false",
389
+ "include-search": "false",
390
+ "items": "items",
391
+ "label": "'Label'",
392
+ "leave-menu-open": "false",
393
+ "max-chips": "4",
394
+ "min-chars": "0",
395
+ "min-input-width": "15",
396
+ "multi-select": "false",
397
+ "show-menu-on-focus": "false",
398
+ "show-spinner": "false",
399
+ "size": "'md'",
400
+ "value": "''"
401
+ },
402
+ "argTypes": {},
403
+ "events": [
404
+ "chipRemove",
405
+ "chipsExpansionChange",
406
+ "clearClick",
407
+ "inputBlur",
408
+ "inputChange",
409
+ "inputFocus",
410
+ "itemSelect",
411
+ ""
412
+ ],
413
+ "fullContent": "import { withActions } from '@storybook/addon-actions/decorator';\nimport { Meta, StoryObj } from '@storybook/web-components';\nimport { html } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { createShadowHostClass } from '../../providers/shadow-dom/shadow-host-helper';\nimport {\n AutocompleteTypes,\n IAutocompleteItem,\n IAutocompleteNoResults,\n ModusSize,\n} from '../types';\n\n// Updated items array includes an optional \"focused\" property.\nconst items: IAutocompleteItem[] = [\n {\n label: 'Apple',\n value: 'apple',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Banana',\n value: 'banana',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Blueberry',\n value: 'blueberry',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Cherry',\n value: 'cherry',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Grape',\n value: 'grape',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Lemon',\n value: 'lemon',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Orange',\n value: 'orange',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Peach',\n value: 'peach',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Pear',\n value: 'pear',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Strawberry',\n value: 'strawberry',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Watermelon',\n value: 'watermelon',\n visibleInMenu: true,\n focused: false,\n disabled: false,\n checkbox: false,\n },\n {\n label: 'Pineapple',\n value: 'pineapple',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Kiwi',\n value: 'kiwi',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Mango',\n value: 'mango',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Papaya',\n value: 'papaya',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Plum',\n value: 'plum',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Raspberry',\n value: 'raspberry',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n {\n label: 'Tangerine',\n value: 'tangerine',\n visibleInMenu: true,\n focused: false,\n checkbox: false,\n },\n];\n\ninterface AutocompleteArgs {\n visibleItems: IAutocompleteItem[];\n 'auto-complete'?: AutocompleteTypes;\n bordered?: boolean;\n 'custom-class'?: string;\n 'debounce-ms'?: number;\n disabled?: boolean;\n feedback?: { level: 'error' | 'warning' | 'success'; message: string };\n 'include-clear'?: boolean;\n 'include-search'?: boolean;\n 'input-id'?: string;\n 'input-tab-index'?: number;\n items: IAutocompleteItem[];\n initialNavigation?: boolean;\n label?: string;\n 'leave-menu-open'?: boolean;\n 'max-chips'?: number;\n 'min-chars': number;\n 'min-input-width'?: number;\n 'multi-select'?: boolean;\n name?: string;\n 'no-results': IAutocompleteNoResults;\n placeholder?: string;\n 'read-only'?: boolean;\n required?: boolean;\n 'show-menu-on-focus'?: boolean;\n 'show-spinner'?: boolean;\n size?: ModusSize;\n value: string;\n 'custom-blur'?: (event: FocusEvent) => void;\n 'custom-input-change'?: (value: string) => void;\n 'custom-item-select'?: (item: IAutocompleteItem) => void;\n 'custom-key-down'?: (event: KeyboardEvent) => void;\n}\n\nconst meta: Meta<AutocompleteArgs> = {\n title: 'Components/Forms/Autocomplete',\n component: 'modus-wc-autocomplete',\n args: {\n 'auto-complete': undefined,\n bordered: true,\n 'debounce-ms': 300,\n disabled: false,\n 'include-clear': false,\n 'include-search': false,\n items: items,\n label: 'Label',\n 'leave-menu-open': false,\n 'max-chips': 4,\n 'min-chars': 0,\n 'min-input-width': 15,\n 'multi-select': false,\n 'show-menu-on-focus': false,\n 'show-spinner': false,\n 'no-results': {\n ariaLabel: 'No results found',\n label: 'No results found',\n subLabel: 'Check spelling or try a different keyword',\n },\n size: 'md',\n value: '',\n },\n argTypes: {\n 'auto-complete': {\n control: { type: 'text' },\n },\n items: {\n description: 'Array of items for the autocomplete component',\n table: {\n type: {\n detail: `\n Interface: IAutocompleteItem\n Properties:\n - label (string): The display text shown for the autocomplete item\n - selected (boolean, optional): Whether the item is currently selected\n - focused (boolean, optional): Whether the item is focused\n - value (string): The unique value identifier for the item\n - visibleInMenu (boolean): Whether the item should be shown in the dropdown menu\n `,\n },\n },\n },\n 'max-chips': {\n control: { type: 'number', min: 1, max: 10 },\n description:\n 'Maximum number of chips to display before showing \"+N more\" button',\n },\n 'min-input-width': {\n control: { type: 'number', min: 10, max: 300 },\n description:\n 'Minimum width for the text input in pixels. When chips would make input smaller, container height increases instead. Default: 20px.',\n },\n size: {\n control: { type: 'select' },\n options: ['sm', 'md', 'lg'],\n },\n 'custom-blur': {\n description:\n 'Custom blur handler function that overrides default blur behavior',\n table: {\n type: { summary: '(event: FocusEvent) => void' },\n category: 'Custom Handlers',\n },\n },\n 'custom-input-change': {\n description:\n 'Custom input change handler function that overrides default input change behavior',\n table: {\n type: { summary: '(value: string) => void' },\n category: 'Custom Handlers',\n },\n },\n 'custom-item-select': {\n description:\n 'Custom item select handler function that overrides default item selection behavior',\n table: {\n type: { summary: '(item: IAutocompleteItem) => void' },\n category: 'Custom Handlers',\n },\n },\n 'custom-key-down': {\n description:\n 'Custom keydown handler function that overrides default keyboard navigation',\n table: {\n type: { summary: '(event: KeyboardEvent) => void' },\n category: 'Custom Handlers',\n },\n },\n },\n decorators: [withActions],\n parameters: {\n actions: {\n handles: [\n 'chipRemove',\n 'chipsExpansionChange',\n 'clearClick',\n 'inputBlur',\n 'inputChange',\n 'inputFocus',\n 'itemSelect',\n ],\n },\n },\n};\n\nexport default meta;\n//prettier-ignore\nconst Items = html`\n// const autocompleteItems = [\n// {\n// label: 'Apple',\n// value: 'apple',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Banana',\n// value: 'banana',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Blueberry',\n// value: 'blueberry',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Cherry',\n// value: 'cherry',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Grape',\n// value: 'grape',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Lemon',\n// value: 'lemon',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Orange',\n// value: 'orange',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Peach',\n// value: 'peach',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Pear',\n// value: 'pear',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Strawberry',\n// value: 'strawberry',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Watermelon',\n// value: 'watermelon',\n// visibleInMenu: true,\n// focused: false,\n// disabled: false,\n// checkbox: false,\n// },\n// {\n// label: 'Pineapple',\n// value: 'pineapple',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Kiwi',\n// value: 'kiwi',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Mango',\n// value: 'mango',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Papaya',\n// value: 'papaya',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Plum',\n// value: 'plum',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Raspberry',\n// value: 'raspberry',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// {\n// label: 'Tangerine',\n// value: 'tangerine',\n// visibleInMenu: true,\n// focused: false,\n// checkbox: false,\n// },\n// ];\n`;\ntype Story = StoryObj<AutocompleteArgs>;\n\nconst Template: Story = {\n render: (args) => {\n // prettier-ignore\n return html`\n<style>\n div[id^='story--components-forms-autocomplete--default'] {\n height: 400px;\n }\n</style>\n<modus-wc-autocomplete\n aria-label=\"Fruit autocomplete\"\n auto-complete=${ifDefined(args['auto-complete'])}\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n .feedback=${ifDefined(args.feedback)}\n ?include-clear=${args['include-clear']}\n ?include-search=${args['include-search']}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${args['min-chars']}\n min-input-width=${ifDefined(args['min-input-width'])}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=${ifDefined(args.placeholder)}\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n ?show-spinner=${args['show-spinner']}\n size=${ifDefined(args.size)}\n value=${args.value}\n></modus-wc-autocomplete>\n<script>\n// Add Autocomplete items\n${Items}\n// Adding this block to show how to set items via JS\n// const autocomplete = document.querySelector('modus-wc-autocomplete');\n// autocomplete.items = autocompleteItems;\n</script>\n `;\n },\n};\n\nexport const Default: Story = {\n ...Template,\n};\n\nexport const WithCustomIconSlot: Story = {\n // prettier-ignore\n render: (args) => html`\n<style>\n div[id^='story--components-forms-autocomplete--with-custom-icon-slot'] {\n height: 400px;\n }\n</style>\n<modus-wc-autocomplete\n aria-label=\"Autocomplete with custom icon\"\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n .feedback=${ifDefined(args.feedback)}\n ?include-clear=${args['include-clear']}\n ?include-search=${args['include-search']}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${args['min-chars']}\n min-input-width=${ifDefined(args['min-input-width'])}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=${ifDefined(args.placeholder)}\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n ?show-spinner=${args['show-spinner']}\n size=${ifDefined(args.size)}\n value=${args.value}\n>\n <modus-wc-icon slot=\"custom-icon\" name=\"heart\" size=\"sm\"></modus-wc-icon>\n</modus-wc-autocomplete>\n<script>\n// Add Autocomplete items\n${Items}\n// Adding this block to show how to set items via JS\n// const autocomplete = document.querySelector('modus-wc-autocomplete');\n// autocomplete.items = autocompleteItems;\n</script>\n `,\n args: {\n placeholder: 'Search fruits...',\n },\n};\n\nexport const WithFeedback: Story = {\n render: (args) => html`\n <style>\n div[id^='story--components-forms-autocomplete--with-feedback'] {\n height: 400px;\n }\n </style>\n <modus-wc-autocomplete\n aria-label=\"Fruit autocomplete with feedback\"\n ?bordered=${args.bordered}\n .items=${args.items}\n .feedback=${args.feedback}\n label=${ifDefined(args.label)}\n ?required=${args.required}\n ></modus-wc-autocomplete>\n `,\n args: {\n feedback: {\n level: 'error',\n message: 'This field is required',\n },\n label: 'With Feedback',\n required: true,\n },\n parameters: {\n docs: {\n source: {\n code: `\n<modus-wc-autocomplete\n aria-label=\"Fruit autocomplete with feedback\"\n label=\"With Feedback\"\n required\n></modus-wc-autocomplete>\n\n<script>\n const autocomplete = document.querySelector('modus-wc-autocomplete');\n autocomplete.feedback = {\n level: 'error',\n message: 'This field is required'\n };\n autocomplete.items = autocompleteItems;\n</script>\n `,\n },\n },\n },\n};\n\nexport const WithTooltips: Story = {\n name: 'With Tooltips',\n parameters: {\n docs: {\n description: {\n story:\n 'This example demonstrates menu items with tooltips. Hover over the items to see the tooltips.',\n },\n source: {\n code: `\n <script>\nconst tooltipItems = [\n {\n label: 'Apple',\n value: 'apple',\n tooltipContent: 'A crisp and sweet fruit',\n tooltipPosition: 'top',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Banana',\n value: 'banana',\n tooltipContent: 'A tropical yellow fruit',\n tooltipPosition: 'right',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Cherry',\n value: 'cherry',\n tooltipContent: 'Small red stone fruit',\n tooltipPosition: 'bottom',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Grape',\n value: 'grape',\n tooltipContent: 'Small juicy fruit that grows in clusters',\n tooltipPosition: 'left',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Orange',\n value: 'orange',\n tooltipContent: 'Citrus fruit with a bright color',\n tooltipPosition: 'top',\n visibleInMenu: true,\n focused: false,\n },\n];\n</script>\n<modus-wc-autocomplete\n aria-label=\"Fruits with tooltips\"\n leave-menu-open=\"true\"\n placeholder=\"Search fruits\"\n min-chars=\"0\"\n></modus-wc-autocomplete>\n\n <script>\n// const autocomplete = document.querySelector('modus-wc-autocomplete');\n// autocomplete.items = tooltipItems;\n</script>\n`,\n },\n },\n },\n render: () => {\n const tooltipItems: IAutocompleteItem[] = [\n {\n label: 'Apple',\n value: 'apple',\n tooltipContent: 'A crisp and sweet fruit',\n tooltipPosition: 'top',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Banana',\n value: 'banana',\n tooltipContent: 'A tropical yellow fruit',\n tooltipPosition: 'right',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Cherry',\n value: 'cherry',\n tooltipContent: 'Small red stone fruit',\n tooltipPosition: 'bottom',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Grape',\n value: 'grape',\n tooltipContent: 'Small juicy fruit that grows in clusters',\n tooltipPosition: 'left',\n visibleInMenu: true,\n focused: false,\n },\n {\n label: 'Orange',\n value: 'orange',\n tooltipContent: 'Citrus fruit with a bright color',\n tooltipPosition: 'top',\n visibleInMenu: true,\n focused: false,\n },\n ];\n\n return html`\n <style>\n div[id^='story--components-forms-autocomplete--with-tooltips'] {\n height: 400px;\n }\n </style>\n <div style=\"width: 300px;\">\n <modus-wc-autocomplete\n aria-label=\"Fruits with tooltips\"\n leave-menu-open=\"true\"\n placeholder=\"Search fruits\"\n .items=${tooltipItems}\n min-chars=\"0\"\n ></modus-wc-autocomplete>\n </div>\n `;\n },\n};\n\nexport const MultiSelect: Story = {\n render: (args) => {\n // Ensure args.items is initialized\n if (!args.items) {\n args.items = [...items];\n }\n // If multi-select, set selected state for some items\n args.items = args.items.map((item) => {\n if (item.value === 'apple' || item.value === 'banana') {\n return { ...item, selected: true };\n }\n return item;\n });\n // prettier-ignore\n return html`\n <style>\n div[id^='story--components-forms-autocomplete--multi-select'] {\n height: 400px;\n }\n .modus-wc-autocomplete-multi-select {\n width: 480px !important;\n }\n </style>\n <modus-wc-autocomplete\n aria-label=\"Fruit autocomplete\"\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n ?include-clear=${args['include-clear']}\n ?include-search=${args['include-search']}\n id=\"fruit-autocomplete\"\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n max-chips=${args['max-chips'] ?? 4}\n min-chars=${args['min-chars']}\n min-input-width=${ifDefined(args['min-input-width'])}\n ?multi-select=${true}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=${ifDefined(args.placeholder)}\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n size=${ifDefined(args.size)}\n value=${args.value}\n ></modus-wc-autocomplete>\n <script>\n //Commenting out the scripts to avoid duplicate declaration in storybook code\n // Add Autocomplete items\n ${Items}\n // // If multi-select, set selected state for some items\n // const itemsWithSelection = autocompleteItems.map((item) => {\n // if (item.value === 'apple' || item.value === 'banana') {\n // return { ...item, selected: true };\n // }\n // return item;\n // });\n // // Adding this block to show how to set items and pre-selected values via JS\n // const autocomplete = document.getElementById('fruit-autocomplete');\n // if (autocomplete) {\n // autocomplete.items = itemsWithSelection;\n // }\n </script>\n `;\n },\n};\n\nexport const WithSpinner: Story = {\n render: (args) => {\n let debounceTimer: number;\n\n const handleInputChange = (e: CustomEvent<Event>) => {\n if (!e.detail?.target) return;\n\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as Element & {\n items: IAutocompleteItem[];\n showSpinner: boolean;\n value: string;\n };\n\n if (autocomplete) {\n const input = e.detail.target as HTMLInputElement;\n const searchText = input.value.toLowerCase();\n\n // Clear previous timeout to avoid multiple API calls\n if (debounceTimer) {\n window.clearTimeout(debounceTimer);\n }\n\n // Show spinner immediately and update input value\n autocomplete.showSpinner = true;\n\n // Simulate an API call with a 2-second delay\n debounceTimer = window.setTimeout(() => {\n // Filter the master list of items to get the new results\n const filteredItems = items.filter((item) =>\n item.label.toLowerCase().includes(searchText)\n );\n\n // Update the component with the new filtered list and hide the spinner\n autocomplete.items = filteredItems;\n autocomplete.showSpinner = false;\n }, 2000);\n }\n };\n // prettier-ignore\n return html`\n <style>\n div[id^='story--components-forms-autocomplete--with-spinner'] {\n height: 400px;\n }\n </style>\n <modus-wc-autocomplete\n aria-label=\"Fruit autocomplete with spinner\"\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${args['min-chars']}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n placeholder=${ifDefined(args.placeholder)}\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n ?show-spinner=${args['show-spinner']}\n size=${ifDefined(args.size)}\n value=${args.value}\n @inputChange=${handleInputChange}\n ></modus-wc-autocomplete>\n <script>\n //Commenting out the scripts to avoid duplicate declaration in storybook code\n // Add Autocomplete items\n ${Items}\n // // Adding this block to show how to set items via JS\n // const autocomplete = document.querySelector('modus-wc-autocomplete');\n // autocomplete.items = autocompleteItems;\n // autocomplete.showSpinner = true;\n\n // // Debounce timer id\n // let debounceTimer;\n\n // // Input change handler for the component's custom event\n // const handleInputChange = (e) => {\n // // Stencil event detail wraps the native event; guard against missing target\n // if (!e.detail?.target) return;\n // const comp = e.target.closest('modus-wc-autocomplete');\n // if (!comp) return;\n\n // const inputEl = e.detail.target; //op native input element\n // const query = (inputEl.value || '').toLowerCase();\n\n // // Clear pending debounce\n // if (debounceTimer) {\n // clearTimeout(debounceTimer);\n // }\n\n // // If query empty restore full list immediately and stop spinner\n // if (query === '') {\n // comp.items = [...autocompleteItems];\n // comp.showSpinner = false;\n // return;\n // }\n\n // // Start spinner\n // comp.showSpinner = true;\n\n // // Simulated async fetch (2s)\n // debounceTimer = setTimeout(() => {\n // // Filter original dataset (do NOT mutate source array)\n // const filtered = autocompleteItems.filter((item) =>\n // item.label.toLowerCase().includes(query)\n // );\n // // Apply results\n // comp.items = filtered;\n // comp.showSpinner = false;\n // }, 2000);\n // };\n\n // // Attach listener once (avoid duplicates if script re-executes)\n // if (autocomplete) {\n // autocomplete.removeEventListener('inputChange', handleInputChange);\n // autocomplete.addEventListener('inputChange', handleInputChange);\n // }\n\n </script>\n `;\n },\n};\n\nexport const CustomMenuItems: Story = {\n render: (args) => {\n const originalNoResults = args['no-results'];\n if (args['leave-menu-open'] == true) {\n args['no-results'] = {\n ariaLabel: '',\n label: '',\n subLabel: '',\n };\n }\n\n const getVisibleItems = (autocomplete: Element): HTMLElement[] => {\n const menuItems = autocomplete.querySelectorAll(\n 'modus-wc-menu-item:not([disabled])'\n );\n return Array.from(menuItems).filter(\n (item: Element): item is HTMLElement => {\n const style = window.getComputedStyle(item);\n return style.display !== 'none' && !item.classList.contains('hidden');\n }\n );\n };\n\n const handleCustomKeyDown = (e: KeyboardEvent) => {\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as Element & {\n openMenu: () => Promise<void>;\n closeMenu: () => Promise<void>;\n readOnly?: boolean;\n disabled?: boolean;\n };\n if (!autocomplete) return;\n\n // Don't process keyboard events when disabled or readOnly\n if (autocomplete.disabled || autocomplete.readOnly) return;\n\n const visibleItems = getVisibleItems(autocomplete);\n\n // Get all button elements within visible menu items\n const buttons = visibleItems\n .map((item) => item.querySelector('button'))\n .filter(Boolean) as HTMLButtonElement[];\n const currentFocusedButton = document.activeElement as HTMLButtonElement;\n const currentIndex = buttons.indexOf(currentFocusedButton);\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n // Open menu when arrow key is pressed\n void autocomplete.openMenu();\n\n let nextIndex = currentIndex + 1;\n // Stop at the last item instead of wrapping\n if (nextIndex >= buttons.length) return;\n if (nextIndex < 0) nextIndex = 0;\n\n buttons[nextIndex]?.focus();\n break;\n }\n\n case 'ArrowUp': {\n e.preventDefault();\n // Open menu when arrow key is pressed\n void autocomplete.openMenu();\n\n let prevIndex = currentIndex - 1;\n // Stop at the first item instead of wrapping\n if (prevIndex < 0) return;\n\n buttons[prevIndex]?.focus();\n break;\n }\n\n case 'Enter': {\n e.preventDefault();\n // If a button is focused, click it\n if (buttons.includes(currentFocusedButton)) {\n currentFocusedButton.click();\n }\n const input = autocomplete.querySelector('input');\n input?.focus();\n break;\n }\n\n case 'Escape': {\n e.preventDefault();\n void autocomplete.closeMenu();\n // Return focus to input\n const input = autocomplete.querySelector('input');\n input?.focus();\n break;\n }\n }\n };\n\n const handleInputChange = (e: CustomEvent<Event>) => {\n if (!e.detail?.target) return;\n\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as Element & { noResults: IAutocompleteNoResults };\n\n if (autocomplete) {\n const searchText = (\n e.detail.target as HTMLInputElement\n ).value.toLowerCase();\n\n const menuItems = autocomplete?.querySelectorAll('modus-wc-menu-item');\n\n // Clear selected state when input is empty\n if (searchText === '') {\n menuItems?.forEach((item) => {\n item.removeAttribute('selected');\n });\n }\n\n let hiddenCount = 0;\n Array.from(menuItems ?? []).forEach((menuItem) => {\n const label = menuItem.getAttribute('label')?.toLowerCase() || '';\n if (!label.includes(searchText)) {\n menuItem.classList.add('hidden');\n hiddenCount++;\n } else {\n menuItem.classList.remove('hidden');\n }\n });\n\n // Show no results if all items are hidden\n autocomplete.noResults =\n hiddenCount === menuItems?.length\n ? originalNoResults\n : { ariaLabel: '', label: '', subLabel: '' };\n\n // Show/hide the no results element\n const noResultsElement = autocomplete.querySelector(\n '.no-results-item'\n ) as HTMLElement;\n if (noResultsElement) {\n if (hiddenCount === menuItems?.length) {\n noResultsElement.classList.add('visible');\n } else {\n noResultsElement.classList.remove('visible');\n }\n }\n }\n };\n\n const handleItemSelect = (e: CustomEvent<{ value: string }>) => {\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n ) as HTMLElement & { value: string; closeMenu: () => Promise<void> };\n\n if (autocomplete) {\n const selectedValue = e.detail.value;\n autocomplete.value = selectedValue;\n // Update selected state on menu items\n const menuItems = autocomplete.querySelectorAll('modus-wc-menu-item');\n menuItems.forEach((item) => {\n if (item.getAttribute('value') === selectedValue) {\n item.setAttribute('selected', 'true');\n } else {\n item.removeAttribute('selected');\n }\n });\n // Close menu after selection unless leaveMenuOpen is true\n if (!args['leave-menu-open']) {\n void autocomplete.closeMenu();\n }\n }\n };\n // prettier-ignore\n return html`\n<style>\ndiv[id^='story--components-forms-autocomplete--custom-menu-items'] {\n height: 400px;\n}\n.modus-wc-autocomplete {\n width: 480px !important;\n }\n.custom-menu-content {\n display: flex;\n align-items: center;\n gap: 12px;\n width: 100%;\n}\n.content-wrapper {\n flex: 1;\n}\n.title {\n font-weight: 500;\n}\n.subtitle {\n font-size: 0.875rem;\n color: #666;\n}\nmodus-wc-menu-item.hidden {\n display: none;\n}\n.no-results-item {\n display: none;\n padding: 16px;\n text-align: center;\n}\n.no-results-item.visible {\n display: block;\n}\n.no-results-header {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n margin-bottom: 8px;\n}\n.no-results-title {\n font-weight: bold;\n}\n.no-results-header modus-wc-icon {\n color: var(--modus-wc-color-gray-6);\n}\n\n</style>\n<modus-wc-autocomplete\n aria-label=\"Custom menu items example\"\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n id=\"custom-autocomplete\"\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${args['min-chars']}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=${ifDefined(args.placeholder)}\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n size=${ifDefined(args.size)}\n ?show-spinner=${args['show-spinner']}\n value=${args.value}\n .customKeyDown=${handleCustomKeyDown}\n @inputChange=${handleInputChange}\n ?include-search=${true}\n>\n <div slot=\"menu-items\">\n <modus-wc-menu-item\n label=\"John Doe\"\n sub-label=\"john.doe@example.com\"\n value=\"John Doe\"\n @itemSelect=${handleItemSelect}\n >\n <div slot=\"start-icon\">\n <modus-wc-avatar aria-label=\"Avatar\" size=\"xs\" img-src=\"https://i.pinimg.com/474x/73/54/79/7354794bf3873c3ef2666f778da4bcac.jpg\" shape=\"circle\" size=\"xs\"></modus-wc-avatar>\n </div>\n </modus-wc-menu-item>\n <modus-wc-menu-item\n label=\"Jane Smith\"\n sub-label=\"jane.smith@example.com\"\n value=\"Jane Smith\"\n @itemSelect=${handleItemSelect}\n >\n <div slot=\"start-icon\">\n <modus-wc-avatar aria-label=\"Avatar\" size=\"xs\" img-src=\"https://i.pinimg.com/474x/73/54/79/7354794bf3873c3ef2666f778da4bcac.jpg\" shape=\"circle\" size=\"xs\"></modus-wc-avatar>\n </div>\n </modus-wc-menu-item>\n <modus-wc-menu-item\n label=\"Bob Johnson\"\n sub-label=\"bob.johnson@example.com\"\n value=\"Bob Johnson\"\n @itemSelect=${handleItemSelect}\n >\n <div slot=\"start-icon\">\n <modus-wc-avatar aria-label=\"Avatar\" size=\"xs\" img-src=\"https://i.pinimg.com/474x/73/54/79/7354794bf3873c3ef2666f778da4bcac.jpg\" shape=\"circle\" size=\"xs\"></modus-wc-avatar>\n </div>\n </modus-wc-menu-item>\n <modus-wc-menu-item\n label=\"Alice Williams\"\n sub-label=\"alice.williams@example.com\"\n value=\"Alice Williams\"\n @itemSelect=${handleItemSelect}\n >\n <div slot=\"start-icon\">\n <modus-wc-avatar aria-label=\"Avatar\" size=\"xs\" alt=\"Example avatar\" img-src=\"https://i.pinimg.com/474x/73/54/79/7354794bf3873c3ef2666f778da4bcac.jpg\" shape=\"circle\" size=\"md\"></modus-wc-avatar>\n </div>\n </modus-wc-menu-item>\n <li class=\"no-results-item\">\n <div class=\"no-results-header\">\n <modus-wc-icon name=\"search\" size=\"lg\"></modus-wc-icon>\n <div class=\"no-results-title\">No results found</div>\n </div>\n </li>\n </div>\n</modus-wc-autocomplete>\n<script>\n // // Get the autocomplete element\n // const autocomplete = document.getElementById('custom-autocomplete');\n\n // const getVisibleItems = (autocompleteElement) => {\n // const menuItems = autocompleteElement.querySelectorAll(\n // 'modus-wc-menu-item:not([disabled])'\n // );\n // return Array.from(menuItems).filter((item) => {\n // const style = window.getComputedStyle(item);\n // return (\n // style.display !== 'none' && !item.classList.contains('hidden')\n // );\n // });\n // };\n\n // const handleCustomKeyDown = (e) => {\n // const autocompleteElement = e.target.closest('modus-wc-autocomplete');\n // if (!autocompleteElement) return;\n\n // // Don't process keyboard events when disabled or readOnly\n // if (autocompleteElement.disabled || autocompleteElement.readOnly)\n // return;\n\n // const visibleItems = getVisibleItems(autocompleteElement);\n\n // // Get all button elements within visible menu items\n // const buttons = visibleItems\n // .map((item) => item.querySelector('button'))\n // .filter(Boolean);\n // const currentFocusedButton = document.activeElement;\n // const currentIndex = buttons.indexOf(currentFocusedButton);\n\n // switch (e.key) {\n // case 'ArrowDown': {\n // e.preventDefault();\n // // Open menu when arrow key is pressed\n // autocompleteElement.openMenu();\n\n // let nextIndex = currentIndex + 1;\n // // Stop at the last item instead of wrapping\n // if (nextIndex >= buttons.length) return;\n // if (nextIndex < 0) nextIndex = 0;\n\n // buttons[nextIndex]?.focus();\n // break;\n // }\n\n // case 'ArrowUp': {\n // e.preventDefault();\n // // Open menu when arrow key is pressed\n // autocompleteElement.openMenu();\n\n // let prevIndex = currentIndex - 1;\n // // Stop at the first item instead of wrapping\n // if (prevIndex < 0) return;\n\n // buttons[prevIndex]?.focus();\n // break;\n // }\n\n // case 'Enter': {\n // e.preventDefault();\n // // If a button is focused, click it\n // if (buttons.includes(currentFocusedButton)) {\n // currentFocusedButton.click();\n // }\n // break;\n // }\n\n // case 'Escape': {\n // e.preventDefault();\n // autocompleteElement.closeMenu();\n // // Return focus to input\n // const input = autocompleteElement.querySelector('input');\n // input?.focus();\n // break;\n // }\n // }\n // };\n\n // const handleInputChange = (e) => {\n // if (!e.detail?.target) return;\n\n // const autocompleteElement = e.target.closest('modus-wc-autocomplete');\n\n // if (autocompleteElement) {\n // const searchText = e.detail.target.value.toLowerCase();\n // const menuItems =\n // autocompleteElement.querySelectorAll('modus-wc-menu-item');\n\n // // Clear selected state when input is empty\n // if (searchText === '') {\n // menuItems?.forEach((item) => {\n // item.removeAttribute('selected');\n // });\n // }\n\n // let hiddenCount = 0;\n // Array.from(menuItems ?? []).forEach((menuItem) => {\n // const label = menuItem.getAttribute('label')?.toLowerCase() || '';\n // if (!label.includes(searchText)) {\n // menuItem.classList.add('hidden');\n // hiddenCount++;\n // } else {\n // menuItem.classList.remove('hidden');\n // }\n // });\n\n // // Show/hide the no results element\n // const noResultsElement =\n // autocompleteElement.querySelector('.no-results-item');\n // if (noResultsElement) {\n // if (hiddenCount === menuItems?.length) {\n // noResultsElement.classList.add('visible');\n // } else {\n // noResultsElement.classList.remove('visible');\n // }\n // }\n // }\n // };\n\n // const handleItemSelect = (e) => {\n // const autocompleteElement = e.target.closest('modus-wc-autocomplete');\n\n // if (autocompleteElement) {\n // const selectedValue = e.detail.value;\n // autocompleteElement.value = selectedValue;\n // // Update selected state on menu items\n // const menuItems =\n // autocompleteElement.querySelectorAll('modus-wc-menu-item');\n // menuItems.forEach((item) => {\n // if (item.getAttribute('value') === selectedValue) {\n // item.setAttribute('selected', 'true');\n // } else {\n // item.removeAttribute('selected');\n // }\n // });\n // // Close menu after selection\n // autocompleteElement.closeMenu();\n // }\n // };\n\n // // Attach event listeners to the autocomplete component\n // if (autocomplete) {\n // autocomplete.addEventListener('keydown', handleCustomKeyDown);\n // autocomplete.addEventListener('inputChange', handleInputChange);\n // autocomplete.addEventListener('itemSelect', handleItemSelect);\n // }\n </script>\n `;\n },\n};\n\nexport const CustomEventHandlers: Story = {\n render: (args) => {\n interface AutocompleteElement extends HTMLElement {\n items: IAutocompleteItem[];\n value: string;\n openMenu(): Promise<void>;\n closeMenu(): Promise<void>;\n }\n\n // Custom keydown handler with skip navigation and escape animation\n const customKeyDown = (e: KeyboardEvent) => {\n const autocomplete = document.getElementById(\n 'autocomplete-custom-event-handlers'\n ) as AutocompleteElement;\n if (!autocomplete) return;\n\n // Prevent default for navigation keys\n if (['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(e.key)) {\n e.preventDefault();\n }\n\n const visibleItems = args.items.filter(\n (item) => item.visibleInMenu && !item.disabled\n );\n\n switch (e.key) {\n case 'Escape':\n args.items = args.items.map((item) => ({\n ...item,\n focused: false,\n }));\n autocomplete.items = [...args.items];\n void autocomplete.closeMenu();\n // Custom: Show escape animation\n autocomplete.style.transform = 'scale(0.98)';\n setTimeout(() => {\n autocomplete.style.transform = '';\n }, 200);\n break;\n\n case 'ArrowDown': {\n // Open menu if not already open\n void autocomplete.openMenu();\n\n const currentIndex = visibleItems.findIndex((item) => item.focused);\n const nextIndex =\n currentIndex < 0\n ? 0\n : Math.min(currentIndex + 1, visibleItems.length - 1);\n\n // Custom: Skip every other item for faster navigation\n const skipIndex =\n nextIndex + 1 < visibleItems.length ? nextIndex + 1 : nextIndex;\n\n args.items = args.items.map((item) => ({\n ...item,\n focused: visibleItems[skipIndex]?.value === item.value,\n }));\n break;\n }\n\n case 'ArrowUp': {\n const currentIndex = visibleItems.findIndex((item) => item.focused);\n const prevIndex =\n currentIndex < 0\n ? visibleItems.length - 1\n : Math.max(currentIndex - 1, 0);\n\n // Custom: Skip every other item for faster navigation\n const skipIndex = prevIndex - 1 >= 0 ? prevIndex - 1 : prevIndex;\n\n args.items = args.items.map((item) => ({\n ...item,\n focused: visibleItems[skipIndex]?.value === item.value,\n }));\n break;\n }\n\n case 'Enter': {\n const focusedItem = visibleItems.find((item) => item.focused);\n if (focusedItem) {\n // For single select, clear previous selection\n args.items = args.items.map((item) => ({\n ...item,\n selected: item.value === focusedItem.value,\n focused: false,\n }));\n autocomplete.value = focusedItem.label;\n void autocomplete.closeMenu();\n }\n break;\n }\n\n default:\n return;\n }\n\n autocomplete.items = [...args.items];\n };\n\n // Custom input change handler with fuzzy character matching\n const customInputChange = (value: string) => {\n const autocomplete = document.getElementById(\n 'autocomplete-custom-event-handlers'\n ) as AutocompleteElement;\n if (!autocomplete) return;\n\n const searchChars = value.toLowerCase().split('');\n\n // Custom fuzzy search: Match items that contain ALL typed characters (in any order)\n if (value.length > 0) {\n // Calculate match score for each item\n const scoredItems = args.items.map((item) => {\n const itemLower = item.label.toLowerCase();\n let score = 0;\n let allCharsFound = true;\n\n // Check if all search characters exist in the item\n for (const char of searchChars) {\n if (itemLower.includes(char)) {\n // Bonus points for consecutive characters\n const charIndex = itemLower.indexOf(char);\n if (charIndex === 0)\n score += 3; // Start of word bonus\n else if (itemLower[charIndex - 1] === ' ')\n score += 2; // Start of any word\n else score += 1;\n } else {\n allCharsFound = false;\n break;\n }\n }\n\n // Additional bonus for exact substring match\n if (allCharsFound && itemLower.includes(value.toLowerCase())) {\n score += 10;\n }\n\n return {\n item,\n score: allCharsFound ? score : -1,\n visible: allCharsFound,\n };\n });\n\n // Sort by score (highest first) and update items\n scoredItems.sort((a, b) => b.score - a.score);\n args.items = scoredItems.map(({ item, visible }) => ({\n ...item,\n visibleInMenu: visible,\n focused: false,\n selected: item.selected && visible,\n // Add score as part of label for demonstration (you can remove this in production)\n label: item.label,\n }));\n } else {\n // No search text, show all items\n args.items = args.items.map((item) => ({\n ...item,\n visibleInMenu: true,\n focused: false,\n }));\n }\n\n autocomplete.items = [...args.items];\n autocomplete.value = value;\n // Show match count in console for demonstration\n const matchCount = args.items.filter((item) => item.visibleInMenu).length;\n console.log(`Fuzzy search for \"${value}\": ${matchCount} matches found`);\n\n // Show menu if there are visible items\n const hasVisibleItems = args.items.some((item) => item.visibleInMenu);\n if (hasVisibleItems && value.length >= args['min-chars']) {\n void autocomplete.openMenu();\n } else {\n void autocomplete.closeMenu();\n }\n };\n\n // Custom item select handler\n const customItemSelect = (item: IAutocompleteItem) => {\n const autocomplete = document.getElementById(\n 'autocomplete-custom-event-handlers'\n ) as AutocompleteElement;\n if (!autocomplete) return;\n\n // Clear previous selections for single select\n args.items = args.items.map((menuItem) => ({\n ...menuItem,\n selected: menuItem.value === item.value,\n focused: false,\n }));\n\n autocomplete.items = [...args.items];\n autocomplete.value = item.label;\n void autocomplete.closeMenu();\n };\n // prettier-ignore\n return html`\n <style>\n div[id^='story--components-forms-autocomplete--custom-event-handlers'] {\n height: 400px;\n }\n\n .modus-wc-autocomplete.modus-wc-autocomplete {\n width: 300px;\n }\n\n .fuzzy-info {\n margin-top: 1rem;\n padding: 1rem;\n background-color: var(--modus-wc-color-info-light);\n border-radius: 4px;\n font-size: 0.875rem;\n }\n </style>\n\n <modus-wc-autocomplete\n aria-label=\"Custom handlers autocomplete\"\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${0}\n ?disabled=${args.disabled}\n id=\"autocomplete-custom-event-handlers\"\n ?include-clear=${args['include-clear']}\n ?include-search=${args['include-search']}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=\"Fruit list with custom handlers\"\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${args['min-chars']}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=\"Type 'bry' for Blueberry or Raspberry\"\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n ?show-spinner=${args['show-spinner']}\n size=${ifDefined(args.size)}\n value=${args.value}\n .customKeyDown=${customKeyDown}\n .customInputChange=${customInputChange}\n .customItemSelect=${customItemSelect}\n ></modus-wc-autocomplete>\n <script>\n //Commenting out the scripts to avoid duplicate declaration in storybook code\n // Add Autocomplete items\n ${Items}\n // const autocomplete = document.getElementById('autocomplete-custom-event-handlers');\n // autocomplete.items = autocompleteItems;\n\n // // Custom keydown handler with skip navigation and escape animation\n // const customKeyDown = (e) => {\n // const autocomplete = document.getElementById(\n // 'autocomplete-custom-event-handlers'\n // );\n // if (!autocomplete) return;\n\n // // Prevent default for navigation keys\n // if (['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(e.key)) {\n // e.preventDefault();\n // }\n\n // const visibleItems = autocomplete.items.filter(\n // (item) => item.visibleInMenu && !item.disabled\n // );\n\n // switch (e.key) {\n // case 'Escape':\n // autocomplete.items = autocomplete.items.map((item) => ({\n // ...item,\n // focused: false,\n // }));\n // void autocomplete.closeMenu();\n // // Custom: Show escape animation\n // autocomplete.style.transform = 'scale(0.98)';\n // setTimeout(() => {\n // autocomplete.style.transform = '';\n // }, 200);\n // break;\n\n // case 'ArrowDown': {\n // // Open menu if not already open\n // void autocomplete.openMenu();\n\n // const currentIndex = visibleItems.findIndex(\n // (item) => item.focused\n // );\n // const nextIndex =\n // currentIndex < 0\n // ? 0\n // : Math.min(currentIndex + 1, visibleItems.length - 1);\n\n // // Custom: Skip every other item for faster navigation\n // const skipIndex =\n // nextIndex + 1 < visibleItems.length ? nextIndex + 1 : nextIndex;\n\n // autocomplete.items = autocomplete.items.map((item) => ({\n // ...item,\n // focused: visibleItems[skipIndex]?.value === item.value,\n // }));\n // break;\n // }\n\n // case 'ArrowUp': {\n // const currentIndex = visibleItems.findIndex(\n // (item) => item.focused\n // );\n // const prevIndex =\n // currentIndex < 0\n // ? visibleItems.length - 1\n // : Math.max(currentIndex - 1, 0);\n\n // // Custom: Skip every other item for faster navigation\n // const skipIndex = prevIndex - 1 >= 0 ? prevIndex - 1 : prevIndex;\n\n // autocomplete.items = autocomplete.items.map((item) => ({\n // ...item,\n // focused: visibleItems[skipIndex]?.value === item.value,\n // }));\n // break;\n // }\n\n // case 'Enter': {\n // const focusedItem = visibleItems.find((item) => item.focused);\n // if (focusedItem) {\n // // For single select, clear previous selection\n // autocomplete.items = autocomplete.items.map((item) => ({\n // ...item,\n // selected: item.value === focusedItem.value,\n // focused: false,\n // }));\n // autocomplete.value = focusedItem.label;\n // void autocomplete.closeMenu();\n // }\n // break;\n // }\n\n // default:\n // return;\n // }\n // };\n\n // // Custom input change handler with fuzzy character matching\n // const customInputChange = (value) => {\n // const autocomplete = document.getElementById(\n // 'autocomplete-custom-event-handlers'\n // );\n // if (!autocomplete) return;\n\n // const searchChars = value.toLowerCase().split('');\n\n // // Custom fuzzy search: Match items that contain ALL typed characters (in any order)\n // if (value.length > 0) {\n // // Calculate match score for each item\n // const scoredItems = autocomplete.items.map((item) => {\n // const itemLower = item.label.toLowerCase();\n // let score = 0;\n // let allCharsFound = true;\n\n // // Check if all search characters exist in the item\n // for (const char of searchChars) {\n // if (itemLower.includes(char)) {\n // // Bonus points for consecutive characters\n // const charIndex = itemLower.indexOf(char);\n // if (charIndex === 0)\n // score += 3; // Start of word bonus\n // else if (itemLower[charIndex - 1] === ' ')\n // score += 2; // Start of any word\n // else score += 1;\n // } else {\n // allCharsFound = false;\n // break;\n // }\n // }\n\n // // Additional bonus for exact substring match\n // if (allCharsFound && itemLower.includes(value.toLowerCase())) {\n // score += 10;\n // }\n\n // return {\n // item,\n // score: allCharsFound ? score : -1,\n // visible: allCharsFound,\n // };\n // });\n\n // // Sort by score (highest first) and update items\n // scoredItems.sort((a, b) => b.score - a.score);\n // autocomplete.items = scoredItems.map(({ item, visible }) => ({\n // ...item,\n // visibleInMenu: visible,\n // focused: false,\n // selected: item.selected && visible,\n // // Add score as part of label for demonstration (you can remove this in production)\n // label: item.label,\n // }));\n // } else {\n // // No search text, show all items\n // autocomplete.items = autocomplete.items.map((item) => ({\n // ...item,\n // visibleInMenu: true,\n // focused: false,\n // }));\n // }\n\n // autocomplete.value = value;\n\n // // Show menu if there are visible items\n // const hasVisibleItems = autocomplete.items.some(\n // (item) => item.visibleInMenu\n // );\n // if (hasVisibleItems && value.length >= 0) {\n // void autocomplete.openMenu();\n // } else {\n // void autocomplete.closeMenu();\n // }\n // };\n\n // // Custom item select handler\n // const customItemSelect = (item) => {\n // const autocomplete = document.getElementById(\n // 'autocomplete-custom-event-handlers'\n // );\n // if (!autocomplete) return;\n\n // // Clear previous selections for single select\n // autocomplete.items = autocomplete.items.map((menuItem) => ({\n // ...menuItem,\n // selected: menuItem.value === item.value,\n // focused: false,\n // }));\n\n // autocomplete.value = item.label;\n // void autocomplete.closeMenu();\n // };\n\n // Attach the custom handlers to the autocomplete component\n // autocomplete.customKeyDown = customKeyDown;\n // autocomplete.customInputChange = customInputChange;\n // autocomplete.customItemSelect = customItemSelect;\n </script>\n `;\n },\n args: {\n bordered: true,\n 'debounce-ms': 0, // Set to 0 to see immediate feedback\n disabled: false,\n 'include-clear': true,\n 'include-search': true,\n items: items,\n 'leave-menu-open': false,\n 'min-chars': 0,\n 'no-results': {\n label: 'No fruits found',\n subLabel: 'Try different characters',\n },\n placeholder: 'Search fruits...',\n 'input-id': 'custom-handlers-input',\n 'read-only': false,\n required: false,\n 'show-menu-on-focus': true,\n 'show-spinner': false,\n size: 'md',\n value: '',\n },\n parameters: {\n docs: {\n description: {\n story: `This example demonstrates custom event handlers with three specific behaviors:\n\n1. **Skip Navigation**: Arrow keys skip every other item for 2x faster navigation\n2. **Escape Animation**: Pressing Escape triggers a subtle scale animation\n3. **Fuzzy Character Search**: Instead of normal substring matching, this searches for items containing ALL typed characters in any order\n\nThe fuzzy search allows finding items with scattered characters:\n- Type \"pae\" to find Pine**a**ppl**e**\n- Type \"bry\" to find Blue**b**er**ry**, Straw**b**er**ry**, Rasp**b**er**ry**\n\nItems are automatically sorted by relevance with exact substring matches appearing first.`,\n },\n },\n },\n};\n\nexport const WithProgrammaticControl: Story = {\n args: {\n ...meta.args,\n items: items, // Explicitly set items for this story\n },\n render: (args) => {\n // Type for autocomplete element with methods\n interface AutocompleteElement extends HTMLElement {\n selectItem(item: IAutocompleteItem | null): Promise<void>;\n openMenu(): Promise<void>;\n closeMenu(): Promise<void>;\n toggleMenu(): Promise<void>;\n focusInput(): Promise<void>;\n clearInput(): Promise<void>;\n }\n\n // Handler functions that will be attached to buttons\n const handleSelectApple = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n const appleItem = items.find((item) => item.value === 'apple') || null;\n await autocomplete.selectItem(appleItem);\n }\n };\n\n const handleSelectNull = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.selectItem(null);\n }\n };\n\n const handleOpenMenu = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.openMenu();\n }\n };\n\n const handleCloseMenu = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.closeMenu();\n }\n };\n\n const handleToggleMenu = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.toggleMenu();\n }\n };\n\n const handleFocusInput = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.focusInput();\n }\n };\n\n const handleClearInput = async () => {\n const autocomplete = document.getElementById(\n 'programmatic-autocomplete'\n ) as AutocompleteElement;\n if (autocomplete) {\n await autocomplete.clearInput();\n }\n };\n\n // Attach handlers to window for inline onclick\n interface WindowWithHandlers extends Window {\n handleSelectApple?: () => Promise<void>;\n handleSelectNull?: () => Promise<void>;\n handleOpenMenu?: () => Promise<void>;\n handleCloseMenu?: () => Promise<void>;\n handleToggleMenu?: () => Promise<void>;\n handleFocusInput?: () => Promise<void>;\n handleClearInput?: () => Promise<void>;\n }\n\n const windowWithHandlers = window as WindowWithHandlers;\n windowWithHandlers.handleSelectApple = handleSelectApple;\n windowWithHandlers.handleSelectNull = handleSelectNull;\n windowWithHandlers.handleOpenMenu = handleOpenMenu;\n windowWithHandlers.handleCloseMenu = handleCloseMenu;\n windowWithHandlers.handleToggleMenu = handleToggleMenu;\n windowWithHandlers.handleFocusInput = handleFocusInput;\n windowWithHandlers.handleClearInput = handleClearInput;\n\n // prettier-ignore\n return html`\n <style>\n div[id^='story--components-forms-autocomplete--with-programmatic-control'] {\n height: 500px;\n }\n\n .controls-content {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n }\n .control-group {\n margin-bottom: 1rem;\n }\n .control-group label {\n display: block;\n margin-bottom: 0.5rem;\n font-weight: 600;\n }\n .button-row {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n }\n </style>\n\n <modus-wc-card class=\"controls-card\">\n <div class=\"controls-content\">\n <h3>Programmatic Control Methods</h3>\n\n <div class=\"control-group\">\n <label>Selection Methods:</label>\n <div class=\"button-row\">\n <modus-wc-button\n onclick=\"window.handleSelectApple()\"\n size=\"sm\"\n >\n Select Apple\n </modus-wc-button>\n <modus-wc-button\n onclick=\"window.handleSelectNull()\"\n size=\"sm\"\n >\n Clear Selection\n </modus-wc-button>\n </div>\n </div>\n\n <div class=\"control-group\">\n <label>Menu Control Methods:</label>\n <div class=\"button-row\">\n <modus-wc-button\n onclick=\"window.handleOpenMenu()\"\n size=\"sm\"\n >\n Open Menu\n </modus-wc-button>\n <modus-wc-button\n onclick=\"window.handleCloseMenu()\"\n size=\"sm\"\n >\n Close Menu\n </modus-wc-button>\n <modus-wc-button\n onclick=\"window.handleToggleMenu()\"\n size=\"sm\"\n >\n Toggle Menu\n </modus-wc-button>\n </div>\n </div>\n\n <div class=\"control-group\">\n <label>Input Control Methods:</label>\n <div class=\"button-row\">\n <modus-wc-button\n onclick=\"window.handleFocusInput()\"\n size=\"sm\"\n >\n Focus Input\n </modus-wc-button>\n <modus-wc-button\n onclick=\"window.handleClearInput()\"\n size=\"sm\"\n >\n Clear All\n </modus-wc-button>\n </div>\n </div>\n </div>\n </modus-wc-card>\n <modus-wc-autocomplete\n id=\"programmatic-autocomplete\"\n aria-label=\"Programmatic control demo\"\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n ?include-clear=${args['include-clear']}\n ?include-search=${args['include-search']}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${args.items}\n label=\"Try the control buttons above\"\n ?leave-menu-open=${args['leave-menu-open']}\n max-chips=${args['max-chips'] ?? 4}\n min-chars=${args['min-chars']}\n min-input-width=${ifDefined(args['min-input-width'])}\n ?multi-select=${args['multi-select']}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=\"Use buttons above to control\"\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${args['show-menu-on-focus']}\n ?show-spinner=${args['show-spinner']}\n size=${ifDefined(args.size)}\n value=${args.value}\n ></modus-wc-autocomplete>\n <script type=\"module\">\n // //Commenting out the scripts to avoid duplicate declaration in storybook code\n // const handleSelectApple = async () => {\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // if (autocomplete) {\n // const appleItem =\n // autocompleteItems.find((item) => item.value === 'apple') || null;\n // await autocomplete.selectItem(appleItem);\n // }\n // };\n\n // const handleSelectNull = async () => {\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // if (autocomplete) {\n // await autocomplete.selectItem(null);\n // }\n // };\n\n // const handleOpenMenu = async () => {\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // if (autocomplete) {\n // await autocomplete.openMenu();\n // }\n // };\n\n // const handleCloseMenu = async () => {\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // if (autocomplete) {\n // await autocomplete.closeMenu();\n // }\n // };\n\n // const handleToggleMenu = async () => {\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // if (autocomplete) {\n // await autocomplete.toggleMenu();\n // }\n // };\n\n // const handleFocusInput = async () => {\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // if (autocomplete) {\n // await autocomplete.focusInput();\n // }\n // };\n\n // const handleClearInput = async () => {\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // if (autocomplete) {\n // await autocomplete.clearInput();\n // }\n // };\n // window.handleSelectApple = handleSelectApple;\n // window.handleSelectNull = handleSelectNull;\n // window.handleOpenMenu = handleOpenMenu;\n // window.handleCloseMenu = handleCloseMenu;\n // window.handleToggleMenu = handleToggleMenu;\n // window.handleFocusInput = handleFocusInput;\n // window.handleClearInput = handleClearInput;\n\n // // Add Autocomplete items\n ${Items}\n // // Adding this block to show how to set options using JS\n // const autocomplete = document.getElementById(\n // 'programmatic-autocomplete'\n // );\n // autocomplete.items = autocompleteItems;\n </script>\n `;\n },\n parameters: {\n docs: {\n description: {\n story: `\n## Public Methods\n\nThe autocomplete component exposes several methods that can be called programmatically:\n\n### selectItem(item: IAutocompleteItem | null): Promise<void>\nProgrammatically select an item. Pass \\`null\\` to clear selection.\n\n\\`\\`\\`javascript\nconst autocomplete = document.querySelector('modus-wc-autocomplete');\nconst item = { label: 'Apple', value: 'apple', visibleInMenu: true };\nawait autocomplete.selectItem(item);\n\\`\\`\\`\n\n### openMenu(): Promise<void>\nProgrammatically open the dropdown menu.\n\n\\`\\`\\`javascript\nawait autocomplete.openMenu();\n\\`\\`\\`\n\n### closeMenu(): Promise<void>\nProgrammatically close the dropdown menu.\n\n\\`\\`\\`javascript\nawait autocomplete.closeMenu();\n\\`\\`\\`\n\n### toggleMenu(): Promise<void>\nToggle the dropdown menu open/closed.\n\n\\`\\`\\`javascript\nawait autocomplete.toggleMenu();\n\\`\\`\\`\n\n### focusInput(): Promise<void>\nSet focus to the input element.\n\n\\`\\`\\`javascript\nawait autocomplete.focusInput();\n\\`\\`\\`\n\n### clearInput(): Promise<void>\nClear the input value and all selections.\n\n\\`\\`\\`javascript\nawait autocomplete.clearInput();\n\\`\\`\\`\n\n `,\n },\n },\n },\n};\n\nexport const DynamicOptions: Story = {\n render: (args) => {\n const defaultFruits = [\n { label: 'Apple', value: 'apple', visibleInMenu: true },\n { label: 'Banana', value: 'banana', visibleInMenu: true },\n { label: 'Orange', value: 'orange', visibleInMenu: true },\n { label: 'Strawberry', value: 'strawberry', visibleInMenu: true },\n ];\n\n // Extended dataset that will be searched when typing\n const allFruits = [\n ...defaultFruits,\n { label: 'Blackberry', value: 'blackberry', visibleInMenu: true },\n { label: 'Blueberry', value: 'blueberry', visibleInMenu: true },\n { label: 'Cherry', value: 'cherry', visibleInMenu: true },\n { label: 'Cranberry', value: 'cranberry', visibleInMenu: true },\n { label: 'Fig', value: 'fig', visibleInMenu: true },\n { label: 'Grape', value: 'grape', visibleInMenu: true },\n { label: 'Kiwi', value: 'kiwi', visibleInMenu: true },\n { label: 'Lemon', value: 'lemon', visibleInMenu: true },\n { label: 'Lime', value: 'lime', visibleInMenu: true },\n { label: 'Mango', value: 'mango', visibleInMenu: true },\n { label: 'Melon', value: 'melon', visibleInMenu: true },\n { label: 'Peach', value: 'peach', visibleInMenu: true },\n { label: 'Pineapple', value: 'pineapple', visibleInMenu: true },\n { label: 'Raspberry', value: 'raspberry', visibleInMenu: true },\n { label: 'Watermelon', value: 'watermelon', visibleInMenu: true },\n ];\n\n const handleInputChange = (e: CustomEvent<Event>) => {\n if (!e.detail?.target) return;\n\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n );\n\n if (autocomplete) {\n const input = e.detail.target as HTMLInputElement;\n const searchText = input.value.toLowerCase();\n\n if (searchText === '') {\n autocomplete.items = [...defaultFruits];\n autocomplete.value = input.value;\n return;\n }\n\n autocomplete.showSpinner = true;\n setTimeout(() => {\n const filteredFruits = allFruits.filter((fruit) =>\n fruit.label.toLowerCase().includes(searchText)\n );\n\n autocomplete.items = filteredFruits;\n autocomplete.showSpinner = false;\n }, 1000);\n\n autocomplete.value = input.value;\n }\n };\n\n const handleItemSelect = (e: CustomEvent<IAutocompleteItem>) => {\n const autocomplete = (e.target as HTMLInputElement).closest(\n 'modus-wc-autocomplete'\n );\n\n if (autocomplete) {\n const label = e.detail.label;\n if (label) {\n autocomplete.value = label;\n }\n }\n };\n\n return html`\n <style>\n div[id^='story--components-forms-autocomplete--dynamic-options'] {\n height: 400px;\n }\n </style>\n <modus-wc-autocomplete\n aria-label=\"Dynamic fruits autocomplete\"\n ?bordered=${args.bordered}\n custom-class=${ifDefined(args['custom-class'])}\n debounce-ms=${ifDefined(args['debounce-ms'])}\n ?disabled=${args.disabled}\n input-id=${ifDefined(args['input-id'])}\n input-tab-index=${ifDefined(args['input-tab-index'])}\n .items=${defaultFruits}\n label=${ifDefined(args.label)}\n ?leave-menu-open=${args['leave-menu-open']}\n min-chars=${0}\n ?multi-select=${false}\n name=${ifDefined(args.name)}\n .noResults=${args['no-results']}\n placeholder=\"Type to search fruits...\"\n ?read-only=${args['read-only']}\n ?required=${args.required}\n ?show-menu-on-focus=${true}\n size=${ifDefined(args.size)}\n value=${args.value}\n @inputChange=${handleInputChange}\n @itemSelect=${handleItemSelect}\n ></modus-wc-autocomplete>\n <script>\n // const defaultFruits = [\n // { label: 'Apple', value: 'apple', visibleInMenu: true },\n // { label: 'Banana', value: 'banana', visibleInMenu: true },\n // { label: 'Orange', value: 'orange', visibleInMenu: true },\n // { label: 'Strawberry', value: 'strawberry', visibleInMenu: true },\n // ];\n\n // // Extended dataset that will be searched when typing\n // const allFruits = [\n // ...defaultFruits,\n // { label: 'Blackberry', value: 'blackberry', visibleInMenu: true },\n // { label: 'Blueberry', value: 'blueberry', visibleInMenu: true },\n // { label: 'Cherry', value: 'cherry', visibleInMenu: true },\n // { label: 'Cranberry', value: 'cranberry', visibleInMenu: true },\n // { label: 'Fig', value: 'fig', visibleInMenu: true },\n // { label: 'Grape', value: 'grape', visibleInMenu: true },\n // { label: 'Kiwi', value: 'kiwi', visibleInMenu: true },\n // { label: 'Lemon', value: 'lemon', visibleInMenu: true },\n // { label: 'Lime', value: 'lime', visibleInMenu: true },\n // { label: 'Mango', value: 'mango', visibleInMenu: true },\n // { label: 'Melon', value: 'melon', visibleInMenu: true },\n // { label: 'Peach', value: 'peach', visibleInMenu: true },\n // { label: 'Pineapple', value: 'pineapple', visibleInMenu: true },\n // { label: 'Raspberry', value: 'raspberry', visibleInMenu: true },\n // { label: 'Watermelon', value: 'watermelon', visibleInMenu: true },\n // ];\n\n // const handleInputChange = (e) => {\n // if (!e.detail?.target) return;\n\n // const autocomplete = e.target.closest('modus-wc-autocomplete');\n\n // if (autocomplete) {\n // const input = e.detail.target;\n // const searchText = input.value.toLowerCase();\n\n // // If empty, show default fruits again\n // if (searchText === '') {\n // autocomplete.items = [...defaultFruits];\n // autocomplete.value = input.value;\n // return;\n // }\n\n // // Show spinner while \"loading\" data\n // autocomplete.showSpinner = true;\n\n // // Simulate API call delay with setTimeout\n // setTimeout(() => {\n // const filteredFruits = allFruits.filter((fruit) =>\n // fruit.label.toLowerCase().includes(searchText)\n // );\n\n // // Update the items with the \"API response\"\n // autocomplete.items = filteredFruits;\n\n // // Hide spinner after \"loading\" completes\n // autocomplete.showSpinner = false;\n // }, 1000);\n\n // autocomplete.value = input.value;\n // }\n // };\n\n // const handleItemSelect = (e) => {\n // const autocomplete = e.target.closest('modus-wc-autocomplete');\n\n // if (autocomplete) {\n // const label = e.detail.label;\n // if (label) {\n // autocomplete.value = label;\n // }\n // }\n // };\n\n // // Adding this block to show how to set Autocomplete items and attching event listeners via JS\n // const autocomplete = document.querySelector('modus-wc-autocomplete');\n\n // if (autocomplete) {\n // // Set initial items\n // autocomplete.items = [...defaultFruits];\n // // Attach event listeners\n // autocomplete.addEventListener('inputChange', handleInputChange);\n // autocomplete.addEventListener('itemSelect', handleItemSelect);\n // }\n </script>\n `;\n },\n};\n\nexport const ShadowDomParent: Story = {\n render: (args) => {\n // Create a unique shadow host for autocomplete component\n if (!customElements.get('autocomplete-shadow-host')) {\n const AutocompleteShadowHost = createShadowHostClass<AutocompleteArgs>({\n componentTag: 'modus-wc-autocomplete',\n propsMapper: (v: AutocompleteArgs, el: HTMLElement) => {\n const autocompleteEl = el as unknown as {\n bordered: boolean;\n customClass: string;\n debounceMs: number;\n disabled: boolean;\n includeClear: boolean;\n includeSearch: boolean;\n inputId: string;\n inputTabIndex: number;\n items: IAutocompleteItem[];\n label: string;\n leaveMenuOpen: boolean;\n maxChips: number;\n minChars: number;\n minInputWidth: number;\n multiSelect: boolean;\n name: string;\n noResults: IAutocompleteNoResults;\n placeholder: string;\n readOnly: boolean;\n required: boolean;\n showMenuOnFocus: boolean;\n showSpinner: boolean;\n size: string;\n value: string;\n };\n autocompleteEl.bordered = Boolean(v.bordered);\n autocompleteEl.customClass = v['custom-class'] || '';\n if (typeof v['debounce-ms'] === 'number') {\n autocompleteEl.debounceMs = v['debounce-ms'];\n }\n autocompleteEl.disabled = Boolean(v.disabled);\n autocompleteEl.includeClear = Boolean(v['include-clear']);\n autocompleteEl.includeSearch = Boolean(v['include-search']);\n if (typeof v['input-id'] === 'string') {\n autocompleteEl.inputId = v['input-id'];\n }\n if (typeof v['input-tab-index'] === 'number') {\n autocompleteEl.inputTabIndex = v['input-tab-index'];\n }\n autocompleteEl.items = v.items;\n if (typeof v.label === 'string') {\n autocompleteEl.label = v.label;\n }\n if (typeof v['leave-menu-open'] === 'boolean') {\n autocompleteEl.leaveMenuOpen = v['leave-menu-open'];\n }\n if (typeof v['max-chips'] === 'number') {\n autocompleteEl.maxChips = v['max-chips'];\n }\n if (typeof v['min-chars'] === 'number') {\n autocompleteEl.minChars = v['min-chars'];\n }\n if (typeof v['min-input-width'] === 'number') {\n autocompleteEl.minInputWidth = v['min-input-width'];\n }\n autocompleteEl.multiSelect = Boolean(v['multi-select']);\n if (typeof v.name === 'string') {\n autocompleteEl.name = v.name;\n }\n autocompleteEl.noResults = v['no-results'];\n if (typeof v.placeholder === 'string') {\n autocompleteEl.placeholder = v.placeholder;\n }\n autocompleteEl.readOnly = Boolean(v['read-only']);\n autocompleteEl.required = Boolean(v.required);\n if (typeof v['show-menu-on-focus'] === 'boolean') {\n autocompleteEl.showMenuOnFocus = v['show-menu-on-focus'];\n }\n autocompleteEl.showSpinner = Boolean(v['show-spinner']);\n if (typeof v.size === 'string') {\n autocompleteEl.size = v.size;\n }\n autocompleteEl.value = v.value;\n },\n });\n customElements.define('autocomplete-shadow-host', AutocompleteShadowHost);\n }\n\n return html`<autocomplete-shadow-host\n .props=${{ ...args }}\n ></autocomplete-shadow-host>`;\n },\n};\n\nexport const Migration: Story = {\n parameters: {\n docs: {\n description: {\n story: `\n#### Breaking Changes\n\n - In 1.0 input state was maintained by the component. 2.0 components encourage users to follow a controlled\n input model. See the Form Inputs [documentation]([Angular](?path=/docs/documentation-form-inputs--docs) for\n additional info and examples.\n - To handle updating items in 2.0, simply create a new array of items and bind it to the \\`items\\` prop. The 1.0 prop\n \\`filter-options\\` is no longer necessary.\n - Size values have changed from verbose names (\\`small\\`, \\`medium\\`, \\`large\\`) to abbreviations (\\`sm\\`, \\`md\\`, \\`lg\\`).\n\n#### Prop Mapping\n\n| 1.0 Prop | 2.0 Prop | Notes |\n|-------------------------------|---------------------|-------------------------------------------------------------|\n| aria-label | aria-label | |\n| clearable | include-clear | |\n| disabled | disabled | |\n| disable-close-on-select | leave-menu-open | |\n| dropdown-max-height | | Not carried over, use CSS instead |\n| dropdown-z-index | | Not carried over, use CSS instead |\n| error-text | feedback | feedback.level = 'error', feedback.message = 'Error message'|\n| filter-options | | Rebind options |\n| include-search-icon | include-search | |\n| label | label | |\n| loading | show-spinner | |\n| multiple | multi-select | |\n| no-results-found-text | no-results.label | |\n| no-results-found-subtext | no-results.subLabel | |\n| options | items | |\n| placeholder | placeholder | |\n| read-only | read-only | |\n| required | required | |\n| show-no-results-found-message | | Not carried over, use \\`no-results\\` prop |\n| show-options-on-focus | show-menu-on-focus | |\n| size | size | \\`small\\` → \\`sm\\`, \\`medium\\` → \\`md\\`, \\`large\\` → \\`lg\\` |\n| value | value | |\n\n#### Event Mapping\n\n| 1.0 Event | 2.0 Event | Notes |\n|-------------|-------------|------------------|\n| optionSelected ||\n| selectionsChanged ||\n| valueChange | inputChange | |\n\n#### Interfaces\n\n##### 1.0\n\n\\`\\`\\`typescript\ninterface ModusAutocompleteOption {\n id: string;\n value: string;\n}\n\\`\\`\\`\n\n##### 2.0\n\n\\`\\`\\`typescript\ninterface IAutocompleteItem {\n label: string;\n selected?: boolean;\n value: string;\n visibleInMenu: boolean;\n}\n\\`\\`\\`\n `,\n },\n },\n // To hide the actual story rendering and only show docs:\n controls: { disable: true },\n canvas: { disable: true },\n },\n // Simple render function or leave it empty\n render: () => html`<div></div>`,\n};\n"
414
+ }
415
+ }