@atelier-ui/react 0.1.2 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,58 @@
1
+ ## 0.1.7 (2026-05-30)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **tooling:** replace @behavior comment marker with typed covers() gate ([ff36fe9](https://github.com/DominikPieper/atelier-ui/commit/ff36fe9))
6
+ - **spec:** AI-readiness metadata + token manifest layer ([d7f6235](https://github.com/DominikPieper/atelier-ui/commit/d7f6235))
7
+
8
+ ### ❤️ Thank You
9
+
10
+ - Claude Opus 4.7 (1M context)
11
+ - Claude Opus 4.8 (1M context)
12
+ - Dominik Pieper @DominikPieper
13
+
14
+ ## 0.1.6 (2026-05-27)
15
+
16
+ ### 🚀 Features
17
+
18
+ - **a11y:** add roving arrow-key navigation to React/Vue radio-group ([0c2ae81](https://github.com/DominikPieper/atelier-ui/commit/0c2ae81))
19
+ - **tooling:** enforce behavioral-parity coverage across framework adapters ([06c1829](https://github.com/DominikPieper/atelier-ui/commit/06c1829))
20
+
21
+ ### 🩹 Fixes
22
+
23
+ - **angular:** suppress click events on disabled button ([1639ced](https://github.com/DominikPieper/atelier-ui/commit/1639ced))
24
+
25
+ ### ❤️ Thank You
26
+
27
+ - Claude Opus 4.7 (1M context)
28
+ - Dominik Pieper @DominikPieper
29
+
30
+ ## 0.1.5 (2026-05-20)
31
+
32
+ This was a version bump only for react to align it with other projects, there were no code changes.
33
+
34
+ ## 0.1.4 (2026-05-20)
35
+
36
+ ### 🩹 Fixes
37
+
38
+ - **storybook-mcp:** align hosted MCPs with Storybook 10.4 reality ([a043e56](https://github.com/DominikPieper/atelier-ui/commit/a043e56))
39
+
40
+ ### ❤️ Thank You
41
+
42
+ - Claude Opus 4.7 (1M context)
43
+ - Dominik Pieper @DominikPieper
44
+
45
+ ## 0.1.3 (2026-05-19)
46
+
47
+ ### 🩹 Fixes
48
+
49
+ - **lint:** satisfy new typescript-eslint + react-hooks v7.1 rules ([e9713f6](https://github.com/DominikPieper/atelier-ui/commit/e9713f6))
50
+
51
+ ### ❤️ Thank You
52
+
53
+ - Claude Opus 4.7 (1M context)
54
+ - Dominik Pieper @DominikPieper
55
+
1
56
  ## 0.1.2 (2026-04-30)
2
57
 
3
58
  This was a version bump only for react to align it with other projects, there were no code changes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atelier-ui/react",
3
- "version": "0.1.2",
3
+ "version": "0.1.7",
4
4
  "description": "Atelier component library for React — LLM-optimised, accessible, design-token-driven",
5
5
  "keywords": [
6
6
  "react",
@@ -1 +1 @@
1
- {"version":3,"file":"llm-combobox.d.ts","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/combobox/llm-combobox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,oBAAoB,CAAC;AAE5B,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAIlC,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAU,EACV,aAAa,EACb,OAAY,EACZ,WAAgB,EAChB,QAAgB,EAChB,QAAgB,EAChB,OAAe,EACf,QAAgB,EAChB,IAAI,EACJ,MAAW,GACZ,EAAE,gBAAgB,2CA8MlB"}
1
+ {"version":3,"file":"llm-combobox.d.ts","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/combobox/llm-combobox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,oBAAoB,CAAC;AAE5B,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAU,EACV,aAAa,EACb,OAAY,EACZ,WAAgB,EAChB,QAAgB,EAChB,QAAgB,EAChB,OAAe,EACf,QAAgB,EAChB,IAAI,EACJ,MAAW,GACZ,EAAE,gBAAgB,2CA2MlB"}
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useEffect, useId, useMemo, useRef, useState } from 'react';
3
3
  import './llm-combobox.css';
4
- let nextId = 0;
5
4
  /**
6
5
  * Filterable autocomplete combobox. Accepts an `options` array and emits the
7
6
  * selected option's `value`. The user types to narrow the list; selecting an
@@ -23,7 +22,8 @@ export function LlmCombobox({ value = '', onValueChange, options = [], placehold
23
22
  const [activeIndex, setActiveIndex] = useState(-1);
24
23
  const inputRef = useRef(null);
25
24
  const containerRef = useRef(null);
26
- const ids = useRef({ input: `llm-combobox-input-${nextId}`, panel: `llm-combobox-panel-${nextId++}` });
25
+ const inputId = useId();
26
+ const panelId = useId();
27
27
  const selectedLabel = useMemo(() => options.find((o) => o.value === value)?.label ?? '', [options, value]);
28
28
  const filteredOptions = useMemo(() => {
29
29
  if (!query)
@@ -31,12 +31,7 @@ export function LlmCombobox({ value = '', onValueChange, options = [], placehold
31
31
  const q = query.toLowerCase();
32
32
  return options.filter((o) => o.label.toLowerCase().includes(q));
33
33
  }, [options, query]);
34
- // Sync display when value changes externally
35
- useEffect(() => {
36
- if (!isOpen) {
37
- setQuery(selectedLabel);
38
- }
39
- }, [selectedLabel, isOpen]);
34
+ const displayValue = isOpen ? query : selectedLabel;
40
35
  // Outside-click closes panel
41
36
  useEffect(() => {
42
37
  if (!isOpen)
@@ -53,6 +48,7 @@ export function LlmCombobox({ value = '', onValueChange, options = [], placehold
53
48
  function open() {
54
49
  if (disabled || readonly)
55
50
  return;
51
+ setQuery(selectedLabel);
56
52
  setIsOpen(true);
57
53
  setActiveIndex(-1);
58
54
  }
@@ -130,8 +126,8 @@ export function LlmCombobox({ value = '', onValueChange, options = [], placehold
130
126
  ]
131
127
  .filter(Boolean)
132
128
  .join(' ');
133
- const activeOptionId = activeIndex >= 0 ? `${ids.current.panel}-option-${activeIndex}` : undefined;
134
- return (_jsxs("div", { className: classes, ref: containerRef, children: [_jsxs("div", { className: "llm-combobox-wrapper", children: [_jsx("input", { ref: inputRef, className: "llm-combobox-input", type: "text", autoComplete: "off", role: "combobox", id: ids.current.input, "aria-expanded": isOpen, "aria-controls": ids.current.panel, "aria-autocomplete": "list", "aria-activedescendant": activeOptionId, "aria-invalid": invalid || undefined, disabled: disabled, readOnly: readonly, required: required, name: name, placeholder: placeholder, value: query, onChange: handleInput, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeydown }), _jsx("span", { className: "llm-combobox-icon", "aria-hidden": "true", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: _jsx("path", { d: "M2 4l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }), isOpen && (_jsx("ul", { id: ids.current.panel, className: "llm-combobox-panel", role: "listbox", "aria-labelledby": ids.current.input, children: filteredOptions.length > 0 ? (filteredOptions.map((option, i) => (_jsxs("li", { id: `${ids.current.panel}-option-${i}`, role: "option", className: [
129
+ const activeOptionId = activeIndex >= 0 ? `${panelId}-option-${activeIndex}` : undefined;
130
+ return (_jsxs("div", { className: classes, ref: containerRef, children: [_jsxs("div", { className: "llm-combobox-wrapper", children: [_jsx("input", { ref: inputRef, className: "llm-combobox-input", type: "text", autoComplete: "off", role: "combobox", id: inputId, "aria-expanded": isOpen, "aria-controls": panelId, "aria-autocomplete": "list", "aria-activedescendant": activeOptionId, "aria-invalid": invalid || undefined, disabled: disabled, readOnly: readonly, required: required, name: name, placeholder: placeholder, value: displayValue, onChange: handleInput, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeydown }), _jsx("span", { className: "llm-combobox-icon", "aria-hidden": "true", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: _jsx("path", { d: "M2 4l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }), isOpen && (_jsx("ul", { id: panelId, className: "llm-combobox-panel", role: "listbox", "aria-labelledby": inputId, children: filteredOptions.length > 0 ? (filteredOptions.map((option, i) => (_jsxs("li", { id: `${panelId}-option-${i}`, role: "option", className: [
135
131
  'llm-combobox-option',
136
132
  activeIndex === i && 'is-active',
137
133
  option.value === value && 'is-selected',
@@ -1 +1 @@
1
- {"version":3,"file":"llm-combobox.js","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/combobox/llm-combobox.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { LlmComboboxOption, LlmComboboxSpec } from '../spec';\nimport './llm-combobox.css';\n\nexport type { LlmComboboxOption };\n\nlet nextId = 0;\n\nexport interface LlmComboboxProps extends LlmComboboxSpec {\n value?: string;\n onValueChange?: (value: string) => void;\n errors?: string[];\n}\n\n/**\n * Filterable autocomplete combobox. Accepts an `options` array and emits the\n * selected option's `value`. The user types to narrow the list; selecting an\n * option commits the value and displays its label.\n *\n * Usage:\n * ```tsx\n * <LlmCombobox\n * value={country}\n * onValueChange={setCountry}\n * options={countryOptions}\n * placeholder=\"Search country…\"\n * />\n * ```\n */\nexport function LlmCombobox({\n value = '',\n onValueChange,\n options = [],\n placeholder = '',\n disabled = false,\n readonly = false,\n invalid = false,\n required = false,\n name,\n errors = [],\n}: LlmComboboxProps) {\n const [query, setQuery] = useState('');\n const [isOpen, setIsOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(-1);\n\n const inputRef = useRef<HTMLInputElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const ids = useRef({ input: `llm-combobox-input-${nextId}`, panel: `llm-combobox-panel-${nextId++}` });\n\n const selectedLabel = useMemo(\n () => options.find((o) => o.value === value)?.label ?? '',\n [options, value],\n );\n\n const filteredOptions = useMemo(() => {\n if (!query) return options;\n const q = query.toLowerCase();\n return options.filter((o) => o.label.toLowerCase().includes(q));\n }, [options, query]);\n\n // Sync display when value changes externally\n useEffect(() => {\n if (!isOpen) {\n setQuery(selectedLabel);\n }\n }, [selectedLabel, isOpen]);\n\n // Outside-click closes panel\n useEffect(() => {\n if (!isOpen) return;\n function handleClick(e: MouseEvent) {\n if (!containerRef.current?.contains(e.target as Node)) {\n setIsOpen(false);\n setQuery(selectedLabel);\n }\n }\n document.addEventListener('click', handleClick);\n return () => document.removeEventListener('click', handleClick);\n }, [isOpen, selectedLabel]);\n\n function open() {\n if (disabled || readonly) return;\n setIsOpen(true);\n setActiveIndex(-1);\n }\n\n function close() {\n setIsOpen(false);\n setActiveIndex(-1);\n }\n\n function selectOption(option: LlmComboboxOption) {\n if (option.disabled || disabled || readonly) return;\n onValueChange?.(option.value);\n setQuery(option.label);\n close();\n inputRef.current?.focus();\n }\n\n function handleInput(e: React.ChangeEvent<HTMLInputElement>) {\n const q = e.target.value;\n setQuery(q);\n setActiveIndex(-1);\n if (q === '') onValueChange?.('');\n if (!isOpen) open();\n }\n\n function handleFocus() {\n open();\n }\n\n function handleBlur() {\n setQuery(selectedLabel);\n }\n\n function handleKeydown(e: React.KeyboardEvent) {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n if (!isOpen) { open(); return; }\n setActiveIndex((i) => (i + 1 >= filteredOptions.length ? 0 : i + 1));\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n if (!isOpen) { open(); return; }\n setActiveIndex((i) => (i - 1 < 0 ? filteredOptions.length - 1 : i - 1));\n break;\n }\n case 'Enter': {\n e.preventDefault();\n if (isOpen && activeIndex >= 0 && activeIndex < filteredOptions.length) {\n selectOption(filteredOptions[activeIndex]);\n }\n break;\n }\n case 'Escape': {\n e.preventDefault();\n setQuery(selectedLabel);\n close();\n break;\n }\n case 'Tab': {\n close();\n break;\n }\n }\n }\n\n const classes = [\n 'llm-combobox',\n isOpen && 'is-open',\n disabled && 'is-disabled',\n invalid && 'is-invalid',\n ]\n .filter(Boolean)\n .join(' ');\n\n const activeOptionId =\n activeIndex >= 0 ? `${ids.current.panel}-option-${activeIndex}` : undefined;\n\n return (\n <div className={classes} ref={containerRef}>\n <div className=\"llm-combobox-wrapper\">\n <input\n ref={inputRef}\n className=\"llm-combobox-input\"\n type=\"text\"\n autoComplete=\"off\"\n role=\"combobox\"\n id={ids.current.input}\n aria-expanded={isOpen}\n aria-controls={ids.current.panel}\n aria-autocomplete=\"list\"\n aria-activedescendant={activeOptionId}\n aria-invalid={invalid || undefined}\n disabled={disabled}\n readOnly={readonly}\n required={required}\n name={name}\n placeholder={placeholder}\n value={query}\n onChange={handleInput}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeydown}\n />\n <span className=\"llm-combobox-icon\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M2 4l4 4 4-4\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </span>\n </div>\n\n {isOpen && (\n <ul\n id={ids.current.panel}\n className=\"llm-combobox-panel\"\n role=\"listbox\"\n aria-labelledby={ids.current.input}\n >\n {filteredOptions.length > 0 ? (\n filteredOptions.map((option, i) => (\n <li\n key={option.value}\n id={`${ids.current.panel}-option-${i}`}\n role=\"option\"\n className={[\n 'llm-combobox-option',\n activeIndex === i && 'is-active',\n option.value === value && 'is-selected',\n option.disabled && 'is-disabled',\n ]\n .filter(Boolean)\n .join(' ')}\n aria-selected={option.value === value}\n aria-disabled={option.disabled || undefined}\n onMouseDown={() => selectOption(option)}\n onMouseEnter={() => setActiveIndex(i)}\n >\n <span>{option.label}</span>\n {option.value === value && (\n <svg className=\"llm-combobox-check\" width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M2 7l4 4 6-7\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )}\n </li>\n ))\n ) : (\n <li className=\"llm-combobox-no-results\" role=\"option\" aria-selected={false} aria-disabled=\"true\">\n No results found.\n </li>\n )}\n </ul>\n )}\n\n {invalid && errors.length > 0 && (\n <div className=\"llm-combobox-errors\" role=\"alert\" aria-live=\"polite\">\n {errors.map((e, i) => (\n <p key={i} className=\"llm-combobox-error-message\">{e}</p>\n ))}\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"llm-combobox.js","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/combobox/llm-combobox.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useId, useMemo, useRef, useState } from 'react';\nimport type { LlmComboboxOption, LlmComboboxSpec } from '../spec';\nimport './llm-combobox.css';\n\nexport type { LlmComboboxOption };\n\nexport interface LlmComboboxProps extends LlmComboboxSpec {\n value?: string;\n onValueChange?: (value: string) => void;\n errors?: string[];\n}\n\n/**\n * Filterable autocomplete combobox. Accepts an `options` array and emits the\n * selected option's `value`. The user types to narrow the list; selecting an\n * option commits the value and displays its label.\n *\n * Usage:\n * ```tsx\n * <LlmCombobox\n * value={country}\n * onValueChange={setCountry}\n * options={countryOptions}\n * placeholder=\"Search country…\"\n * />\n * ```\n */\nexport function LlmCombobox({\n value = '',\n onValueChange,\n options = [],\n placeholder = '',\n disabled = false,\n readonly = false,\n invalid = false,\n required = false,\n name,\n errors = [],\n}: LlmComboboxProps) {\n const [query, setQuery] = useState('');\n const [isOpen, setIsOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(-1);\n\n const inputRef = useRef<HTMLInputElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const inputId = useId();\n const panelId = useId();\n\n const selectedLabel = useMemo(\n () => options.find((o) => o.value === value)?.label ?? '',\n [options, value],\n );\n\n const filteredOptions = useMemo(() => {\n if (!query) return options;\n const q = query.toLowerCase();\n return options.filter((o) => o.label.toLowerCase().includes(q));\n }, [options, query]);\n\n const displayValue = isOpen ? query : selectedLabel;\n\n // Outside-click closes panel\n useEffect(() => {\n if (!isOpen) return;\n function handleClick(e: MouseEvent) {\n if (!containerRef.current?.contains(e.target as Node)) {\n setIsOpen(false);\n setQuery(selectedLabel);\n }\n }\n document.addEventListener('click', handleClick);\n return () => document.removeEventListener('click', handleClick);\n }, [isOpen, selectedLabel]);\n\n function open() {\n if (disabled || readonly) return;\n setQuery(selectedLabel);\n setIsOpen(true);\n setActiveIndex(-1);\n }\n\n function close() {\n setIsOpen(false);\n setActiveIndex(-1);\n }\n\n function selectOption(option: LlmComboboxOption) {\n if (option.disabled || disabled || readonly) return;\n onValueChange?.(option.value);\n setQuery(option.label);\n close();\n inputRef.current?.focus();\n }\n\n function handleInput(e: React.ChangeEvent<HTMLInputElement>) {\n const q = e.target.value;\n setQuery(q);\n setActiveIndex(-1);\n if (q === '') onValueChange?.('');\n if (!isOpen) open();\n }\n\n function handleFocus() {\n open();\n }\n\n function handleBlur() {\n setQuery(selectedLabel);\n }\n\n function handleKeydown(e: React.KeyboardEvent) {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n if (!isOpen) { open(); return; }\n setActiveIndex((i) => (i + 1 >= filteredOptions.length ? 0 : i + 1));\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n if (!isOpen) { open(); return; }\n setActiveIndex((i) => (i - 1 < 0 ? filteredOptions.length - 1 : i - 1));\n break;\n }\n case 'Enter': {\n e.preventDefault();\n if (isOpen && activeIndex >= 0 && activeIndex < filteredOptions.length) {\n selectOption(filteredOptions[activeIndex]);\n }\n break;\n }\n case 'Escape': {\n e.preventDefault();\n setQuery(selectedLabel);\n close();\n break;\n }\n case 'Tab': {\n close();\n break;\n }\n }\n }\n\n const classes = [\n 'llm-combobox',\n isOpen && 'is-open',\n disabled && 'is-disabled',\n invalid && 'is-invalid',\n ]\n .filter(Boolean)\n .join(' ');\n\n const activeOptionId =\n activeIndex >= 0 ? `${panelId}-option-${activeIndex}` : undefined;\n\n return (\n <div className={classes} ref={containerRef}>\n <div className=\"llm-combobox-wrapper\">\n <input\n ref={inputRef}\n className=\"llm-combobox-input\"\n type=\"text\"\n autoComplete=\"off\"\n role=\"combobox\"\n id={inputId}\n aria-expanded={isOpen}\n aria-controls={panelId}\n aria-autocomplete=\"list\"\n aria-activedescendant={activeOptionId}\n aria-invalid={invalid || undefined}\n disabled={disabled}\n readOnly={readonly}\n required={required}\n name={name}\n placeholder={placeholder}\n value={displayValue}\n onChange={handleInput}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeydown}\n />\n <span className=\"llm-combobox-icon\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M2 4l4 4 4-4\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </span>\n </div>\n\n {isOpen && (\n <ul\n id={panelId}\n className=\"llm-combobox-panel\"\n role=\"listbox\"\n aria-labelledby={inputId}\n >\n {filteredOptions.length > 0 ? (\n filteredOptions.map((option, i) => (\n <li\n key={option.value}\n id={`${panelId}-option-${i}`}\n role=\"option\"\n className={[\n 'llm-combobox-option',\n activeIndex === i && 'is-active',\n option.value === value && 'is-selected',\n option.disabled && 'is-disabled',\n ]\n .filter(Boolean)\n .join(' ')}\n aria-selected={option.value === value}\n aria-disabled={option.disabled || undefined}\n onMouseDown={() => selectOption(option)}\n onMouseEnter={() => setActiveIndex(i)}\n >\n <span>{option.label}</span>\n {option.value === value && (\n <svg className=\"llm-combobox-check\" width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M2 7l4 4 6-7\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )}\n </li>\n ))\n ) : (\n <li className=\"llm-combobox-no-results\" role=\"option\" aria-selected={false} aria-disabled=\"true\">\n No results found.\n </li>\n )}\n </ul>\n )}\n\n {invalid && errors.length > 0 && (\n <div className=\"llm-combobox-errors\" role=\"alert\" aria-live=\"polite\">\n {errors.map((e, i) => (\n <p key={i} className=\"llm-combobox-error-message\">{e}</p>\n ))}\n </div>\n )}\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"llm-radio-group.d.ts","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/radio-group/llm-radio-group.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,mBAAmB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,uBAAuB,CAAC;AAE/B;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;OAEG;IACH,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,iBAAiB,iDAQ5B,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,kBACf,SAAQ,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC3D,iBAAiB;IACnB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;OAEG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAU,EACV,aAAa,EACb,WAAwB,EACxB,IAAS,EACT,QAAgB,EAChB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,YAAY,EACtB,OAAe,EACf,QAAgB,EAChB,MAAW,EACX,QAAQ,EACR,SAAS,EACT,GAAG,IAAI,EACR,EAAE,kBAAkB,2CA6CpB;AAED,wBAAgB,aAAa,IAAI,sBAAsB,CAEtD"}
1
+ {"version":3,"file":"llm-radio-group.d.ts","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/radio-group/llm-radio-group.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,mBAAmB,EACnB,SAAS,EACV,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,uBAAuB,CAAC;AAE/B;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;OAEG;IACH,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,iBAAiB,iDAQ5B,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,kBACf,SAAQ,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC3D,iBAAiB;IACnB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;OAEG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAU,EACV,aAAa,EACb,WAAwB,EACxB,IAAS,EACT,QAAgB,EAChB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,YAAY,EACtB,OAAe,EACf,QAAgB,EAChB,MAAW,EACX,QAAQ,EACR,SAAS,EACT,GAAG,IAAI,EACR,EAAE,kBAAkB,2CAsEpB;AAED,wBAAgB,aAAa,IAAI,sBAAsB,CAEtD"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { createContext, useContext } from 'react';
2
+ import { createContext, useContext, useRef, } from 'react';
3
3
  import './llm-radio-group.css';
4
4
  export const RadioGroupContext = createContext({
5
5
  value: '',
@@ -15,6 +15,28 @@ export const RadioGroupContext = createContext({
15
15
  */
16
16
  export function LlmRadioGroup({ value = '', onValueChange, orientation = 'vertical', name = '', disabled = false, readOnly: reactReadOnly, readonly: specReadOnly, invalid = false, required = false, errors = [], children, className, ...rest }) {
17
17
  const readOnly = reactReadOnly ?? specReadOnly ?? false;
18
+ const groupRef = useRef(null);
19
+ // Roving arrow-key navigation per the WAI-ARIA radiogroup pattern: Arrow
20
+ // Down/Right move to the next enabled radio, Up/Left to the previous, both
21
+ // wrapping, and the navigated radio is selected and focused.
22
+ const handleKeyDown = (event) => {
23
+ if (disabled || readOnly)
24
+ return;
25
+ const forward = event.key === 'ArrowDown' || event.key === 'ArrowRight';
26
+ const backward = event.key === 'ArrowUp' || event.key === 'ArrowLeft';
27
+ if (!forward && !backward)
28
+ return;
29
+ const radios = Array.from(groupRef.current?.querySelectorAll('input[type="radio"]') ?? []).filter((radio) => !radio.disabled);
30
+ if (radios.length === 0)
31
+ return;
32
+ event.preventDefault();
33
+ const currentIdx = radios.findIndex((radio) => radio.value === value);
34
+ const start = currentIdx === -1 ? (forward ? -1 : 0) : currentIdx;
35
+ const nextIdx = (start + (forward ? 1 : -1) + radios.length) % radios.length;
36
+ const target = radios[nextIdx];
37
+ target.focus();
38
+ onValueChange?.(target.value);
39
+ };
18
40
  const classes = [
19
41
  'llm-radio-group',
20
42
  `orientation-${orientation}`,
@@ -33,7 +55,7 @@ export function LlmRadioGroup({ value = '', onValueChange, orientation = 'vertic
33
55
  invalid,
34
56
  onSelect: (v) => !disabled && !readOnly && onValueChange?.(v),
35
57
  onBlur: () => undefined,
36
- }, children: _jsxs("div", { className: classes, role: "radiogroup", "aria-invalid": invalid || undefined, "aria-required": required || undefined, ...rest, children: [children, errors.length > 0 && (_jsx("div", { className: "errors", role: "alert", "aria-live": "polite", children: errors.map((err, i) => (_jsx("p", { className: "error-message", children: err }, i))) }))] }) }));
58
+ }, children: _jsxs("div", { ref: groupRef, className: classes, role: "radiogroup", "aria-invalid": invalid || undefined, "aria-required": required || undefined, onKeyDown: handleKeyDown, ...rest, children: [children, errors.length > 0 && (_jsx("div", { className: "errors", role: "alert", "aria-live": "polite", children: errors.map((err, i) => (_jsx("p", { className: "error-message", children: err }, i))) }))] }) }));
37
59
  }
38
60
  export function useRadioGroup() {
39
61
  return useContext(RadioGroupContext);
@@ -1 +1 @@
1
- {"version":3,"file":"llm-radio-group.js","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/radio-group/llm-radio-group.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkC,MAAM,OAAO,CAAC;AAElF,OAAO,uBAAuB,CAAC;AAoC/B,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAyB;IACrE,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,GAAG,EAAE,CAAC,SAAS;IACzB,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS;CACxB,CAAC,CAAC;AAkDH;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,KAAK,GAAG,EAAE,EACV,aAAa,EACb,WAAW,GAAG,UAAU,EACxB,IAAI,GAAG,EAAE,EACT,QAAQ,GAAG,KAAK,EAChB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,YAAY,EACtB,OAAO,GAAG,KAAK,EACf,QAAQ,GAAG,KAAK,EAChB,MAAM,GAAG,EAAE,EACX,QAAQ,EACR,SAAS,EACT,GAAG,IAAI,EACY;IACnB,MAAM,QAAQ,GAAG,aAAa,IAAI,YAAY,IAAI,KAAK,CAAC;IACxD,MAAM,OAAO,GAAG;QACd,iBAAiB;QACjB,eAAe,WAAW,EAAE;QAC5B,OAAO,IAAI,YAAY;QACvB,QAAQ,IAAI,aAAa;QACzB,QAAQ,IAAI,aAAa;QACzB,SAAS;KACV;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IACzB,KAAK,EAAE;YACL,KAAK;YACL,IAAI;YACJ,QAAQ;YACR,QAAQ;YACR,OAAO;YACP,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,QAAQ,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC;YAC7D,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS;SACxB,YAED,eACE,SAAS,EAAE,OAAO,EAClB,IAAI,EAAC,YAAY,kBACH,OAAO,IAAI,SAAS,mBACnB,QAAQ,IAAI,SAAS,KAChC,IAAI,aAEP,QAAQ,EACR,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CACpB,cAAK,SAAS,EAAC,QAAQ,EAAC,IAAI,EAAC,OAAO,eAAW,QAAQ,YACpD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CACtB,YAAW,SAAS,EAAC,eAAe,YACjC,GAAG,IADE,CAAC,CAEL,CACL,CAAC,GACE,CACP,IACG,GACqB,CAC9B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,CAAC,iBAAiB,CAAC,CAAC;AACvC,CAAC","sourcesContent":["import { createContext, useContext, InputHTMLAttributes, ReactNode } from 'react';\nimport type { LlmRadioGroupSpec } from '../spec';\nimport './llm-radio-group.css';\n\n/**\n * Interface for the RadioGroup context.\n */\nexport interface RadioGroupContextValue {\n /**\n * The currently selected value.\n */\n value: string;\n /**\n * The name of the radio group.\n */\n name: string;\n /**\n * Whether the entire radio group is disabled.\n */\n disabled: boolean;\n /**\n * Whether the entire radio group is read-only.\n */\n readOnly: boolean;\n /**\n * Whether the radio group is in an invalid state.\n */\n invalid: boolean;\n /**\n * Callback to select a value.\n */\n onSelect: (value: string) => void;\n /**\n * Callback triggered on blur.\n */\n onBlur: () => void;\n}\n\nexport const RadioGroupContext = createContext<RadioGroupContextValue>({\n value: '',\n name: '',\n disabled: false,\n readOnly: false,\n invalid: false,\n onSelect: () => undefined,\n onBlur: () => undefined,\n});\n\n/**\n * Properties for the LlmRadioGroup component.\n */\nexport interface LlmRadioGroupProps\n extends Omit<InputHTMLAttributes<HTMLDivElement>, 'onChange'>,\n LlmRadioGroupSpec {\n /**\n * The current value of the radio group.\n */\n value?: string;\n /**\n * Callback triggered when the value changes.\n */\n onValueChange?: (value: string) => void;\n /**\n * The layout orientation of the radio group.\n */\n orientation?: 'horizontal' | 'vertical';\n /**\n * The name of the radio group, applied to all child radio buttons.\n */\n name?: string;\n /**\n * Whether the radio group is disabled.\n */\n disabled?: boolean;\n /**\n * Whether the radio group is read-only.\n */\n readOnly?: boolean;\n /**\n * Whether the radio group is in an invalid state.\n */\n invalid?: boolean;\n /**\n * Whether a value is required.\n */\n required?: boolean;\n /**\n * Array of error messages to display.\n */\n errors?: string[];\n /**\n * The radio buttons to be rendered.\n */\n children?: ReactNode;\n}\n\n/**\n * A group of radio buttons.\n */\nexport function LlmRadioGroup({\n value = '',\n onValueChange,\n orientation = 'vertical',\n name = '',\n disabled = false,\n readOnly: reactReadOnly,\n readonly: specReadOnly,\n invalid = false,\n required = false,\n errors = [],\n children,\n className,\n ...rest\n}: LlmRadioGroupProps) {\n const readOnly = reactReadOnly ?? specReadOnly ?? false;\n const classes = [\n 'llm-radio-group',\n `orientation-${orientation}`,\n invalid && 'is-invalid',\n disabled && 'is-disabled',\n readOnly && 'is-readonly',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <RadioGroupContext.Provider\n value={{\n value,\n name,\n disabled,\n readOnly,\n invalid,\n onSelect: (v) => !disabled && !readOnly && onValueChange?.(v),\n onBlur: () => undefined,\n }}\n >\n <div\n className={classes}\n role=\"radiogroup\"\n aria-invalid={invalid || undefined}\n aria-required={required || undefined}\n {...rest}\n >\n {children}\n {errors.length > 0 && (\n <div className=\"errors\" role=\"alert\" aria-live=\"polite\">\n {errors.map((err, i) => (\n <p key={i} className=\"error-message\">\n {err}\n </p>\n ))}\n </div>\n )}\n </div>\n </RadioGroupContext.Provider>\n );\n}\n\nexport function useRadioGroup(): RadioGroupContextValue {\n return useContext(RadioGroupContext);\n}\n"]}
1
+ {"version":3,"file":"llm-radio-group.js","sourceRoot":"","sources":["../../../../../../libs/react/src/lib/radio-group/llm-radio-group.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n createContext,\n useContext,\n useRef,\n type KeyboardEvent,\n InputHTMLAttributes,\n ReactNode,\n} from 'react';\nimport type { LlmRadioGroupSpec } from '../spec';\nimport './llm-radio-group.css';\n\n/**\n * Interface for the RadioGroup context.\n */\nexport interface RadioGroupContextValue {\n /**\n * The currently selected value.\n */\n value: string;\n /**\n * The name of the radio group.\n */\n name: string;\n /**\n * Whether the entire radio group is disabled.\n */\n disabled: boolean;\n /**\n * Whether the entire radio group is read-only.\n */\n readOnly: boolean;\n /**\n * Whether the radio group is in an invalid state.\n */\n invalid: boolean;\n /**\n * Callback to select a value.\n */\n onSelect: (value: string) => void;\n /**\n * Callback triggered on blur.\n */\n onBlur: () => void;\n}\n\nexport const RadioGroupContext = createContext<RadioGroupContextValue>({\n value: '',\n name: '',\n disabled: false,\n readOnly: false,\n invalid: false,\n onSelect: () => undefined,\n onBlur: () => undefined,\n});\n\n/**\n * Properties for the LlmRadioGroup component.\n */\nexport interface LlmRadioGroupProps\n extends Omit<InputHTMLAttributes<HTMLDivElement>, 'onChange'>,\n LlmRadioGroupSpec {\n /**\n * The current value of the radio group.\n */\n value?: string;\n /**\n * Callback triggered when the value changes.\n */\n onValueChange?: (value: string) => void;\n /**\n * The layout orientation of the radio group.\n */\n orientation?: 'horizontal' | 'vertical';\n /**\n * The name of the radio group, applied to all child radio buttons.\n */\n name?: string;\n /**\n * Whether the radio group is disabled.\n */\n disabled?: boolean;\n /**\n * Whether the radio group is read-only.\n */\n readOnly?: boolean;\n /**\n * Whether the radio group is in an invalid state.\n */\n invalid?: boolean;\n /**\n * Whether a value is required.\n */\n required?: boolean;\n /**\n * Array of error messages to display.\n */\n errors?: string[];\n /**\n * The radio buttons to be rendered.\n */\n children?: ReactNode;\n}\n\n/**\n * A group of radio buttons.\n */\nexport function LlmRadioGroup({\n value = '',\n onValueChange,\n orientation = 'vertical',\n name = '',\n disabled = false,\n readOnly: reactReadOnly,\n readonly: specReadOnly,\n invalid = false,\n required = false,\n errors = [],\n children,\n className,\n ...rest\n}: LlmRadioGroupProps) {\n const readOnly = reactReadOnly ?? specReadOnly ?? false;\n const groupRef = useRef<HTMLDivElement>(null);\n\n // Roving arrow-key navigation per the WAI-ARIA radiogroup pattern: Arrow\n // Down/Right move to the next enabled radio, Up/Left to the previous, both\n // wrapping, and the navigated radio is selected and focused.\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (disabled || readOnly) return;\n const forward = event.key === 'ArrowDown' || event.key === 'ArrowRight';\n const backward = event.key === 'ArrowUp' || event.key === 'ArrowLeft';\n if (!forward && !backward) return;\n const radios = Array.from(\n groupRef.current?.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]') ?? []\n ).filter((radio) => !radio.disabled);\n if (radios.length === 0) return;\n event.preventDefault();\n const currentIdx = radios.findIndex((radio) => radio.value === value);\n const start = currentIdx === -1 ? (forward ? -1 : 0) : currentIdx;\n const nextIdx = (start + (forward ? 1 : -1) + radios.length) % radios.length;\n const target = radios[nextIdx];\n target.focus();\n onValueChange?.(target.value);\n };\n\n const classes = [\n 'llm-radio-group',\n `orientation-${orientation}`,\n invalid && 'is-invalid',\n disabled && 'is-disabled',\n readOnly && 'is-readonly',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <RadioGroupContext.Provider\n value={{\n value,\n name,\n disabled,\n readOnly,\n invalid,\n onSelect: (v) => !disabled && !readOnly && onValueChange?.(v),\n onBlur: () => undefined,\n }}\n >\n <div\n ref={groupRef}\n className={classes}\n role=\"radiogroup\"\n aria-invalid={invalid || undefined}\n aria-required={required || undefined}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {children}\n {errors.length > 0 && (\n <div className=\"errors\" role=\"alert\" aria-live=\"polite\">\n {errors.map((err, i) => (\n <p key={i} className=\"error-message\">\n {err}\n </p>\n ))}\n </div>\n )}\n </div>\n </RadioGroupContext.Provider>\n );\n}\n\nexport function useRadioGroup(): RadioGroupContextValue {\n return useContext(RadioGroupContext);\n}\n"]}