@papernote/ui 1.7.6 → 1.7.7

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.
@@ -8,6 +8,8 @@ export interface AutocompleteOption {
8
8
  label: string;
9
9
  description?: string;
10
10
  metadata?: Record<string, unknown>;
11
+ /** If true, renders as a non-selectable section header */
12
+ isHeader?: boolean;
11
13
  }
12
14
  export interface AutocompleteProps {
13
15
  value: string;
@@ -25,6 +27,8 @@ export interface AutocompleteProps {
25
27
  maxResults?: number;
26
28
  clearable?: boolean;
27
29
  className?: string;
30
+ /** Show static options dropdown on focus when input is empty. Default: true */
31
+ showOptionsOnFocus?: boolean;
28
32
  }
29
33
  declare const Autocomplete: React.ForwardRefExoticComponent<AutocompleteProps & React.RefAttributes<AutocompleteHandle>>;
30
34
  export default Autocomplete;
@@ -1 +1 @@
1
- {"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../src/components/Autocomplete.tsx"],"names":[],"mappings":"AACA,OAAO,KAA8E,MAAM,OAAO,CAAC;AAGnG,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/D,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,YAAY,8FAkThB,CAAC;AAGH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../src/components/Autocomplete.tsx"],"names":[],"mappings":"AACA,OAAO,KAA8E,MAAM,OAAO,CAAC;AAGnG,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/D,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,QAAA,MAAM,YAAY,8FA6WhB,CAAC;AAGH,eAAe,YAAY,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1966,6 +1966,8 @@ interface AutocompleteOption {
1966
1966
  label: string;
1967
1967
  description?: string;
1968
1968
  metadata?: Record<string, unknown>;
1969
+ /** If true, renders as a non-selectable section header */
1970
+ isHeader?: boolean;
1969
1971
  }
1970
1972
  interface AutocompleteProps {
1971
1973
  value: string;
@@ -1983,6 +1985,8 @@ interface AutocompleteProps {
1983
1985
  maxResults?: number;
1984
1986
  clearable?: boolean;
1985
1987
  className?: string;
1988
+ /** Show static options dropdown on focus when input is empty. Default: true */
1989
+ showOptionsOnFocus?: boolean;
1986
1990
  }
1987
1991
  declare const Autocomplete: React__default.ForwardRefExoticComponent<AutocompleteProps & React__default.RefAttributes<AutocompleteHandle>>;
1988
1992
 
package/dist/index.esm.js CHANGED
@@ -5007,7 +5007,7 @@ const MaskedInput = forwardRef(({ value, onChange, maskType = 'phone', customMas
5007
5007
  });
5008
5008
  MaskedInput.displayName = 'MaskedInput';
5009
5009
 
5010
- const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, label, placeholder = 'Search...', required = false, disabled = false, error, helperText, minChars = 1, debounceMs = 300, maxResults = 10, clearable = true, className = '', }, ref) => {
5010
+ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, label, placeholder = 'Search...', required = false, disabled = false, error, helperText, minChars = 1, debounceMs = 300, maxResults = 10, clearable = true, className = '', showOptionsOnFocus = true, }, ref) => {
5011
5011
  const [isOpen, setIsOpen] = useState(false);
5012
5012
  const [filteredOptions, setFilteredOptions] = useState([]);
5013
5013
  const [loading, setLoading] = useState(false);
@@ -5019,6 +5019,30 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
5019
5019
  const labelId = useId();
5020
5020
  const listboxId = useId();
5021
5021
  const errorId = useId();
5022
+ // Helper to find next selectable (non-header) index
5023
+ const findNextSelectableIndex = (currentIndex, optionsList) => {
5024
+ for (let i = currentIndex + 1; i < optionsList.length; i++) {
5025
+ if (!optionsList[i].isHeader)
5026
+ return i;
5027
+ }
5028
+ return currentIndex; // Stay at current if no next selectable
5029
+ };
5030
+ // Helper to find previous selectable (non-header) index
5031
+ const findPrevSelectableIndex = (currentIndex, optionsList) => {
5032
+ for (let i = currentIndex - 1; i >= 0; i--) {
5033
+ if (!optionsList[i].isHeader)
5034
+ return i;
5035
+ }
5036
+ return -1; // Go to -1 if no previous selectable
5037
+ };
5038
+ // Helper to find first selectable (non-header) index
5039
+ const findFirstSelectableIndex = (optionsList) => {
5040
+ for (let i = 0; i < optionsList.length; i++) {
5041
+ if (!optionsList[i].isHeader)
5042
+ return i;
5043
+ }
5044
+ return -1;
5045
+ };
5022
5046
  // Expose methods via ref
5023
5047
  useImperativeHandle(ref, () => ({
5024
5048
  focus: () => inputRef.current?.focus(),
@@ -5048,8 +5072,8 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
5048
5072
  const results = await onSearch(query);
5049
5073
  setFilteredOptions(results.slice(0, maxResults));
5050
5074
  setIsOpen(results.length > 0);
5051
- // Auto-highlight first result for keyboard navigation
5052
- setHighlightedIndex(results.length > 0 ? 0 : -1);
5075
+ // Auto-highlight first selectable (non-header) result
5076
+ setHighlightedIndex(findFirstSelectableIndex(results.slice(0, maxResults)));
5053
5077
  }
5054
5078
  catch (err) {
5055
5079
  console.error('Autocomplete search error:', err);
@@ -5066,8 +5090,16 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
5066
5090
  const filtered = filterOptions(query);
5067
5091
  setFilteredOptions(filtered);
5068
5092
  setIsOpen(filtered.length > 0);
5069
- // Auto-highlight first result for keyboard navigation
5070
- setHighlightedIndex(filtered.length > 0 ? 0 : -1);
5093
+ // Auto-highlight first selectable (non-header) result
5094
+ setHighlightedIndex(findFirstSelectableIndex(filtered));
5095
+ }
5096
+ };
5097
+ // Show static options (for focus/arrow down when input is empty)
5098
+ const showStaticOptions = () => {
5099
+ if (options.length > 0) {
5100
+ setFilteredOptions(options.slice(0, maxResults));
5101
+ setIsOpen(true);
5102
+ setHighlightedIndex(findFirstSelectableIndex(options.slice(0, maxResults)));
5071
5103
  }
5072
5104
  };
5073
5105
  // Debounced search
@@ -5108,7 +5140,11 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
5108
5140
  // If we have cached results from a previous search, show them
5109
5141
  if (filteredOptions.length > 0) {
5110
5142
  setIsOpen(true);
5111
- setHighlightedIndex(0);
5143
+ setHighlightedIndex(findFirstSelectableIndex(filteredOptions));
5144
+ }
5145
+ else if (value.length < minChars && options.length > 0) {
5146
+ // Show static options when input is empty/below minChars
5147
+ showStaticOptions();
5112
5148
  }
5113
5149
  else if (value.length >= minChars) {
5114
5150
  // Otherwise trigger a new search
@@ -5120,16 +5156,20 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
5120
5156
  switch (e.key) {
5121
5157
  case 'ArrowDown':
5122
5158
  e.preventDefault();
5123
- setHighlightedIndex((prev) => prev < filteredOptions.length - 1 ? prev + 1 : prev);
5159
+ setHighlightedIndex((prev) => findNextSelectableIndex(prev, filteredOptions));
5124
5160
  break;
5125
5161
  case 'ArrowUp':
5126
5162
  e.preventDefault();
5127
- setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : -1));
5163
+ setHighlightedIndex((prev) => findPrevSelectableIndex(prev, filteredOptions));
5128
5164
  break;
5129
5165
  case 'Enter':
5130
5166
  e.preventDefault();
5131
5167
  if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
5132
- handleSelect(filteredOptions[highlightedIndex]);
5168
+ const option = filteredOptions[highlightedIndex];
5169
+ // Don't select headers
5170
+ if (!option.isHeader) {
5171
+ handleSelect(option);
5172
+ }
5133
5173
  }
5134
5174
  break;
5135
5175
  case 'Escape':
@@ -5159,7 +5199,16 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
5159
5199
  }
5160
5200
  };
5161
5201
  }, []);
5162
- return (jsxs("div", { className: `relative ${className}`, children: [label && (jsxs("label", { id: labelId, className: "block text-sm font-medium text-ink-900 mb-1.5", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2", children: loading ? (jsx(Loader2, { className: "h-4 w-4 text-ink-400 animate-spin" })) : (jsx(Search, { className: "h-4 w-4 text-ink-400" })) }), jsx("input", { ref: inputRef, type: "text", value: value, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: () => value.length >= minChars && handleSearch(value), placeholder: placeholder, disabled: disabled, className: `
5202
+ return (jsxs("div", { className: `relative ${className}`, children: [label && (jsxs("label", { id: labelId, className: "block text-sm font-medium text-ink-900 mb-1.5", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2", children: loading ? (jsx(Loader2, { className: "h-4 w-4 text-ink-400 animate-spin" })) : (jsx(Search, { className: "h-4 w-4 text-ink-400" })) }), jsx("input", { ref: inputRef, type: "text", value: value, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: () => {
5203
+ if (showOptionsOnFocus && value.length < minChars && options.length > 0) {
5204
+ // Show static options when input is empty/below minChars
5205
+ showStaticOptions();
5206
+ }
5207
+ else if (value.length >= minChars) {
5208
+ // Trigger search if we have enough chars
5209
+ handleSearch(value);
5210
+ }
5211
+ }, placeholder: placeholder, disabled: disabled, className: `
5163
5212
  w-full pl-9 pr-9 py-2
5164
5213
  text-sm text-ink-900 placeholder-ink-400
5165
5214
  bg-white border rounded-lg
@@ -5169,12 +5218,16 @@ const Autocomplete = forwardRef(({ value, onChange, options = [], onSearch, labe
5169
5218
  ${error
5170
5219
  ? 'border-error-500 focus:ring-error-400 focus:border-error-400'
5171
5220
  : 'border-paper-300'}
5172
- `, role: "combobox", "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Search' : undefined, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? `autocomplete-option-${highlightedIndex}` : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : undefined, "aria-busy": loading }), clearable && value && !disabled && (jsx("button", { type: "button", onClick: handleClear, className: "absolute right-3 top-1/2 -translate-y-1/2 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Clear", children: jsx(X, { className: "h-4 w-4" }) }))] }), isOpen && filteredOptions.length > 0 && (jsx("div", { ref: dropdownRef, id: listboxId, className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg max-h-60 overflow-y-auto", role: "listbox", "aria-label": "Search results", children: filteredOptions.map((option, index) => (jsxs("button", { id: `autocomplete-option-${index}`, type: "button", onClick: () => handleSelect(option), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": highlightedIndex === index, className: `
5173
- w-full text-left px-3 py-2 transition-colors
5174
- ${highlightedIndex === index
5221
+ `, role: "combobox", "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Search' : undefined, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? `autocomplete-option-${highlightedIndex}` : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : undefined, "aria-busy": loading }), clearable && value && !disabled && (jsx("button", { type: "button", onClick: handleClear, className: "absolute right-3 top-1/2 -translate-y-1/2 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Clear", children: jsx(X, { className: "h-4 w-4" }) }))] }), isOpen && filteredOptions.length > 0 && (jsx("div", { ref: dropdownRef, id: listboxId, className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg max-h-60 overflow-y-auto", role: "listbox", "aria-label": "Search results", children: filteredOptions.map((option, index) => (option.isHeader ? (
5222
+ // Render section header (non-selectable)
5223
+ jsx("div", { className: "px-3 py-2 text-xs font-semibold text-ink-500 uppercase tracking-wide bg-paper-50 border-t border-paper-200 first:border-t-0 first:rounded-t-lg cursor-default", role: "presentation", children: option.label }, `header-${option.value}`)) : (
5224
+ // Render selectable option
5225
+ jsxs("button", { id: `autocomplete-option-${index}`, type: "button", onClick: () => handleSelect(option), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": highlightedIndex === index, className: `
5226
+ w-full text-left px-3 py-2 transition-colors
5227
+ ${highlightedIndex === index
5175
5228
  ? 'bg-accent-50'
5176
5229
  : 'hover:bg-paper-50'}
5177
- `, children: [jsx("div", { className: "text-sm font-medium text-ink-900", children: option.label }), option.description && (jsx("div", { className: "text-xs text-ink-600 mt-0.5", children: option.description }))] }, option.value))) })), isOpen && !loading && filteredOptions.length === 0 && value.length >= minChars && (jsx("div", { className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg p-3", role: "status", "aria-live": "polite", children: jsx("p", { className: "text-sm text-ink-500 text-center", children: "No results found" }) })), error && (jsx("p", { id: errorId, className: "mt-1.5 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsx("p", { className: "mt-1.5 text-xs text-ink-600", children: helperText }))] }));
5230
+ `, children: [jsx("div", { className: "text-sm font-medium text-ink-900", children: option.label }), option.description && (jsx("div", { className: "text-xs text-ink-600 mt-0.5", children: option.description }))] }, option.value)))) })), isOpen && !loading && filteredOptions.length === 0 && value.length >= minChars && (jsx("div", { className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg p-3", role: "status", "aria-live": "polite", children: jsx("p", { className: "text-sm text-ink-500 text-center", children: "No results found" }) })), error && (jsx("p", { id: errorId, className: "mt-1.5 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsx("p", { className: "mt-1.5 text-xs text-ink-600", children: helperText }))] }));
5178
5231
  });
5179
5232
  Autocomplete.displayName = 'Autocomplete';
5180
5233