@k2b/ui 0.25.0 → 0.27.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.
Files changed (111) hide show
  1. package/dist/browser/actions/GestureMenu.js +2 -2
  2. package/dist/browser/actions/GestureMenu.js.map +1 -1
  3. package/dist/browser/chat/ChatComposer.js +811 -406
  4. package/dist/browser/chat/ChatComposer.js.map +1 -1
  5. package/dist/browser/chat/chat-behavior.js +4 -0
  6. package/dist/browser/chat/chat-behavior.js.map +1 -1
  7. package/dist/browser/chat/chat-messages.js +14 -0
  8. package/dist/browser/chat/chat-messages.js.map +1 -1
  9. package/dist/browser/chat/command-search.js +170 -0
  10. package/dist/browser/chat/command-search.js.map +1 -0
  11. package/dist/browser/chat/composer-document.js +1 -1
  12. package/dist/browser/chat/composer-document.js.map +1 -1
  13. package/dist/browser/chat/index.js.map +1 -1
  14. package/dist/browser/content/Calendar.js +8 -7
  15. package/dist/browser/content/Calendar.js.map +1 -1
  16. package/dist/browser/content/CalendarDayView.js +32 -6
  17. package/dist/browser/content/CalendarDayView.js.map +1 -1
  18. package/dist/browser/content/CalendarWeekView.js +4 -3
  19. package/dist/browser/content/CalendarWeekView.js.map +1 -1
  20. package/dist/browser/content/Timeline.js +3 -3
  21. package/dist/browser/content/Timeline.js.map +1 -1
  22. package/dist/browser/content/calendar-time-view.js +0 -1
  23. package/dist/browser/content/calendar-time-view.js.map +1 -1
  24. package/dist/browser/index.js +1 -0
  25. package/dist/browser/index.js.map +1 -1
  26. package/dist/browser/inputs/Combobox.js +26 -5
  27. package/dist/browser/inputs/Combobox.js.map +1 -1
  28. package/dist/browser/inputs/QrScanner.js +2 -1
  29. package/dist/browser/inputs/QrScanner.js.map +1 -1
  30. package/dist/browser/inputs/SelectChip.js +43 -12
  31. package/dist/browser/inputs/SelectChip.js.map +1 -1
  32. package/dist/browser/layout/AppWorkspace.js +720 -528
  33. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  34. package/dist/browser/layout/BottomSheet.js +8 -0
  35. package/dist/browser/layout/BottomSheet.js.map +1 -1
  36. package/dist/browser/layout/app-workspace-controller.js +159 -27
  37. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  38. package/dist/browser/layout/app-workspace-state.js +5 -1
  39. package/dist/browser/layout/app-workspace-state.js.map +1 -1
  40. package/dist/browser/layout/index.js +1 -1
  41. package/dist/browser/layout/index.js.map +1 -1
  42. package/dist/browser/layout/scroll-fade.js +14 -9
  43. package/dist/browser/layout/scroll-fade.js.map +1 -1
  44. package/dist/browser/layout/sidebar-scroll-memory.js +38 -0
  45. package/dist/browser/layout/sidebar-scroll-memory.js.map +1 -0
  46. package/dist/browser/motion.js +7 -0
  47. package/dist/browser/motion.js.map +1 -0
  48. package/dist/ssr/actions/GestureMenu.js +2 -2
  49. package/dist/ssr/actions/GestureMenu.js.map +1 -1
  50. package/dist/ssr/chat/ChatComposer.js +410 -93
  51. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  52. package/dist/ssr/chat/chat-behavior.js +4 -0
  53. package/dist/ssr/chat/chat-behavior.js.map +1 -1
  54. package/dist/ssr/chat/chat-messages.js +14 -0
  55. package/dist/ssr/chat/chat-messages.js.map +1 -1
  56. package/dist/ssr/chat/command-search.js +170 -0
  57. package/dist/ssr/chat/command-search.js.map +1 -0
  58. package/dist/ssr/chat/composer-document.js +1 -1
  59. package/dist/ssr/chat/composer-document.js.map +1 -1
  60. package/dist/ssr/chat/index.js.map +1 -1
  61. package/dist/ssr/content/Calendar.js +8 -7
  62. package/dist/ssr/content/Calendar.js.map +1 -1
  63. package/dist/ssr/content/CalendarDayView.js +32 -6
  64. package/dist/ssr/content/CalendarDayView.js.map +1 -1
  65. package/dist/ssr/content/CalendarWeekView.js +4 -3
  66. package/dist/ssr/content/CalendarWeekView.js.map +1 -1
  67. package/dist/ssr/content/Timeline.js +3 -3
  68. package/dist/ssr/content/Timeline.js.map +1 -1
  69. package/dist/ssr/content/calendar-time-view.js +0 -1
  70. package/dist/ssr/content/calendar-time-view.js.map +1 -1
  71. package/dist/ssr/index.js +1 -0
  72. package/dist/ssr/index.js.map +1 -1
  73. package/dist/ssr/inputs/Combobox.js +22 -5
  74. package/dist/ssr/inputs/Combobox.js.map +1 -1
  75. package/dist/ssr/inputs/QrScanner.js +2 -1
  76. package/dist/ssr/inputs/QrScanner.js.map +1 -1
  77. package/dist/ssr/inputs/SelectChip.js +35 -11
  78. package/dist/ssr/inputs/SelectChip.js.map +1 -1
  79. package/dist/ssr/layout/AppWorkspace.js +254 -118
  80. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  81. package/dist/ssr/layout/BottomSheet.js +8 -0
  82. package/dist/ssr/layout/BottomSheet.js.map +1 -1
  83. package/dist/ssr/layout/app-workspace-controller.js +159 -27
  84. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  85. package/dist/ssr/layout/app-workspace-state.js +5 -1
  86. package/dist/ssr/layout/app-workspace-state.js.map +1 -1
  87. package/dist/ssr/layout/index.js +1 -1
  88. package/dist/ssr/layout/index.js.map +1 -1
  89. package/dist/ssr/layout/scroll-fade.js +14 -9
  90. package/dist/ssr/layout/scroll-fade.js.map +1 -1
  91. package/dist/ssr/layout/sidebar-scroll-memory.js +38 -0
  92. package/dist/ssr/layout/sidebar-scroll-memory.js.map +1 -0
  93. package/dist/ssr/motion.js +7 -0
  94. package/dist/ssr/motion.js.map +1 -0
  95. package/dist/styles.css +1 -1
  96. package/dist/types/chat/ChatComposer.d.ts +44 -2
  97. package/dist/types/chat/chat-behavior.d.ts +4 -0
  98. package/dist/types/chat/chat-messages.d.ts +7 -0
  99. package/dist/types/chat/command-search.d.ts +39 -0
  100. package/dist/types/chat/index.d.ts +1 -1
  101. package/dist/types/content/calendar-time-view.d.ts +0 -1
  102. package/dist/types/index.d.ts +1 -0
  103. package/dist/types/inputs/Combobox.d.ts +1 -0
  104. package/dist/types/inputs/SelectChip.d.ts +4 -0
  105. package/dist/types/layout/AppWorkspace.d.ts +13 -1
  106. package/dist/types/layout/BottomSheet.d.ts +8 -0
  107. package/dist/types/layout/index.d.ts +1 -1
  108. package/dist/types/layout/scroll-fade.d.ts +2 -0
  109. package/dist/types/layout/sidebar-scroll-memory.d.ts +6 -0
  110. package/dist/types/motion.d.ts +6 -0
  111. package/package.json +4 -4
@@ -13,7 +13,7 @@ var _tmpl$ = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
13
13
  _tmpl$6 = ["<button type=\"button\" id=\"", "\" class=\"k2b-choice-option\" role=\"option\"", ">", "<span><strong>", "</strong>", "</span></button>"],
14
14
  _tmpl$7 = ["<i", " aria-hidden=\"true\"></i>"],
15
15
  _tmpl$8 = ["<small>", "</small>"];
16
- import { createSignal, For, Show } from "solid-js";
16
+ import { createEffect, createSignal, For, Show } from "solid-js";
17
17
  import { createFieldMeta, Field, fieldControlAria } from "../internal/field";
18
18
  import { useUiMessages } from "../intl/messages";
19
19
  import { createChoiceLoader, createChoicePopover, nextEnabledChoiceIndex } from "./choice";
@@ -44,7 +44,7 @@ export function Combobox(props) {
44
44
  // chevron slot and stale results stay visible, so there is nothing to pad —
45
45
  // 150ms keeps typing responsive without hammering the loader.
46
46
  () => props.debounceMs ?? 150);
47
- const popover = createChoicePopover(() => Boolean(props.disabled));
47
+ const popover = createChoicePopover(() => Boolean(props.disabled || props.readOnly));
48
48
  const options = loader.options;
49
49
  const focusedOption = () => options()[focusedIndex()];
50
50
  const focus = index => {
@@ -56,7 +56,7 @@ export function Combobox(props) {
56
56
  /** `eager` loads the currently controlled query on open; typing supplies its
57
57
  * own query, so opening from `onInput` skips the duplicate request. */
58
58
  const open = (eager = true) => {
59
- if (props.disabled || popover.open()) return;
59
+ if (props.disabled || props.readOnly || popover.open()) return;
60
60
  popover.show();
61
61
  if (eager) loader.load(query(), true);
62
62
  focus(-1);
@@ -70,7 +70,15 @@ export function Combobox(props) {
70
70
  popover.hide();
71
71
  reset();
72
72
  };
73
+ createEffect(() => {
74
+ if (props.readOnly) {
75
+ popover.hide();
76
+ loader.cancel();
77
+ setFocusedIndex(-1);
78
+ }
79
+ });
73
80
  const select = option => {
81
+ if (props.disabled || props.readOnly) return;
74
82
  popover.hide();
75
83
  props.onSelect({
76
84
  id: option.value,
@@ -82,12 +90,15 @@ export function Combobox(props) {
82
90
  inputRef?.focus();
83
91
  };
84
92
  const onKeyDown = event => {
93
+ if (props.disabled || props.readOnly) return;
85
94
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
86
95
  event.preventDefault();
87
96
  if (!popover.open()) open();else focus(nextEnabledChoiceIndex(options(), focusedIndex(), event.key === "ArrowDown" ? 1 : -1));
88
- } else if (event.key === "Enter" && popover.open() && options()[focusedIndex()]) {
97
+ } else if (event.key === "Enter" && popover.open()) {
98
+ // While the results are open, Enter belongs to them: it never submits an enclosing form, even with no option active.
89
99
  event.preventDefault();
90
- select(options()[focusedIndex()]);
100
+ const option = options()[focusedIndex()];
101
+ if (option) select(option);
91
102
  } else if ((event.key === "Escape" || event.key === "Tab") && popover.open()) {
92
103
  // Escape is only ours while the list is open; otherwise it has to reach an
93
104
  // enclosing dialog or drawer.
@@ -130,6 +141,12 @@ export function Combobox(props) {
130
141
  get disabled() {
131
142
  return props.disabled;
132
143
  },
144
+ get readOnly() {
145
+ return props.readOnly;
146
+ },
147
+ get ["aria-readonly"]() {
148
+ return props.readOnly ? "true" : undefined;
149
+ },
133
150
  role: "combobox",
134
151
  "aria-autocomplete": "list",
135
152
  get ["aria-expanded"]() {
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["inputs/Combobox.tsx"],"sourcesContent":["import { createSignal, For, type JSX, Show } from \"solid-js\";\nimport { createFieldMeta, Field, fieldControlAria } from \"../internal/field\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { type ChoiceOption, createChoiceLoader, createChoicePopover, nextEnabledChoiceIndex } from \"./choice\";\nimport type { FieldProps, MaybeAccessor } from \"./field-contract\";\nimport { resolveMaybeAccessor } from \"./field-contract\";\nimport { useSelectMessages } from \"./select-messages\";\n\nexport type ComboboxOption = { id: string; label: string; description?: string; icon?: string };\nexport type ComboboxProps = FieldProps & {\n query?: MaybeAccessor<string>;\n onQueryChange?: (query: string) => void;\n placeholder?: string;\n fetchData: (query: string, signal: AbortSignal) => Promise<ComboboxOption[]>;\n onSelect: (option: ComboboxOption) => void;\n debounceMs?: number;\n};\n\nconst iconClass = (icon: string) => (icon.split(/\\s+/).includes(\"ti\") ? icon : `ti ${icon}`);\n\nexport function Combobox(props: ComboboxProps): JSX.Element {\n const messages = useUiMessages();\n const selectMessages = useSelectMessages();\n const meta = createFieldMeta(props.id);\n const listboxId = `${meta.controlId}-listbox`;\n const [localQuery, setLocalQuery] = createSignal(\"\");\n const query = () => resolveMaybeAccessor(props.query) ?? localQuery();\n const setQuery = (value: string) => {\n setLocalQuery(value);\n props.onQueryChange?.(value);\n };\n const error = () => resolveMaybeAccessor(props.error);\n const [focusedIndex, setFocusedIndex] = createSignal(-1);\n let inputRef: HTMLInputElement | undefined;\n let optionRefs: HTMLButtonElement[] = [];\n const loader = createChoiceLoader(\n () => async (value, signal) => (await props.fetchData(value, signal)).map((option) => ({ ...option, value: option.id })),\n // Cloud debounces at 200ms and then pads the response with a 200ms minimum\n // load time to stop the spinner flickering. The spinner here sits in the\n // chevron slot and stale results stay visible, so there is nothing to pad —\n // 150ms keeps typing responsive without hammering the loader.\n () => props.debounceMs ?? 150,\n );\n const popover = createChoicePopover(() => Boolean(props.disabled));\n const options = loader.options;\n const focusedOption = () => options()[focusedIndex()];\n const focus = (index: number) => {\n setFocusedIndex(index);\n optionRefs[index]?.scrollIntoView({ block: \"nearest\" });\n };\n /** `eager` loads the currently controlled query on open; typing supplies its\n * own query, so opening from `onInput` skips the duplicate request. */\n const open = (eager = true) => {\n if (props.disabled || popover.open()) return;\n popover.show();\n if (eager) loader.load(query(), true);\n focus(-1);\n };\n const reset = () => {\n loader.cancel();\n setQuery(\"\");\n focus(-1);\n };\n const close = () => {\n popover.hide();\n reset();\n };\n const select = (option: ChoiceOption<string>) => {\n popover.hide();\n props.onSelect({ id: option.value, label: option.label, description: option.description, icon: option.icon });\n reset();\n inputRef?.focus();\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n if (!popover.open()) open();\n else focus(nextEnabledChoiceIndex(options(), focusedIndex(), event.key === \"ArrowDown\" ? 1 : -1));\n } else if (event.key === \"Enter\" && popover.open() && options()[focusedIndex()]) {\n event.preventDefault();\n select(options()[focusedIndex()]!);\n } else if ((event.key === \"Escape\" || event.key === \"Tab\") && popover.open()) {\n // Escape is only ours while the list is open; otherwise it has to reach an\n // enclosing dialog or drawer.\n if (event.key === \"Escape\") event.preventDefault();\n close();\n }\n };\n\n return (\n <Field\n class={props.class}\n label={props.label}\n description={props.description}\n error={error()}\n meta={meta}\n required={props.required}\n disabled={props.disabled}\n >\n <div class=\"k2b-combobox\" data-invalid={error() ? \"true\" : undefined}>\n <div ref={popover.setTrigger} class=\"k2b-combobox__input\">\n <i class=\"ti ti-search\" aria-hidden=\"true\" />\n {/* A click, typing, or an arrow key opens the list. Focus alone (Tab, a dialog's initial focus) does not, so Escape still closes the enclosing dialog. */}\n <input\n ref={(element) => {\n inputRef = element;\n }}\n id={meta.controlId}\n type=\"text\"\n value={query()}\n placeholder={props.placeholder ?? messages().search}\n disabled={props.disabled}\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={popover.open()}\n aria-controls={listboxId}\n aria-activedescendant={focusedOption() ? `${listboxId}-${focusedIndex()}` : undefined}\n {...fieldControlAria(meta, props)}\n onClick={() => open()}\n onInput={(event) => {\n setQuery(event.currentTarget.value);\n open(false);\n loader.load(event.currentTarget.value);\n focus(-1);\n }}\n onKeyDown={onKeyDown}\n />\n <Show when={loader.loading()} fallback={<i class=\"ti ti-chevron-down\" aria-hidden=\"true\" />}>\n <i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\" />\n </Show>\n </div>\n <div ref={popover.setPopover} popover=\"manual\" class=\"k2b-choice-popover\">\n <div class=\"k2b-choice-options\" role=\"listbox\" id={listboxId}>\n <Show when={loader.error()}>\n {(message) => (\n <div class=\"k2b-choice-status\" data-tone=\"danger\">\n <span>{message()}</span>\n <button type=\"button\" onClick={loader.retry}>\n {messages().retry}\n </button>\n </div>\n )}\n </Show>\n <For\n each={loader.error() ? [] : options()}\n fallback={\n <div class=\"k2b-choice-status\">{query().length >= 2 ? selectMessages().noResultsFound : selectMessages().typeToSearch}</div>\n }\n >\n {(option, index) => (\n <button\n ref={(element) => (optionRefs[index()] = element)}\n type=\"button\"\n id={`${listboxId}-${index()}`}\n class=\"k2b-choice-option\"\n role=\"option\"\n aria-selected={index() === focusedIndex()}\n data-focused={index() === focusedIndex() ? \"true\" : undefined}\n onPointerMove={() => focus(index())}\n onPointerDown={(event) => event.preventDefault()}\n onClick={() => select(option)}\n >\n <Show when={option.icon}>{(icon) => <i class={iconClass(icon())} aria-hidden=\"true\" />}</Show>\n <span>\n <strong>{option.label}</strong>\n <Show when={option.description}>{(description) => <small>{description()}</small>}</Show>\n </span>\n </button>\n )}\n </For>\n </div>\n </div>\n </div>\n </Field>\n );\n}\n\nexport default Combobox;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["inputs/Combobox.tsx"],"sourcesContent":["import { createEffect, createSignal, For, type JSX, Show } from \"solid-js\";\nimport { createFieldMeta, Field, fieldControlAria } from \"../internal/field\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { type ChoiceOption, createChoiceLoader, createChoicePopover, nextEnabledChoiceIndex } from \"./choice\";\nimport type { FieldProps, MaybeAccessor } from \"./field-contract\";\nimport { resolveMaybeAccessor } from \"./field-contract\";\nimport { useSelectMessages } from \"./select-messages\";\n\nexport type ComboboxOption = { id: string; label: string; description?: string; icon?: string };\nexport type ComboboxProps = FieldProps & {\n readOnly?: boolean;\n query?: MaybeAccessor<string>;\n onQueryChange?: (query: string) => void;\n placeholder?: string;\n fetchData: (query: string, signal: AbortSignal) => Promise<ComboboxOption[]>;\n onSelect: (option: ComboboxOption) => void;\n debounceMs?: number;\n};\n\nconst iconClass = (icon: string) => (icon.split(/\\s+/).includes(\"ti\") ? icon : `ti ${icon}`);\n\nexport function Combobox(props: ComboboxProps): JSX.Element {\n const messages = useUiMessages();\n const selectMessages = useSelectMessages();\n const meta = createFieldMeta(props.id);\n const listboxId = `${meta.controlId}-listbox`;\n const [localQuery, setLocalQuery] = createSignal(\"\");\n const query = () => resolveMaybeAccessor(props.query) ?? localQuery();\n const setQuery = (value: string) => {\n setLocalQuery(value);\n props.onQueryChange?.(value);\n };\n const error = () => resolveMaybeAccessor(props.error);\n const [focusedIndex, setFocusedIndex] = createSignal(-1);\n let inputRef: HTMLInputElement | undefined;\n let optionRefs: HTMLButtonElement[] = [];\n const loader = createChoiceLoader(\n () => async (value, signal) => (await props.fetchData(value, signal)).map((option) => ({ ...option, value: option.id })),\n // Cloud debounces at 200ms and then pads the response with a 200ms minimum\n // load time to stop the spinner flickering. The spinner here sits in the\n // chevron slot and stale results stay visible, so there is nothing to pad —\n // 150ms keeps typing responsive without hammering the loader.\n () => props.debounceMs ?? 150,\n );\n const popover = createChoicePopover(() => Boolean(props.disabled || props.readOnly));\n const options = loader.options;\n const focusedOption = () => options()[focusedIndex()];\n const focus = (index: number) => {\n setFocusedIndex(index);\n optionRefs[index]?.scrollIntoView({ block: \"nearest\" });\n };\n /** `eager` loads the currently controlled query on open; typing supplies its\n * own query, so opening from `onInput` skips the duplicate request. */\n const open = (eager = true) => {\n if (props.disabled || props.readOnly || popover.open()) return;\n popover.show();\n if (eager) loader.load(query(), true);\n focus(-1);\n };\n const reset = () => {\n loader.cancel();\n setQuery(\"\");\n focus(-1);\n };\n const close = () => {\n popover.hide();\n reset();\n };\n createEffect(() => {\n if (props.readOnly) {\n popover.hide();\n loader.cancel();\n setFocusedIndex(-1);\n }\n });\n const select = (option: ChoiceOption<string>) => {\n if (props.disabled || props.readOnly) return;\n popover.hide();\n props.onSelect({ id: option.value, label: option.label, description: option.description, icon: option.icon });\n reset();\n inputRef?.focus();\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (props.disabled || props.readOnly) return;\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n if (!popover.open()) open();\n else focus(nextEnabledChoiceIndex(options(), focusedIndex(), event.key === \"ArrowDown\" ? 1 : -1));\n } else if (event.key === \"Enter\" && popover.open()) {\n // While the results are open, Enter belongs to them: it never submits an enclosing form, even with no option active.\n event.preventDefault();\n const option = options()[focusedIndex()];\n if (option) select(option);\n } else if ((event.key === \"Escape\" || event.key === \"Tab\") && popover.open()) {\n // Escape is only ours while the list is open; otherwise it has to reach an\n // enclosing dialog or drawer.\n if (event.key === \"Escape\") event.preventDefault();\n close();\n }\n };\n\n return (\n <Field\n class={props.class}\n label={props.label}\n description={props.description}\n error={error()}\n meta={meta}\n required={props.required}\n disabled={props.disabled}\n >\n <div class=\"k2b-combobox\" data-invalid={error() ? \"true\" : undefined}>\n <div ref={popover.setTrigger} class=\"k2b-combobox__input\">\n <i class=\"ti ti-search\" aria-hidden=\"true\" />\n {/* A click, typing, or an arrow key opens the list. Focus alone (Tab, a dialog's initial focus) does not, so Escape still closes the enclosing dialog. */}\n <input\n ref={(element) => {\n inputRef = element;\n }}\n id={meta.controlId}\n type=\"text\"\n value={query()}\n placeholder={props.placeholder ?? messages().search}\n disabled={props.disabled}\n readOnly={props.readOnly}\n aria-readonly={props.readOnly ? \"true\" : undefined}\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={popover.open()}\n aria-controls={listboxId}\n aria-activedescendant={focusedOption() ? `${listboxId}-${focusedIndex()}` : undefined}\n {...fieldControlAria(meta, props)}\n onClick={() => open()}\n onInput={(event) => {\n if (props.disabled || props.readOnly) {\n event.currentTarget.value = query();\n return;\n }\n setQuery(event.currentTarget.value);\n open(false);\n loader.load(event.currentTarget.value);\n focus(-1);\n }}\n onKeyDown={onKeyDown}\n />\n <Show when={loader.loading()} fallback={<i class=\"ti ti-chevron-down\" aria-hidden=\"true\" />}>\n <i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\" />\n </Show>\n </div>\n <div ref={popover.setPopover} popover=\"manual\" class=\"k2b-choice-popover\">\n <div class=\"k2b-choice-options\" role=\"listbox\" id={listboxId}>\n <Show when={loader.error()}>\n {(message) => (\n <div class=\"k2b-choice-status\" data-tone=\"danger\">\n <span>{message()}</span>\n <button type=\"button\" onClick={loader.retry}>\n {messages().retry}\n </button>\n </div>\n )}\n </Show>\n <For\n each={loader.error() ? [] : options()}\n fallback={\n <div class=\"k2b-choice-status\">{query().length >= 2 ? selectMessages().noResultsFound : selectMessages().typeToSearch}</div>\n }\n >\n {(option, index) => (\n <button\n ref={(element) => (optionRefs[index()] = element)}\n type=\"button\"\n id={`${listboxId}-${index()}`}\n class=\"k2b-choice-option\"\n role=\"option\"\n aria-selected={index() === focusedIndex()}\n data-focused={index() === focusedIndex() ? \"true\" : undefined}\n onPointerMove={() => focus(index())}\n onPointerDown={(event) => event.preventDefault()}\n onClick={() => select(option)}\n >\n <Show when={option.icon}>{(icon) => <i class={iconClass(icon())} aria-hidden=\"true\" />}</Show>\n <span>\n <strong>{option.label}</strong>\n <Show when={option.description}>{(description) => <small>{description()}</small>}</Show>\n </span>\n </button>\n )}\n </For>\n </div>\n </div>\n </div>\n </Field>\n );\n}\n\nexport default Combobox;\n"],"mappings":"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","ignoreList":[]}
@@ -6,6 +6,7 @@ var _tmpl$ = ["<p class=\"k2b-qr-scanner__note\">", "</p>"],
6
6
  _tmpl$2 = ["<div", "><div class=\"k2b-qr-scanner__preview\"", "><video muted playsinline", "></video></div><p role=\"status\">", "</p>", "</div>"];
7
7
  import { timed } from "@k2b/stdlib/solid";
8
8
  import { createSignal, onCleanup, onMount, Show } from "solid-js";
9
+ import { prefersReducedMotion } from "../motion";
9
10
  import { useQrMessages } from "./qr-messages";
10
11
 
11
12
  /**
@@ -176,7 +177,7 @@ export function QrScanner(props) {
176
177
  for (const animation of scanner?.$overlay?.getAnimations({
177
178
  subtree: true
178
179
  }) ?? []) {
179
- if (motion.matches) animation.pause();else animation.play();
180
+ if (prefersReducedMotion()) animation.pause();else animation.play();
180
181
  }
181
182
  };
182
183
  applyMotion();
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["inputs/QrScanner.tsx"],"sourcesContent":["import { timed } from \"@k2b/stdlib/solid\";\nimport type QrScannerEngine from \"qr-scanner\";\nimport { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { useQrMessages } from \"./qr-messages\";\n\n/**\n * Why the scanner could not start, as far as the browser tells: `\"denied\"` when camera access is off, `\"no-camera\"`\n * when the device has no camera, `\"in-use\"` when the camera is busy or could not start, most often because another app\n * holds it, and `\"unavailable\"` for anything else, including a scanning engine that cannot load.\n */\nexport type QrScannerError = \"denied\" | \"no-camera\" | \"in-use\" | \"unavailable\";\n\nexport type QrScannerProps = {\n /** Decoded text is untrusted. Return `true` to accept it and stop the camera, `false` to reject it and keep scanning. */\n onResult: (text: string) => boolean;\n /** The page was hidden or left; unmount the scanner. */\n onStop: () => void;\n /**\n * The camera or the scanning engine could not start; the scanner has stopped. Offer another way in. A frame that\n * cannot be decoded never ends here.\n */\n onError: (reason: QrScannerError) => void;\n /** What to point the camera at. Defaults to a generic QR code hint. */\n instructions?: string;\n class?: string;\n};\n\nconst sizes: MediaTrackConstraints[] = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}];\n/** The rear camera at a resolution that keeps small codes readable, then any camera: the order qr-scanner tries. */\nconst cameraRequests: MediaTrackConstraints[] = [...sizes.map((size) => ({ ...size, facingMode: { exact: \"environment\" } })), ...sizes];\n\nconst errorName = (error: unknown): string => (typeof error === \"object\" && error !== null && \"name\" in error ? String(error.name) : \"\");\n\nconst cameraFailure = (name: string): QrScannerError => {\n if (name === \"NotAllowedError\" || name === \"SecurityError\") return \"denied\";\n if (name === \"NotFoundError\" || name === \"OverconstrainedError\") return \"no-camera\";\n if (name === \"NotReadableError\") return \"in-use\";\n return \"unavailable\";\n};\n\n/** The facing a track reports, or else the one its label names, as qr-scanner reads it. */\nconst facingOf = (track: MediaStreamTrack | undefined): string | undefined => {\n if (!track) return undefined;\n const { facingMode } = track.getSettings();\n if (facingMode) return facingMode;\n if (/rear|back|environment/i.test(track.label)) return \"environment\";\n if (/front|user|face/i.test(track.label)) return \"user\";\n return undefined;\n};\n\n/**\n * Opens the camera here rather than in qr-scanner, which turns every failure into \"Camera not found.\". A refusal ends\n * the search, because asking again could show the permission prompt again. Any other failure may belong to the request,\n * such as a camera that cannot start at the larger size, so the next, looser request follows, as in qr-scanner. When\n * none succeeds, a camera that exists but fails outweighs a request no camera could meet. A closed scanner asks no more.\n */\nconst openCamera = async (closed: () => boolean): Promise<{ stream: MediaStream; mirrored: boolean } | { error: QrScannerError }> => {\n if (!navigator.mediaDevices) return { error: \"unavailable\" };\n let error: QrScannerError = \"no-camera\";\n for (const video of cameraRequests) {\n if (closed()) break;\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ video, audio: false });\n const facing = facingOf(stream.getVideoTracks()[0]);\n // Like qr-scanner, mirror a front camera, and a camera of unknown facing that the rear request did not find.\n return { stream, mirrored: facing ? facing === \"user\" : !video.facingMode };\n } catch (failure) {\n const reason = cameraFailure(errorName(failure));\n if (reason === \"denied\") return { error: reason };\n if (reason !== \"no-camera\") error = reason;\n }\n }\n return { error };\n};\n\nconst stopStream = (stream: MediaStream) => {\n for (const track of stream.getTracks()) track.stop();\n};\n\n/**\n * Scans a QR code with the rear camera. Each mount owns exactly one camera session, which stops when the scanner\n * unmounts, the page is hidden, or a result is accepted. The scanning engine loads only when the scanner mounts.\n * Decoded content is never logged.\n */\nexport function QrScanner(props: QrScannerProps): JSX.Element {\n const qrMessages = useQrMessages();\n const [starting, setStarting] = createSignal(true);\n const [invalid, setInvalid] = createSignal(false);\n let lastRejected: string | undefined;\n const resetRejected = timed.debounce(() => {\n lastRejected = undefined;\n }, 1000);\n const resetFeedback = timed.debounce(() => setInvalid(false), 1500);\n let video!: HTMLVideoElement;\n let scanner: QrScannerEngine | undefined;\n let closed = false;\n let cleanupMotion: (() => void) | undefined;\n const destroy = () => {\n closed = true;\n cleanupMotion?.();\n resetRejected.cancel();\n resetFeedback.cancel();\n // pause(true) stops the camera stream immediately.\n void scanner?.pause(true);\n scanner?.destroy();\n scanner = undefined;\n };\n // Once closed, the host has unmounted the scanner or heard why it stopped; later failures stay silent.\n const fail = (reason: QrScannerError) => {\n if (closed) return;\n destroy();\n props.onError(reason);\n };\n onMount(() => {\n const hidden = () => {\n if (document.visibilityState !== \"visible\") props.onStop();\n };\n const leaving = () => props.onStop();\n document.addEventListener(\"visibilitychange\", hidden);\n window.addEventListener(\"pagehide\", leaving);\n onCleanup(() => {\n destroy();\n document.removeEventListener(\"visibilitychange\", hidden);\n window.removeEventListener(\"pagehide\", leaving);\n });\n void (async () => {\n try {\n const { default: Engine } = await import(\"qr-scanner\");\n if (closed || document.visibilityState !== \"visible\") return;\n scanner = new Engine(\n video,\n (result) => {\n if (closed) return;\n // Rearm only after a full second without a decoded QR, not between individual frames.\n resetRejected.debouncedFn();\n if (result.data === lastRejected) return;\n if (props.onResult(result.data)) {\n destroy();\n return;\n }\n lastRejected = result.data;\n setInvalid(true);\n resetFeedback.debouncedFn();\n },\n {\n highlightScanRegion: true,\n // A frame that cannot be decoded never ends the session: the camera runs, and the next frame may read.\n // An engine error other than an empty frame, which the native BarcodeDetector reports as \"Scanner error:\n // No QR code found\", means the detector cannot read on this device. Fall back to the bundled worker for\n // the page through the flag qr-scanner 1.4.2 sets itself for \"not implemented\" and \"service unavailable\";\n // it swaps engines after this frame. Worker errors carry the same prefix; the flag changes nothing there.\n // qr-scanner reports frames only as strings: an Error means the engine itself did not load, such as a\n // decoder chunk gone after a deploy or a worker that cannot start, and no later frame can read. Never log\n // decoded content or frame data.\n onDecodeError: (error) => {\n if (typeof error !== \"string\") return fail(\"unavailable\");\n if (error.startsWith(\"Scanner error: \") && !error.endsWith(Engine.NO_QR_CODE_FOUND)) {\n Reflect.set(Engine, \"_disableBarcodeDetector\", true);\n }\n },\n },\n );\n const motion = matchMedia(\"(prefers-reduced-motion: reduce)\");\n const applyMotion = () => {\n for (const animation of scanner?.$overlay?.getAnimations({ subtree: true }) ?? []) {\n if (motion.matches) animation.pause();\n else animation.play();\n }\n };\n applyMotion();\n motion.addEventListener(\"change\", applyMotion);\n cleanupMotion = () => motion.removeEventListener(\"change\", applyMotion);\n const camera = await openCamera(() => closed);\n if (\"error\" in camera) return fail(camera.error);\n if (closed) return stopStream(camera.stream);\n // qr-scanner plays a stream that is already set instead of opening its own.\n video.srcObject = camera.stream;\n video.style.transform = camera.mirrored ? \"scaleX(-1)\" : \"\";\n await scanner.start();\n if (!closed) setStarting(false);\n } catch {\n fail(\"unavailable\");\n }\n })();\n });\n return (\n <div class={props.class ? `k2b-qr-scanner ${props.class}` : \"k2b-qr-scanner\"}>\n <div class=\"k2b-qr-scanner__preview\" data-invalid={invalid() || undefined}>\n {/* Live camera preview contains no prerecorded speech or audio. */}\n <video ref={video} muted playsinline aria-label={qrMessages().qrCameraPreview} />\n </div>\n <p role=\"status\">{starting() ? qrMessages().qrCameraStarting : (props.instructions ?? qrMessages().qrCameraInstructions)}</p>\n <Show when={!starting()}>\n <p class=\"k2b-qr-scanner__note\">{qrMessages().qrCameraPrivacy}</p>\n </Show>\n </div>\n );\n}\n\nexport default QrScanner;\n"],"mappings":";;;;;;AAAA,SAAS,KAAK,QAAQ,mBAAmB;AAEzC,SAAS,YAAY,EAAY,SAAS,EAAE,OAAO,EAAE,IAAI,QAAQ,UAAU;AAC3E,SAAS,aAAa,QAAQ,eAAe;;AAE7C;AACA;AACA;AACA;AACA;;AAkBA,MAAM,KAA8B,GAAG,CAAC;EAAE,KAAK,EAAE;IAAE,GAAG,EAAE;EAAK;AAAE,CAAC,EAAE;EAAE,KAAK,EAAE;IAAE,GAAG,EAAE;EAAI;AAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9F;AACA,MAAM,cAAuC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAE,IAAI,KAAM;EAAE,GAAG,IAAI;EAAE,UAAU,EAAE;IAAE,KAAK,EAAE;EAAc;AAAE,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC;AAEvI,MAAM,SAAS,GAAI,KAAc,IAAc,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAG;AAExI,MAAM,aAAa,GAAI,IAAY,IAAqB;EACtD,IAAI,IAAI,KAAK,iBAAiB,IAAI,IAAI,KAAK,eAAe,EAAE,OAAO,QAAQ;EAC3E,IAAI,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,sBAAsB,EAAE,OAAO,WAAW;EACnF,IAAI,IAAI,KAAK,kBAAkB,EAAE,OAAO,QAAQ;EAChD,OAAO,aAAa;AACtB,CAAC;;AAED;AACA,MAAM,QAAQ,GAAI,KAAmC,IAAyB;EAC5E,IAAI,CAAC,KAAK,EAAE,OAAO,SAAS;EAC5B,MAAM;IAAE;EAAW,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;EAC1C,IAAI,UAAU,EAAE,OAAO,UAAU;EACjC,IAAI,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,aAAa;EACpE,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,MAAM;EACvD,OAAO,SAAS;AAClB,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,UAAU,GAAG,MAAO,MAAqB,IAAsF;EACnI,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO;IAAE,KAAK,EAAE;EAAc,CAAC;EAC5D,IAAI,KAAqB,GAAG,WAAW;EACvC,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE;IAClC,IAAI,MAAM,CAAC,CAAC,EAAE;IACd,IAAI;MACF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC;QAAE,KAAK;QAAE,KAAK,EAAE;MAAM,CAAC,CAAC;MACjF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;MACnD;MACA,OAAO;QAAE,MAAM;QAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,CAAC,KAAK,CAAC;MAAW,CAAC;IAC7E,CAAC,CAAC,OAAO,OAAO,EAAE;MAChB,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;MAChD,IAAI,MAAM,KAAK,QAAQ,EAAE,OAAO;QAAE,KAAK,EAAE;MAAO,CAAC;MACjD,IAAI,MAAM,KAAK,WAAW,EAAE,KAAK,GAAG,MAAM;IAC5C;EACF;EACA,OAAO;IAAE;EAAM,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,GAAI,MAAmB,IAAK;EAC1C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS,SAAS,CAAC,KAAqB,EAAe;EAC5D,MAAM,UAAU,GAAG,aAAa,CAAC,CAAC;EAClC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC;EAClD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC;EACjD,IAAI,YAAgC;EACpC,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM;IACzC,YAAY,GAAG,SAAS;EAC1B,CAAC,EAAE,IAAI,CAAC;EACR,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;EACnE,IAAI,KAAwB;EAC5B,IAAI,OAAoC;EACxC,IAAI,MAAM,GAAG,KAAK;EAClB,IAAI,aAAuC;EAC3C,MAAM,OAAO,GAAG,MAAM;IACpB,MAAM,GAAG,IAAI;IACb,aAAa,GAAG,CAAC;IACjB,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB;IACA,KAAK,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC,CAAC;IAClB,OAAO,GAAG,SAAS;EACrB,CAAC;EACD;EACA,MAAM,IAAI,GAAI,MAAsB,IAAK;IACvC,IAAI,MAAM,EAAE;IACZ,OAAO,CAAC,CAAC;IACT,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;EACvB,CAAC;EACD,OAAO,CAAC,MAAM;IACZ,MAAM,MAAM,GAAG,MAAM;MACnB,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACrD,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC;IAC5C,SAAS,CAAC,MAAM;MACd,OAAO,CAAC,CAAC;MACT,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,MAAM,CAAC;MACxD,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC;IACjD,CAAC,CAAC;IACF,KAAK,CAAC,YAAY;MAChB,IAAI;QACF,MAAM;UAAE,OAAO,EAAE;QAAO,CAAC,GAAG,MAAM,OAAO,YAAY,CAAC;QACtD,IAAI,MAAM,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE;QACtD,OAAO,GAAG,IAAI,MAAM,CAClB,KAAK,EACJ,MAAM,IAAK;UACV,IAAI,MAAM,EAAE;UACZ;UACA,aAAa,CAAC,WAAW,CAAC,CAAC;UAC3B,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE;UAClC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;YAC/B,OAAO,CAAC,CAAC;YACT;UACF;UACA,YAAY,GAAG,MAAM,CAAC,IAAI;UAC1B,UAAU,CAAC,IAAI,CAAC;UAChB,aAAa,CAAC,WAAW,CAAC,CAAC;QAC7B,CAAC,EACD;UACE,mBAAmB,EAAE,IAAI;UACzB;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA,aAAa,EAAG,KAAK,IAAK;YACxB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,OAAO,IAAI,CAAC,aAAa,CAAC;YACzD,IAAI,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE;cACnF,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,yBAAyB,EAAE,IAAI,CAAC;YACtD;UACF;QACF,CACF,CAAC;QACD,MAAM,MAAM,GAAG,UAAU,CAAC,kCAAkC,CAAC;QAC7D,MAAM,WAAW,GAAG,MAAM;UACxB,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC;YAAE,OAAO,EAAE;UAAK,CAAC,CAAC,IAAI,EAAE,EAAE;YACjF,IAAI,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,KACjC,SAAS,CAAC,IAAI,CAAC,CAAC;UACvB;QACF,CAAC;QACD,WAAW,CAAC,CAAC;QACb,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,WAAW,CAAC;QAC9C,aAAa,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,WAAW,CAAC;QACvE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,MAAM,CAAC;QAC7C,IAAI,OAAO,IAAI,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAChD,IAAI,MAAM,EAAE,OAAO,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;QAC5C;QACA,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC,MAAM;QAC/B,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC,QAAQ,GAAG,YAAY,GAAG,EAAE;QAC3D,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QACrB,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC;MACjC,CAAC,CAAC,MAAM;QACN,IAAI,CAAC,aAAa,CAAC;MACrB;IACF,CAAC,EAAE,CAAC;EACN,CAAC,CAAC;EACF,8CACc,KAAK,CAAC,KAAK,GAAG,2BAAkB,KAAK,CAAC,KAAK,SAAE,GAAG,gBAAgB,kDACvB,OAAO,CAAC,CAAC,IAAI,SAAS,uDAEtB,UAAU,CAAC,CAAC,CAAC,eAAe,iBAE7D,QAAQ,CAAC,CAAC,YAAG,UAAU,CAAC,CAAC,CAAC,gBAAgB,aAAI,KAAK,CAAC,YAAY,IAAI,UAAU,CAAC,CAAC,CAAC,oBAAoB,CAAC,6BACvH,IAAI;IAAA,IAAC,IAAI;MAAA,OAAE,CAAC,QAAQ,CAAC,CAAC;IAAA;IAAA;MAAA,8BACY,UAAU,CAAC,CAAC,CAAC,eAAe;IAAA;EAAA;AAIrE;AAEA,eAAe,SAAS","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["inputs/QrScanner.tsx"],"sourcesContent":["import { timed } from \"@k2b/stdlib/solid\";\nimport type QrScannerEngine from \"qr-scanner\";\nimport { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { prefersReducedMotion } from \"../motion\";\nimport { useQrMessages } from \"./qr-messages\";\n\n/**\n * Why the scanner could not start, as far as the browser tells: `\"denied\"` when camera access is off, `\"no-camera\"`\n * when the device has no camera, `\"in-use\"` when the camera is busy or could not start, most often because another app\n * holds it, and `\"unavailable\"` for anything else, including a scanning engine that cannot load.\n */\nexport type QrScannerError = \"denied\" | \"no-camera\" | \"in-use\" | \"unavailable\";\n\nexport type QrScannerProps = {\n /** Decoded text is untrusted. Return `true` to accept it and stop the camera, `false` to reject it and keep scanning. */\n onResult: (text: string) => boolean;\n /** The page was hidden or left; unmount the scanner. */\n onStop: () => void;\n /**\n * The camera or the scanning engine could not start; the scanner has stopped. Offer another way in. A frame that\n * cannot be decoded never ends here.\n */\n onError: (reason: QrScannerError) => void;\n /** What to point the camera at. Defaults to a generic QR code hint. */\n instructions?: string;\n class?: string;\n};\n\nconst sizes: MediaTrackConstraints[] = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}];\n/** The rear camera at a resolution that keeps small codes readable, then any camera: the order qr-scanner tries. */\nconst cameraRequests: MediaTrackConstraints[] = [...sizes.map((size) => ({ ...size, facingMode: { exact: \"environment\" } })), ...sizes];\n\nconst errorName = (error: unknown): string => (typeof error === \"object\" && error !== null && \"name\" in error ? String(error.name) : \"\");\n\nconst cameraFailure = (name: string): QrScannerError => {\n if (name === \"NotAllowedError\" || name === \"SecurityError\") return \"denied\";\n if (name === \"NotFoundError\" || name === \"OverconstrainedError\") return \"no-camera\";\n if (name === \"NotReadableError\") return \"in-use\";\n return \"unavailable\";\n};\n\n/** The facing a track reports, or else the one its label names, as qr-scanner reads it. */\nconst facingOf = (track: MediaStreamTrack | undefined): string | undefined => {\n if (!track) return undefined;\n const { facingMode } = track.getSettings();\n if (facingMode) return facingMode;\n if (/rear|back|environment/i.test(track.label)) return \"environment\";\n if (/front|user|face/i.test(track.label)) return \"user\";\n return undefined;\n};\n\n/**\n * Opens the camera here rather than in qr-scanner, which turns every failure into \"Camera not found.\". A refusal ends\n * the search, because asking again could show the permission prompt again. Any other failure may belong to the request,\n * such as a camera that cannot start at the larger size, so the next, looser request follows, as in qr-scanner. When\n * none succeeds, a camera that exists but fails outweighs a request no camera could meet. A closed scanner asks no more.\n */\nconst openCamera = async (closed: () => boolean): Promise<{ stream: MediaStream; mirrored: boolean } | { error: QrScannerError }> => {\n if (!navigator.mediaDevices) return { error: \"unavailable\" };\n let error: QrScannerError = \"no-camera\";\n for (const video of cameraRequests) {\n if (closed()) break;\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ video, audio: false });\n const facing = facingOf(stream.getVideoTracks()[0]);\n // Like qr-scanner, mirror a front camera, and a camera of unknown facing that the rear request did not find.\n return { stream, mirrored: facing ? facing === \"user\" : !video.facingMode };\n } catch (failure) {\n const reason = cameraFailure(errorName(failure));\n if (reason === \"denied\") return { error: reason };\n if (reason !== \"no-camera\") error = reason;\n }\n }\n return { error };\n};\n\nconst stopStream = (stream: MediaStream) => {\n for (const track of stream.getTracks()) track.stop();\n};\n\n/**\n * Scans a QR code with the rear camera. Each mount owns exactly one camera session, which stops when the scanner\n * unmounts, the page is hidden, or a result is accepted. The scanning engine loads only when the scanner mounts.\n * Decoded content is never logged.\n */\nexport function QrScanner(props: QrScannerProps): JSX.Element {\n const qrMessages = useQrMessages();\n const [starting, setStarting] = createSignal(true);\n const [invalid, setInvalid] = createSignal(false);\n let lastRejected: string | undefined;\n const resetRejected = timed.debounce(() => {\n lastRejected = undefined;\n }, 1000);\n const resetFeedback = timed.debounce(() => setInvalid(false), 1500);\n let video!: HTMLVideoElement;\n let scanner: QrScannerEngine | undefined;\n let closed = false;\n let cleanupMotion: (() => void) | undefined;\n const destroy = () => {\n closed = true;\n cleanupMotion?.();\n resetRejected.cancel();\n resetFeedback.cancel();\n // pause(true) stops the camera stream immediately.\n void scanner?.pause(true);\n scanner?.destroy();\n scanner = undefined;\n };\n // Once closed, the host has unmounted the scanner or heard why it stopped; later failures stay silent.\n const fail = (reason: QrScannerError) => {\n if (closed) return;\n destroy();\n props.onError(reason);\n };\n onMount(() => {\n const hidden = () => {\n if (document.visibilityState !== \"visible\") props.onStop();\n };\n const leaving = () => props.onStop();\n document.addEventListener(\"visibilitychange\", hidden);\n window.addEventListener(\"pagehide\", leaving);\n onCleanup(() => {\n destroy();\n document.removeEventListener(\"visibilitychange\", hidden);\n window.removeEventListener(\"pagehide\", leaving);\n });\n void (async () => {\n try {\n const { default: Engine } = await import(\"qr-scanner\");\n if (closed || document.visibilityState !== \"visible\") return;\n scanner = new Engine(\n video,\n (result) => {\n if (closed) return;\n // Rearm only after a full second without a decoded QR, not between individual frames.\n resetRejected.debouncedFn();\n if (result.data === lastRejected) return;\n if (props.onResult(result.data)) {\n destroy();\n return;\n }\n lastRejected = result.data;\n setInvalid(true);\n resetFeedback.debouncedFn();\n },\n {\n highlightScanRegion: true,\n // A frame that cannot be decoded never ends the session: the camera runs, and the next frame may read.\n // An engine error other than an empty frame, which the native BarcodeDetector reports as \"Scanner error:\n // No QR code found\", means the detector cannot read on this device. Fall back to the bundled worker for\n // the page through the flag qr-scanner 1.4.2 sets itself for \"not implemented\" and \"service unavailable\";\n // it swaps engines after this frame. Worker errors carry the same prefix; the flag changes nothing there.\n // qr-scanner reports frames only as strings: an Error means the engine itself did not load, such as a\n // decoder chunk gone after a deploy or a worker that cannot start, and no later frame can read. Never log\n // decoded content or frame data.\n onDecodeError: (error) => {\n if (typeof error !== \"string\") return fail(\"unavailable\");\n if (error.startsWith(\"Scanner error: \") && !error.endsWith(Engine.NO_QR_CODE_FOUND)) {\n Reflect.set(Engine, \"_disableBarcodeDetector\", true);\n }\n },\n },\n );\n const motion = matchMedia(\"(prefers-reduced-motion: reduce)\");\n const applyMotion = () => {\n for (const animation of scanner?.$overlay?.getAnimations({ subtree: true }) ?? []) {\n if (prefersReducedMotion()) animation.pause();\n else animation.play();\n }\n };\n applyMotion();\n motion.addEventListener(\"change\", applyMotion);\n cleanupMotion = () => motion.removeEventListener(\"change\", applyMotion);\n const camera = await openCamera(() => closed);\n if (\"error\" in camera) return fail(camera.error);\n if (closed) return stopStream(camera.stream);\n // qr-scanner plays a stream that is already set instead of opening its own.\n video.srcObject = camera.stream;\n video.style.transform = camera.mirrored ? \"scaleX(-1)\" : \"\";\n await scanner.start();\n if (!closed) setStarting(false);\n } catch {\n fail(\"unavailable\");\n }\n })();\n });\n return (\n <div class={props.class ? `k2b-qr-scanner ${props.class}` : \"k2b-qr-scanner\"}>\n <div class=\"k2b-qr-scanner__preview\" data-invalid={invalid() || undefined}>\n {/* Live camera preview contains no prerecorded speech or audio. */}\n <video ref={video} muted playsinline aria-label={qrMessages().qrCameraPreview} />\n </div>\n <p role=\"status\">{starting() ? qrMessages().qrCameraStarting : (props.instructions ?? qrMessages().qrCameraInstructions)}</p>\n <Show when={!starting()}>\n <p class=\"k2b-qr-scanner__note\">{qrMessages().qrCameraPrivacy}</p>\n </Show>\n </div>\n );\n}\n\nexport default QrScanner;\n"],"mappings":"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","ignoreList":[]}
@@ -5,11 +5,10 @@ import { escape as _$escape } from "solid-js/web";
5
5
  import { createComponent as _$createComponent } from "solid-js/web";
6
6
  import { memo as _$memo } from "solid-js/web";
7
7
  var _tmpl$ = ["<span", ">", "</span>"],
8
- _tmpl$2 = "<i class=\"ti ti-chevron-down\" aria-hidden=\"true\"></i>",
9
- _tmpl$3 = ["<i", " aria-hidden=\"true\"></i>"],
10
- _tmpl$4 = ["<img", " alt>"],
11
- _tmpl$5 = ["<input type=\"hidden\"", ">"];
12
- import { createMemo, Show } from "solid-js";
8
+ _tmpl$2 = ["<i", " aria-hidden=\"true\"></i>"],
9
+ _tmpl$3 = ["<img", " alt>"],
10
+ _tmpl$4 = ["<input type=\"hidden\"", ">"];
11
+ import { createComputed, createEffect, createMemo, on, Show } from "solid-js";
13
12
  import { Dropdown } from "../actions/Dropdown";
14
13
  import { createFieldMeta, Field, fieldControlAria } from "../internal/field";
15
14
  import { commitFieldValue, resolveMaybeAccessor } from "./field-contract";
@@ -19,6 +18,25 @@ export function SelectChip(props) {
19
18
  const meta = createFieldMeta(props.id);
20
19
  const value = () => resolveMaybeAccessor(props.value);
21
20
  const selected = () => props.options.find(option => option.value === value());
21
+ const disabled = createMemo(() => Boolean(props.disabled || props.loading));
22
+ // A disabled button loses focus, so a chip disabled while a change saves (its own or one elsewhere) would leave
23
+ // keyboard focus on the page. Focus returns to the chip once it is enabled again, unless it has moved on
24
+ // meanwhile. The check runs as a computation, before the DOM update disables the button.
25
+ let trigger;
26
+ let refocus = false;
27
+ createComputed(on(disabled, isDisabled => {
28
+ if (isDisabled) refocus = trigger !== undefined && document.activeElement === trigger;
29
+ }, {
30
+ defer: true
31
+ }));
32
+ createEffect(on(disabled, isDisabled => {
33
+ if (isDisabled || !refocus) return;
34
+ refocus = false;
35
+ const active = document.activeElement;
36
+ if (trigger?.isConnected && (active === null || active === document.body)) trigger.focus();
37
+ }, {
38
+ defer: true
39
+ }));
22
40
  const triggerLabel = () => props["aria-label"] ?? (typeof props.label === "string" ? props.label : selectMessages().chooseOption);
23
41
  const items = createMemo(() => props.options.map(option => ({
24
42
  action: () => commitFieldValue(props, option.value),
@@ -49,7 +67,7 @@ export function SelectChip(props) {
49
67
  return props.required;
50
68
  },
51
69
  get disabled() {
52
- return props.disabled;
70
+ return disabled();
53
71
  },
54
72
  get children() {
55
73
  return [_$createComponent(Dropdown.Root, {
@@ -78,11 +96,17 @@ export function SelectChip(props) {
78
96
  return props.iconOnly ? undefined : "k2b-select-chip";
79
97
  },
80
98
  get disabled() {
81
- return props.disabled;
99
+ return disabled();
100
+ },
101
+ get ["aria-busy"]() {
102
+ return props.loading ? "true" : undefined;
82
103
  },
83
104
  get iconOnly() {
84
105
  return props.iconOnly;
85
106
  },
107
+ get loading() {
108
+ return _$memo(() => !!props.iconOnly)() ? props.loading : undefined;
109
+ },
86
110
  get label() {
87
111
  return triggerLabel();
88
112
  },
@@ -103,16 +127,16 @@ export function SelectChip(props) {
103
127
  get when() {
104
128
  return selected()?.icon ?? props.icon;
105
129
  },
106
- children: icon => _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(icon(), true), false))
130
+ children: icon => _$ssr(_tmpl$2, _$ssrAttribute("class", _$escape(icon(), true), false))
107
131
  });
108
132
  },
109
- children: image => _$ssr(_tmpl$4, _$ssrAttribute("src", _$escape(image(), true), false))
133
+ children: image => _$ssr(_tmpl$3, _$ssrAttribute("src", _$escape(image(), true), false))
110
134
  }), _$createComponent(Show, {
111
135
  get when() {
112
136
  return !props.iconOnly;
113
137
  },
114
138
  get children() {
115
- return [_$ssr(_tmpl$, _$ssrAttribute("data-placeholder", selected() ? _$escape(undefined, true) : "true", false), _$escape(selected()?.label ?? props.placeholder ?? "")), _$ssr(_tmpl$2)];
139
+ return [_$ssr(_tmpl$, _$ssrAttribute("data-placeholder", selected() ? _$escape(undefined, true) : "true", false), _$escape(selected()?.label ?? props.placeholder ?? "")), _$ssr(_tmpl$2, _$ssrAttribute("class", props.loading ? "ti ti-loader-2 k2b-spin" : "ti ti-chevron-down", false))];
116
140
  }
117
141
  })];
118
142
  }
@@ -122,7 +146,7 @@ export function SelectChip(props) {
122
146
  get when() {
123
147
  return props.name;
124
148
  },
125
- children: name => _$ssr(_tmpl$5, _$ssrAttribute("name", _$escape(name(), true), false) + _$ssrAttribute("value", _$escape(value(), true), false))
149
+ children: name => _$ssr(_tmpl$4, _$ssrAttribute("name", _$escape(name(), true), false) + _$ssrAttribute("value", _$escape(value(), true), false))
126
150
  })];
127
151
  }
128
152
  });
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["inputs/SelectChip.tsx"],"sourcesContent":["import { createMemo, type JSX, Show } from \"solid-js\";\nimport type { ButtonSize } from \"../actions/Button\";\nimport { Dropdown, type DropdownItem, type DropdownPosition } from \"../actions/Dropdown\";\nimport { createFieldMeta, Field, fieldControlAria } from \"../internal/field\";\nimport type { MaybeAccessor, ValueFieldProps } from \"./field-contract\";\nimport { commitFieldValue, resolveMaybeAccessor } from \"./field-contract\";\nimport { useSelectMessages } from \"./select-messages\";\n\nexport type SelectChipOption<T extends string | number = string> = {\n value: T;\n label: string;\n description?: string;\n icon?: string;\n image?: string;\n disabled?: boolean;\n};\n\nexport type SelectChipProps<T extends string | number = string> = ValueFieldProps<T> & {\n value: MaybeAccessor<T>;\n options: SelectChipOption<T>[];\n icon?: string;\n iconOnly?: boolean;\n size?: ButtonSize;\n placeholder?: string;\n position?: DropdownPosition;\n /** Exact menu width as a CSS length. Without it the menu sizes to its options, at least `10rem`. */\n menuWidth?: string;\n name?: string;\n};\n\nexport function SelectChip<T extends string | number = string>(props: SelectChipProps<T>): JSX.Element {\n const selectMessages = useSelectMessages();\n const meta = createFieldMeta(props.id);\n const value = () => resolveMaybeAccessor(props.value)!;\n const selected = () => props.options.find((option) => option.value === value());\n const triggerLabel = () => props[\"aria-label\"] ?? (typeof props.label === \"string\" ? props.label : selectMessages().chooseOption);\n const items = createMemo<DropdownItem[]>(() =>\n props.options.map((option) => ({\n action: () => commitFieldValue(props, option.value),\n checked: () => option.value === value(),\n choice: \"radio\" as const,\n class: \"k2b-select-chip__option\",\n description: option.description,\n disabled: option.disabled,\n icon: option.icon,\n image: option.image,\n label: option.label,\n })),\n );\n\n return (\n <Field\n class={props.class}\n label={props.label}\n description={props.description}\n error={props.error}\n meta={meta}\n required={props.required}\n disabled={props.disabled}\n >\n <Dropdown.Root\n position={props.position ?? \"bottom-right\"}\n /* Cloud opens this menu at `w-40`; the stylesheet keeps that as its floor. */\n width={props.menuWidth}\n menuClass=\"k2b-select-chip__menu\"\n label={triggerLabel()}\n items={items()}\n >\n <Dropdown.Trigger\n appearance={props.iconOnly ? \"button\" : \"plain\"}\n id={meta.controlId}\n class={props.iconOnly ? undefined : \"k2b-select-chip\"}\n disabled={props.disabled}\n iconOnly={props.iconOnly}\n label={triggerLabel()}\n size={props.iconOnly ? props.size : undefined}\n title={props.iconOnly ? triggerLabel() : undefined}\n {...fieldControlAria(meta, props)}\n >\n <Show\n when={selected()?.image}\n fallback={<Show when={selected()?.icon ?? props.icon}>{(icon) => <i class={icon()} aria-hidden=\"true\" />}</Show>}\n >\n {(image) => <img src={image()} alt=\"\" />}\n </Show>\n <Show when={!props.iconOnly}>\n <span data-placeholder={selected() ? undefined : \"true\"}>{selected()?.label ?? props.placeholder ?? \"\"}</span>\n <i class=\"ti ti-chevron-down\" aria-hidden=\"true\" />\n </Show>\n </Dropdown.Trigger>\n </Dropdown.Root>\n <Show when={props.name}>{(name) => <input type=\"hidden\" name={name()} value={value()} />}</Show>\n </Field>\n );\n}\n\nexport default SelectChip;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["inputs/SelectChip.tsx"],"sourcesContent":["import { createComputed, createEffect, createMemo, type JSX, on, Show } from \"solid-js\";\nimport type { ButtonSize } from \"../actions/Button\";\nimport { Dropdown, type DropdownItem, type DropdownPosition } from \"../actions/Dropdown\";\nimport { createFieldMeta, Field, fieldControlAria } from \"../internal/field\";\nimport type { MaybeAccessor, ValueFieldProps } from \"./field-contract\";\nimport { commitFieldValue, resolveMaybeAccessor } from \"./field-contract\";\nimport { useSelectMessages } from \"./select-messages\";\n\nexport type SelectChipOption<T extends string | number = string> = {\n value: T;\n label: string;\n description?: string;\n icon?: string;\n image?: string;\n disabled?: boolean;\n};\n\nexport type SelectChipProps<T extends string | number = string> = ValueFieldProps<T> & {\n value: MaybeAccessor<T>;\n options: SelectChipOption<T>[];\n icon?: string;\n iconOnly?: boolean;\n size?: ButtonSize;\n placeholder?: string;\n position?: DropdownPosition;\n /** Exact menu width as a CSS length. Without it the menu sizes to its options, at least `10rem`. */\n menuWidth?: string;\n name?: string;\n /** Shows that a change from this chip is saving: the chip stays in place with\n * its size, cannot be opened, is marked busy, and shows a spinner where its\n * chevron (or, icon-only, its icon) is. */\n loading?: boolean;\n};\n\nexport function SelectChip<T extends string | number = string>(props: SelectChipProps<T>): JSX.Element {\n const selectMessages = useSelectMessages();\n const meta = createFieldMeta(props.id);\n const value = () => resolveMaybeAccessor(props.value)!;\n const selected = () => props.options.find((option) => option.value === value());\n const disabled = createMemo(() => Boolean(props.disabled || props.loading));\n // A disabled button loses focus, so a chip disabled while a change saves (its own or one elsewhere) would leave\n // keyboard focus on the page. Focus returns to the chip once it is enabled again, unless it has moved on\n // meanwhile. The check runs as a computation, before the DOM update disables the button.\n let trigger: HTMLButtonElement | undefined;\n let refocus = false;\n createComputed(\n on(\n disabled,\n (isDisabled) => {\n if (isDisabled) refocus = trigger !== undefined && document.activeElement === trigger;\n },\n { defer: true },\n ),\n );\n createEffect(\n on(\n disabled,\n (isDisabled) => {\n if (isDisabled || !refocus) return;\n refocus = false;\n const active = document.activeElement;\n if (trigger?.isConnected && (active === null || active === document.body)) trigger.focus();\n },\n { defer: true },\n ),\n );\n const triggerLabel = () => props[\"aria-label\"] ?? (typeof props.label === \"string\" ? props.label : selectMessages().chooseOption);\n const items = createMemo<DropdownItem[]>(() =>\n props.options.map((option) => ({\n action: () => commitFieldValue(props, option.value),\n checked: () => option.value === value(),\n choice: \"radio\" as const,\n class: \"k2b-select-chip__option\",\n description: option.description,\n disabled: option.disabled,\n icon: option.icon,\n image: option.image,\n label: option.label,\n })),\n );\n\n return (\n <Field\n class={props.class}\n label={props.label}\n description={props.description}\n error={props.error}\n meta={meta}\n required={props.required}\n disabled={disabled()}\n >\n <Dropdown.Root\n position={props.position ?? \"bottom-right\"}\n /* Cloud opens this menu at `w-40`; the stylesheet keeps that as its floor. */\n width={props.menuWidth}\n menuClass=\"k2b-select-chip__menu\"\n label={triggerLabel()}\n items={items()}\n >\n <Dropdown.Trigger\n appearance={props.iconOnly ? \"button\" : \"plain\"}\n id={meta.controlId}\n ref={(element: HTMLButtonElement) => {\n trigger = element;\n }}\n class={props.iconOnly ? undefined : \"k2b-select-chip\"}\n disabled={disabled()}\n aria-busy={props.loading ? \"true\" : undefined}\n iconOnly={props.iconOnly}\n loading={props.iconOnly ? props.loading : undefined}\n label={triggerLabel()}\n size={props.iconOnly ? props.size : undefined}\n title={props.iconOnly ? triggerLabel() : undefined}\n {...fieldControlAria(meta, props)}\n >\n <Show\n when={selected()?.image}\n fallback={<Show when={selected()?.icon ?? props.icon}>{(icon) => <i class={icon()} aria-hidden=\"true\" />}</Show>}\n >\n {(image) => <img src={image()} alt=\"\" />}\n </Show>\n <Show when={!props.iconOnly}>\n <span data-placeholder={selected() ? undefined : \"true\"}>{selected()?.label ?? props.placeholder ?? \"\"}</span>\n {/* Both glyphs are one icon wide, so a saving chip keeps its size. */}\n <i class={props.loading ? \"ti ti-loader-2 k2b-spin\" : \"ti ti-chevron-down\"} aria-hidden=\"true\" />\n </Show>\n </Dropdown.Trigger>\n </Dropdown.Root>\n <Show when={props.name}>{(name) => <input type=\"hidden\" name={name()} value={value()} />}</Show>\n </Field>\n );\n}\n\nexport default SelectChip;\n"],"mappings":"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","ignoreList":[]}