@rcarls/rc-search-bar 0.3.1 → 0.4.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.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # `@rcarls/rc-search-bar`
2
2
 
3
- Search field wrapper for a native `<input type="search">` with icon chrome, clear button, and debounced events.
3
+ Search field and view wrapper for a native `<input type="search">` with icon chrome, a clear
4
+ button, suggestions, and debounced events.
4
5
 
5
6
  Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-search-bar](https://richardcarls.github.io/rc-webcomponents/components/rc-search-bar).
6
7
 
@@ -43,8 +44,8 @@ import { RCSearchBar } from '@rcarls/rc-search-bar';
43
44
  ```
44
45
 
45
46
  The component's shadow CSS already strips the slotted input's native border,
46
- background, and outline so it blends into the wrapper chrome — no consumer reset
47
- needed.
47
+ background, and outline so it blends into the wrapper chrome without a consumer
48
+ reset.
48
49
 
49
50
  The native WebKit cancel button is suppressed automatically. Set
50
51
  `allow-native-clear` to restore it.
@@ -57,7 +58,10 @@ The native WebKit cancel button is suppressed automatically. Set
57
58
 
58
59
  | Property | Attribute | Type | Default | Description |
59
60
  | ------------------ | --------------------- | --------------------- | ---------------- | --------------------------------------------------------------------------------------- |
60
- | `value` | — | `string` | `''` | Current search value; reads from the native input, host writes are silent |
61
+ | `variant` | `variant` | `'bar' \| 'view'` | `'bar'` | Presentation mode: docked field or expandable search view |
62
+ | `open` | `open` | `boolean` | `false` | Controlled search view open state; host writes are silent |
63
+ | `defaultOpen` | `default-open` | `boolean` | `false` | Initial uncontrolled search view open state |
64
+ | `value` | None | `string` | `''` | Current search value; reads from the native input, host writes are silent |
61
65
  | `defaultValue` | `default-value` | `string \| undefined` | `undefined` | Initial uncontrolled value hint, applied once |
62
66
  | `debounce` | `debounce` | `number` | `200` | Debounce window in ms for `rc-search-bar-input`; `0` dispatches synchronously |
63
67
  | `clearLabel` | `clear-label` | `string` | `'Clear search'` | Accessible label for the clear button |
@@ -79,6 +83,19 @@ The native WebKit cancel button is suppressed automatically. Set
79
83
  | `--rc-search-bar-height` | `var(--rc-control-block-size, 2.5rem)` | Wrapper block size |
80
84
  | `--rc-search-bar-padding-inline` | `var(--rc-control-padding-inline, 0.75rem)` | Wrapper horizontal padding |
81
85
  | `--rc-search-bar-gap` | `var(--rc-control-gap, 0.25em)` | Gap between icon, input, and clear button |
86
+ | `--rc-search-bar-input-color` | inherit | Input text color |
87
+ | `--rc-search-bar-input-font-family` | inherit | Input font family |
88
+ | `--rc-search-bar-input-font-size` | inherit | Input font size |
89
+ | `--rc-search-bar-view-bg` | `var(--rc-search-bar-bg)` | Search view panel background |
90
+ | `--rc-search-bar-view-color` | `var(--rc-search-bar-color)` | Search view panel text color |
91
+ | `--rc-search-bar-view-radius` | `var(--rc-search-bar-radius)` | Search view panel radius |
92
+ | `--rc-search-bar-view-shadow` | `var(--rc-search-bar-shadow)` | Search view panel elevation |
93
+ | `--rc-search-bar-view-border` | `var(--rc-search-bar-border)` | Search view panel border |
94
+ | `--rc-search-bar-view-offset` | `0.25rem` | Gap between the search bar and the view panel below it |
95
+ | `--rc-search-bar-view-padding-block` | `0.5rem` | Search view suggestion list block padding |
96
+ | `--rc-search-bar-suggestion-min-block-size` | `2.75rem` | Datalist-derived suggestion row minimum block size |
97
+ | `--rc-search-bar-suggestion-padding-inline` | `1rem` | Datalist-derived suggestion inline padding |
98
+ | `--rc-search-bar-suggestion-hover-bg` | `ButtonFace` | Datalist-derived suggestion hover/focus background |
82
99
 
83
100
  ### CSS parts
84
101
 
@@ -86,7 +103,11 @@ The native WebKit cancel button is suppressed automatically. Set
86
103
  | --------- | ------------- | ------------------------------------ |
87
104
  | `root` | wrapper `div` | The field chrome |
88
105
  | `leading` | `span` | Wrapper around the leading icon slot |
106
+ | `trailing` | `span` | Wrapper around the trailing slot |
89
107
  | `clear` | `button` | The clear button |
108
+ | `view` | `div` | Search view panel |
109
+ | `suggestions` | `div` | Suggestions container |
110
+ | `suggestion` | `button` | Datalist-derived suggestion button |
90
111
 
91
112
  ### Slots
92
113
 
@@ -96,19 +117,33 @@ The native WebKit cancel button is suppressed automatically. Set
96
117
  | `leading` | Decorative leading icon; mark it `aria-hidden="true"` |
97
118
  | `clear-icon` | Optional glyph replacing the default ✕ in the clear button |
98
119
  | `trailing` | Optional content after the clear button (supplementary badges or actions) |
120
+ | `suggestions` | Rich search view suggestions; takes precedence over datalist suggestions |
99
121
 
100
122
  ### Events
101
123
 
102
124
  | Event | Detail | Description |
103
125
  | --------------------- | ------------------- | ---------------------------------------------------------------------------- |
104
- | `rc-search-bar-input` | `{ value: string }` | Debounced after typing; fired immediately (pending timer cancelled) on clear |
126
+ | `rc-search-bar-input` | `{ value: string }` | Debounced after typing; fired immediately (pending timer canceled) on clear |
105
127
  | `rc-search-bar-clear` | `{}` | Fired when the clear button is activated |
128
+ | `rc-search-bar-toggle` | `{ open: boolean }` | Fired when user interaction opens or closes the search view |
129
+ | `rc-search-bar-suggestion-select` | `{ value: string, label: string }` | Fired when a datalist-derived suggestion is activated |
130
+
131
+ ### Search view suggestions
132
+
133
+ Use `variant="view"` for the expandable search view. Rich Material-style
134
+ suggestions should be rendered in the `suggestions` slot. For simple text
135
+ suggestions, the component reads the native input's `list` attribute and renders
136
+ the associated `<datalist><option>` values when the slot is empty.
137
+
138
+ `<datalist>` remains a progressive-enhancement path with browser-dependent
139
+ styling and accessibility behavior; prefer slotted suggestions when rows need
140
+ icons, supporting text, actions, or stronger AT/styling control.
106
141
 
107
142
  ---
108
143
 
109
144
  ## Accessibility notes
110
145
 
111
- - The component writes no ARIA to the input — `type="search"` already exposes
146
+ - The component writes no ARIA to the input because `type="search"` already exposes
112
147
  the `searchbox` role, and author labels are never overwritten. Label the
113
148
  input with `label[for]`, a wrapping `<label>`, or `aria-label`.
114
149
  - The component adds no `role="search"` landmark; wrap in `<search>` or
@@ -110,6 +110,56 @@
110
110
  {
111
111
  "description": "Input text color (inherits when unset)",
112
112
  "name": "--rc-search-bar-input-color"
113
+ },
114
+ {
115
+ "description": "Search view panel background",
116
+ "name": "--rc-search-bar-view-bg",
117
+ "default": "var(--rc-search-bar-bg)"
118
+ },
119
+ {
120
+ "description": "Search view panel text color",
121
+ "name": "--rc-search-bar-view-color",
122
+ "default": "var(--rc-search-bar-color)"
123
+ },
124
+ {
125
+ "description": "Search view panel radius",
126
+ "name": "--rc-search-bar-view-radius",
127
+ "default": "var(--rc-search-bar-radius)"
128
+ },
129
+ {
130
+ "description": "Search view panel shadow",
131
+ "name": "--rc-search-bar-view-shadow",
132
+ "default": "var(--rc-search-bar-shadow)"
133
+ },
134
+ {
135
+ "description": "Search view panel border",
136
+ "name": "--rc-search-bar-view-border",
137
+ "default": "var(--rc-search-bar-border)"
138
+ },
139
+ {
140
+ "description": "Gap between the search bar and the view panel below it",
141
+ "name": "--rc-search-bar-view-offset",
142
+ "default": "0.25rem"
143
+ },
144
+ {
145
+ "description": "Search view suggestion list block padding",
146
+ "name": "--rc-search-bar-view-padding-block",
147
+ "default": "0.5rem"
148
+ },
149
+ {
150
+ "description": "Datalist-derived suggestion row minimum block size",
151
+ "name": "--rc-search-bar-suggestion-min-block-size",
152
+ "default": "2.75rem"
153
+ },
154
+ {
155
+ "description": "Datalist-derived suggestion inline padding",
156
+ "name": "--rc-search-bar-suggestion-padding-inline",
157
+ "default": "1rem"
158
+ },
159
+ {
160
+ "description": "Datalist-derived suggestion hover/focus background",
161
+ "name": "--rc-search-bar-suggestion-hover-bg",
162
+ "default": "ButtonFace"
113
163
  }
114
164
  ],
115
165
  "cssParts": [
@@ -128,6 +178,18 @@
128
178
  {
129
179
  "description": "The clear button",
130
180
  "name": "clear"
181
+ },
182
+ {
183
+ "description": "Search view panel",
184
+ "name": "view"
185
+ },
186
+ {
187
+ "description": "Suggestions container",
188
+ "name": "suggestions"
189
+ },
190
+ {
191
+ "description": "Datalist-derived suggestion button",
192
+ "name": "suggestion"
131
193
  }
132
194
  ],
133
195
  "slots": [
@@ -146,9 +208,24 @@
146
208
  {
147
209
  "description": "Optional glyph replacing the default clear glyph",
148
210
  "name": "clear-icon"
211
+ },
212
+ {
213
+ "description": "Rich search view suggestions; takes precedence over datalist-derived suggestions.",
214
+ "name": "suggestions"
149
215
  }
150
216
  ],
151
217
  "members": [
218
+ {
219
+ "kind": "field",
220
+ "name": "variant",
221
+ "type": {
222
+ "text": "RCSearchBarVariant"
223
+ },
224
+ "default": "'bar'",
225
+ "description": "Presentation mode: docked search bar or expandable search view.",
226
+ "attribute": "variant",
227
+ "reflects": true
228
+ },
152
229
  {
153
230
  "kind": "field",
154
231
  "name": "debounce",
@@ -209,6 +286,33 @@
209
286
  "description": "Placeholder mirrored onto the native input.\n\nConsumer `placeholder` attribute is always honored.",
210
287
  "attribute": "placeholder"
211
288
  },
289
+ {
290
+ "kind": "field",
291
+ "name": "_open",
292
+ "type": {
293
+ "text": "boolean"
294
+ },
295
+ "privacy": "private",
296
+ "default": "false"
297
+ },
298
+ {
299
+ "kind": "field",
300
+ "name": "_defaultOpen",
301
+ "type": {
302
+ "text": "boolean"
303
+ },
304
+ "privacy": "private",
305
+ "default": "false"
306
+ },
307
+ {
308
+ "kind": "field",
309
+ "name": "_openInitialized",
310
+ "type": {
311
+ "text": "boolean"
312
+ },
313
+ "privacy": "private",
314
+ "default": "false"
315
+ },
212
316
  {
213
317
  "kind": "field",
214
318
  "name": "_value",
@@ -273,6 +377,15 @@
273
377
  "privacy": "private",
274
378
  "default": "undefined"
275
379
  },
380
+ {
381
+ "kind": "field",
382
+ "name": "_$dataListRef",
383
+ "type": {
384
+ "text": "WeakRef<HTMLDataListElement> | null"
385
+ },
386
+ "privacy": "private",
387
+ "default": "null"
388
+ },
276
389
  {
277
390
  "kind": "field",
278
391
  "name": "_hasInput",
@@ -318,6 +431,24 @@
318
431
  "privacy": "private",
319
432
  "default": "false"
320
433
  },
434
+ {
435
+ "kind": "field",
436
+ "name": "_hasSuggestionsSlot",
437
+ "type": {
438
+ "text": "boolean"
439
+ },
440
+ "privacy": "private",
441
+ "default": "false"
442
+ },
443
+ {
444
+ "kind": "field",
445
+ "name": "_dataListSuggestions",
446
+ "type": {
447
+ "text": "RCSearchBarSuggestion[]"
448
+ },
449
+ "privacy": "private",
450
+ "default": "[]"
451
+ },
321
452
  {
322
453
  "kind": "field",
323
454
  "name": "_disabledHost",
@@ -353,14 +484,40 @@
353
484
  "name": "_disabledObserver",
354
485
  "privacy": "private",
355
486
  "readonly": true,
356
- "default": "new MutationObserver(() => { const $input = this._$input(); if ($input) { this._inputDisabled = $input.disabled; } })"
487
+ "default": "new MutationObserver(() => { const $input = this._$input(); if ($input) { this._inputDisabled = $input.disabled; this._syncDataListSuggestions($input); } })"
488
+ },
489
+ {
490
+ "kind": "field",
491
+ "name": "_dataListObserver",
492
+ "privacy": "private",
493
+ "readonly": true,
494
+ "default": "new MutationObserver(() => this._syncDataListSuggestions())"
357
495
  },
358
496
  {
359
497
  "kind": "field",
360
498
  "name": "_inputController",
361
499
  "privacy": "private",
362
500
  "readonly": true,
363
- "default": "new NativeChildController<HTMLInputElement>(this, { selector: ':scope > input[type=\"search\"]', observe: true, onChange: ($input, $previousInput) => this._setupInput($input, $previousInput), onMissing: () => { if (import.meta.env.DEV) { warnMissingDirectChild(this, { selector: ':scope > input[type=\"search\"]', message: '[rc-search-bar] No direct child <input type=\"search\"> found. ' + 'Place a native search input inside <rc-search-bar>.', }); } }, })"
501
+ "default": "new NativeChildController<HTMLInputElement>(this, { selector: ':scope > input[type=\"search\"]', observe: { childList: true, attributes: true, attributeFilter: ['list'] }, onChange: ($input, $previousInput) => this._setupInput($input, $previousInput), onMissing: () => { if (import.meta.env.DEV) { warnMissingDirectChild(this, { selector: ':scope > input[type=\"search\"]', message: '[rc-search-bar] No direct child <input type=\"search\"> found. ' + 'Place a native search input inside <rc-search-bar>.', }); } }, })"
502
+ },
503
+ {
504
+ "kind": "field",
505
+ "name": "open",
506
+ "type": {
507
+ "text": "boolean"
508
+ },
509
+ "description": "Whether the search view panel is open. Host writes are silent.",
510
+ "attribute": "open",
511
+ "reflects": true
512
+ },
513
+ {
514
+ "kind": "field",
515
+ "name": "defaultOpen",
516
+ "type": {
517
+ "text": "boolean"
518
+ },
519
+ "description": "Initial uncontrolled search view open state.",
520
+ "attribute": "default-open"
364
521
  },
365
522
  {
366
523
  "kind": "field",
@@ -465,6 +622,12 @@
465
622
  "privacy": "private",
466
623
  "readonly": true
467
624
  },
625
+ {
626
+ "kind": "field",
627
+ "name": "_onNativeKeyDown",
628
+ "privacy": "private",
629
+ "readonly": true
630
+ },
468
631
  {
469
632
  "kind": "method",
470
633
  "name": "_scheduleInputEvent",
@@ -511,6 +674,91 @@
511
674
  }
512
675
  ]
513
676
  },
677
+ {
678
+ "kind": "method",
679
+ "name": "_setUserOpen",
680
+ "privacy": "private",
681
+ "return": {
682
+ "type": {
683
+ "text": "void"
684
+ }
685
+ },
686
+ "parameters": [
687
+ {
688
+ "name": "open",
689
+ "type": {
690
+ "text": "boolean"
691
+ }
692
+ }
693
+ ]
694
+ },
695
+ {
696
+ "kind": "method",
697
+ "name": "showView",
698
+ "privacy": "public",
699
+ "return": {
700
+ "type": {
701
+ "text": "void"
702
+ }
703
+ },
704
+ "description": "Opens the search view and fires `rc-search-bar-toggle` when it changes."
705
+ },
706
+ {
707
+ "kind": "method",
708
+ "name": "closeView",
709
+ "privacy": "public",
710
+ "return": {
711
+ "type": {
712
+ "text": "void"
713
+ }
714
+ },
715
+ "description": "Closes the search view and fires `rc-search-bar-toggle` when it changes."
716
+ },
717
+ {
718
+ "kind": "method",
719
+ "name": "toggleView",
720
+ "privacy": "public",
721
+ "return": {
722
+ "type": {
723
+ "text": "void"
724
+ }
725
+ },
726
+ "description": "Toggles the search view and fires `rc-search-bar-toggle` when it changes."
727
+ },
728
+ {
729
+ "kind": "method",
730
+ "name": "_syncDataListSuggestions",
731
+ "privacy": "private",
732
+ "return": {
733
+ "type": {
734
+ "text": "void"
735
+ }
736
+ },
737
+ "parameters": [
738
+ {
739
+ "name": "$input",
740
+ "default": "this._$input()"
741
+ }
742
+ ]
743
+ },
744
+ {
745
+ "kind": "method",
746
+ "name": "_selectDataListSuggestion",
747
+ "privacy": "private",
748
+ "return": {
749
+ "type": {
750
+ "text": "void"
751
+ }
752
+ },
753
+ "parameters": [
754
+ {
755
+ "name": "suggestion",
756
+ "type": {
757
+ "text": "RCSearchBarSuggestion"
758
+ }
759
+ }
760
+ ]
761
+ },
514
762
  {
515
763
  "kind": "method",
516
764
  "name": "_handleClear",
@@ -532,6 +780,11 @@
532
780
  },
533
781
  "description": "Programmatically clears the value and fires rc-search-bar-clear + rc-search-bar-input."
534
782
  },
783
+ {
784
+ "kind": "method",
785
+ "name": "_renderSearchView",
786
+ "privacy": "private"
787
+ },
535
788
  {
536
789
  "kind": "method",
537
790
  "name": "_onLeadingSlotChange",
@@ -567,6 +820,24 @@
567
820
  }
568
821
  }
569
822
  ]
823
+ },
824
+ {
825
+ "kind": "method",
826
+ "name": "_onSuggestionsSlotChange",
827
+ "privacy": "private",
828
+ "return": {
829
+ "type": {
830
+ "text": "void"
831
+ }
832
+ },
833
+ "parameters": [
834
+ {
835
+ "name": "e",
836
+ "type": {
837
+ "text": "Event"
838
+ }
839
+ }
840
+ ]
570
841
  }
571
842
  ],
572
843
  "events": [
@@ -577,6 +848,20 @@
577
848
  },
578
849
  "description": "Debounced after typing, immediate on clear; `detail: { value }`"
579
850
  },
851
+ {
852
+ "name": "rc-search-bar-toggle",
853
+ "type": {
854
+ "text": "CustomEvent"
855
+ },
856
+ "description": "When user interaction opens or closes the search view; `detail: { open }`"
857
+ },
858
+ {
859
+ "name": "rc-search-bar-suggestion-select",
860
+ "type": {
861
+ "text": "CustomEvent"
862
+ },
863
+ "description": "When a datalist-derived suggestion is activated; `detail: { value, label }`"
864
+ },
580
865
  {
581
866
  "name": "rc-search-bar-clear",
582
867
  "type": {
@@ -587,63 +872,88 @@
587
872
  ],
588
873
  "attributes": [
589
874
  {
875
+ "description": "Presentation mode: docked search bar or expandable search view.",
876
+ "name": "variant",
877
+ "type": {
878
+ "text": "RCSearchBarVariant"
879
+ },
880
+ "default": "'bar'",
881
+ "fieldName": "variant"
882
+ },
883
+ {
884
+ "description": "Debounce window in ms for `rc-search-bar-input`; `0` dispatches synchronously.",
590
885
  "name": "debounce",
591
886
  "type": {
592
887
  "text": "number"
593
888
  },
594
889
  "default": "200",
595
- "description": "Debounce window in ms for `rc-search-bar-input`; `0` dispatches synchronously.",
596
890
  "fieldName": "debounce"
597
891
  },
598
892
  {
893
+ "description": "Accessible label for the clear button.",
599
894
  "name": "clear-label",
600
895
  "type": {
601
896
  "text": "string"
602
897
  },
603
898
  "default": "'Clear search'",
604
- "description": "Accessible label for the clear button.",
605
899
  "fieldName": "clearLabel"
606
900
  },
607
901
  {
902
+ "description": "When set, leaves the browser's native WebKit cancel button visible.",
608
903
  "name": "allow-native-clear",
609
904
  "type": {
610
905
  "text": "boolean"
611
906
  },
612
907
  "default": "false",
613
- "description": "When set, leaves the browser's native WebKit cancel button visible.",
614
908
  "fieldName": "allowNativeClear"
615
909
  },
616
910
  {
911
+ "description": "When set, the clear button is visible whenever the input is focused.\n\nMatching the Apple HIG \"cancel\" pattern.\nConsider setting `clear-label=\"Cancel\"` in this mode.",
617
912
  "name": "show-clear-on-focus",
618
913
  "type": {
619
914
  "text": "boolean"
620
915
  },
621
916
  "default": "false",
622
- "description": "When set, the clear button is visible whenever the input is focused.\n\nMatching the Apple HIG \"cancel\" pattern.\nConsider setting `clear-label=\"Cancel\"` in this mode.",
623
917
  "fieldName": "showClearOnFocus"
624
918
  },
625
919
  {
920
+ "description": "Disables the component and mirrors the state to the slotted input.",
626
921
  "name": "disabled",
627
922
  "type": {
628
923
  "text": "boolean"
629
924
  },
630
- "description": "Disables the component and mirrors the state to the slotted input.",
631
925
  "fieldName": "disabled"
632
926
  },
633
927
  {
928
+ "description": "Placeholder mirrored onto the native input.\n\nConsumer `placeholder` attribute is always honored.",
634
929
  "name": "placeholder",
635
930
  "type": {
636
931
  "text": "string | undefined"
637
932
  },
638
- "description": "Placeholder mirrored onto the native input.\n\nConsumer `placeholder` attribute is always honored.",
639
933
  "fieldName": "placeholder"
640
934
  },
641
935
  {
936
+ "description": "Whether the search view panel is open. Host writes are silent.",
937
+ "name": "open",
938
+ "type": {
939
+ "text": "boolean"
940
+ },
941
+ "fieldName": "open"
942
+ },
943
+ {
944
+ "description": "Initial uncontrolled search view open state.",
945
+ "name": "default-open",
946
+ "type": {
947
+ "text": "boolean"
948
+ },
949
+ "fieldName": "defaultOpen"
950
+ },
951
+ {
952
+ "description": "Initial uncontrolled value hint. Applied once, and only when neither an\nauthor `value`, a host `value` write, nor user input owns the value.",
642
953
  "name": "default-value",
643
954
  "type": {
644
955
  "text": "string | undefined"
645
956
  },
646
- "description": "Initial uncontrolled value hint. Applied once, and only when neither an\nauthor `value`, a host `value` write, nor user input owns the value.",
647
957
  "fieldName": "defaultValue"
648
958
  }
649
959
  ],