@k2b/ui 0.24.0 → 0.26.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 (166) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/actions/Dropdown.js +67 -52
  3. package/dist/browser/actions/Dropdown.js.map +1 -1
  4. package/dist/browser/actions/GestureMenu.js +5 -5
  5. package/dist/browser/actions/GestureMenu.js.map +1 -1
  6. package/dist/browser/actions/SpotlightSearch.js +13 -6
  7. package/dist/browser/actions/SpotlightSearch.js.map +1 -1
  8. package/dist/browser/actions/Toolbar.js +75 -1
  9. package/dist/browser/actions/Toolbar.js.map +1 -1
  10. package/dist/browser/content/Calendar.js +1539 -911
  11. package/dist/browser/content/Calendar.js.map +1 -1
  12. package/dist/browser/content/CalendarDayView.js +5 -4
  13. package/dist/browser/content/CalendarDayView.js.map +1 -1
  14. package/dist/browser/content/CalendarWeekView.js +5 -4
  15. package/dist/browser/content/CalendarWeekView.js.map +1 -1
  16. package/dist/browser/content/Kbd.js +51 -0
  17. package/dist/browser/content/Kbd.js.map +1 -0
  18. package/dist/browser/content/LabelChip.js +38 -0
  19. package/dist/browser/content/LabelChip.js.map +1 -0
  20. package/dist/browser/content/MarkdownView.js +2 -1
  21. package/dist/browser/content/MarkdownView.js.map +1 -1
  22. package/dist/browser/content/Steps.js +34 -0
  23. package/dist/browser/content/Steps.js.map +1 -0
  24. package/dist/browser/content/Timeline.js +3 -3
  25. package/dist/browser/content/Timeline.js.map +1 -1
  26. package/dist/browser/content/VirtualFeed.js +33 -1
  27. package/dist/browser/content/VirtualFeed.js.map +1 -1
  28. package/dist/browser/content/calendar-dialogs.js +32 -60
  29. package/dist/browser/content/calendar-dialogs.js.map +1 -1
  30. package/dist/browser/content/calendar-month-scroll.js +80 -0
  31. package/dist/browser/content/calendar-month-scroll.js.map +1 -0
  32. package/dist/browser/content/calendar-time-view.js +0 -1
  33. package/dist/browser/content/calendar-time-view.js.map +1 -1
  34. package/dist/browser/content/index.js +4 -1
  35. package/dist/browser/content/index.js.map +1 -1
  36. package/dist/browser/content/markdown-info-blocks.js +33 -5
  37. package/dist/browser/content/markdown-info-blocks.js.map +1 -1
  38. package/dist/browser/feedback/prompts.js +17 -3
  39. package/dist/browser/feedback/prompts.js.map +1 -1
  40. package/dist/browser/index.js +1 -0
  41. package/dist/browser/index.js.map +1 -1
  42. package/dist/browser/inputs/Combobox.js +22 -3
  43. package/dist/browser/inputs/Combobox.js.map +1 -1
  44. package/dist/browser/inputs/QrScanner.js +2 -1
  45. package/dist/browser/inputs/QrScanner.js.map +1 -1
  46. package/dist/browser/intl/index.js +1 -0
  47. package/dist/browser/intl/index.js.map +1 -1
  48. package/dist/browser/intl/shortcut-messages.js +77 -0
  49. package/dist/browser/intl/shortcut-messages.js.map +1 -0
  50. package/dist/browser/intl/shortcut.js +123 -0
  51. package/dist/browser/intl/shortcut.js.map +1 -0
  52. package/dist/browser/layout/AppWorkspace.js +355 -338
  53. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  54. package/dist/browser/layout/TableOfContents.js +115 -0
  55. package/dist/browser/layout/TableOfContents.js.map +1 -0
  56. package/dist/browser/layout/app-workspace-controller.js +7 -50
  57. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  58. package/dist/browser/layout/index.js +1 -0
  59. package/dist/browser/layout/index.js.map +1 -1
  60. package/dist/browser/layout/scroll-fade.js +14 -9
  61. package/dist/browser/layout/scroll-fade.js.map +1 -1
  62. package/dist/browser/layout/sidebar-scroll-memory.js +38 -0
  63. package/dist/browser/layout/sidebar-scroll-memory.js.map +1 -0
  64. package/dist/browser/layout/table-of-contents-messages.js +19 -0
  65. package/dist/browser/layout/table-of-contents-messages.js.map +1 -0
  66. package/dist/browser/motion.js +7 -0
  67. package/dist/browser/motion.js.map +1 -0
  68. package/dist/browser/surfaces/LinkCard.js +40 -24
  69. package/dist/browser/surfaces/LinkCard.js.map +1 -1
  70. package/dist/ssr/actions/Dropdown.js +11 -5
  71. package/dist/ssr/actions/Dropdown.js.map +1 -1
  72. package/dist/ssr/actions/GestureMenu.js +5 -5
  73. package/dist/ssr/actions/GestureMenu.js.map +1 -1
  74. package/dist/ssr/actions/SpotlightSearch.js +9 -3
  75. package/dist/ssr/actions/SpotlightSearch.js.map +1 -1
  76. package/dist/ssr/actions/Toolbar.js +67 -1
  77. package/dist/ssr/actions/Toolbar.js.map +1 -1
  78. package/dist/ssr/content/Calendar.js +864 -312
  79. package/dist/ssr/content/Calendar.js.map +1 -1
  80. package/dist/ssr/content/CalendarDayView.js +5 -4
  81. package/dist/ssr/content/CalendarDayView.js.map +1 -1
  82. package/dist/ssr/content/CalendarWeekView.js +5 -4
  83. package/dist/ssr/content/CalendarWeekView.js.map +1 -1
  84. package/dist/ssr/content/Kbd.js +38 -0
  85. package/dist/ssr/content/Kbd.js.map +1 -0
  86. package/dist/ssr/content/LabelChip.js +21 -0
  87. package/dist/ssr/content/LabelChip.js.map +1 -0
  88. package/dist/ssr/content/MarkdownView.js +2 -1
  89. package/dist/ssr/content/MarkdownView.js.map +1 -1
  90. package/dist/ssr/content/Steps.js +14 -0
  91. package/dist/ssr/content/Steps.js.map +1 -0
  92. package/dist/ssr/content/Timeline.js +3 -3
  93. package/dist/ssr/content/Timeline.js.map +1 -1
  94. package/dist/ssr/content/VirtualFeed.js +33 -1
  95. package/dist/ssr/content/VirtualFeed.js.map +1 -1
  96. package/dist/ssr/content/calendar-dialogs.js +12 -32
  97. package/dist/ssr/content/calendar-dialogs.js.map +1 -1
  98. package/dist/ssr/content/calendar-month-scroll.js +80 -0
  99. package/dist/ssr/content/calendar-month-scroll.js.map +1 -0
  100. package/dist/ssr/content/calendar-time-view.js +0 -1
  101. package/dist/ssr/content/calendar-time-view.js.map +1 -1
  102. package/dist/ssr/content/index.js +4 -1
  103. package/dist/ssr/content/index.js.map +1 -1
  104. package/dist/ssr/content/markdown-info-blocks.js +33 -5
  105. package/dist/ssr/content/markdown-info-blocks.js.map +1 -1
  106. package/dist/ssr/feedback/prompts.js +17 -3
  107. package/dist/ssr/feedback/prompts.js.map +1 -1
  108. package/dist/ssr/index.js +1 -0
  109. package/dist/ssr/index.js.map +1 -1
  110. package/dist/ssr/inputs/Combobox.js +18 -3
  111. package/dist/ssr/inputs/Combobox.js.map +1 -1
  112. package/dist/ssr/inputs/QrScanner.js +2 -1
  113. package/dist/ssr/inputs/QrScanner.js.map +1 -1
  114. package/dist/ssr/intl/index.js +1 -0
  115. package/dist/ssr/intl/index.js.map +1 -1
  116. package/dist/ssr/intl/shortcut-messages.js +77 -0
  117. package/dist/ssr/intl/shortcut-messages.js.map +1 -0
  118. package/dist/ssr/intl/shortcut.js +123 -0
  119. package/dist/ssr/intl/shortcut.js.map +1 -0
  120. package/dist/ssr/layout/AppWorkspace.js +77 -72
  121. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  122. package/dist/ssr/layout/TableOfContents.js +79 -0
  123. package/dist/ssr/layout/TableOfContents.js.map +1 -0
  124. package/dist/ssr/layout/app-workspace-controller.js +7 -50
  125. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  126. package/dist/ssr/layout/index.js +1 -0
  127. package/dist/ssr/layout/index.js.map +1 -1
  128. package/dist/ssr/layout/scroll-fade.js +14 -9
  129. package/dist/ssr/layout/scroll-fade.js.map +1 -1
  130. package/dist/ssr/layout/sidebar-scroll-memory.js +38 -0
  131. package/dist/ssr/layout/sidebar-scroll-memory.js.map +1 -0
  132. package/dist/ssr/layout/table-of-contents-messages.js +19 -0
  133. package/dist/ssr/layout/table-of-contents-messages.js.map +1 -0
  134. package/dist/ssr/motion.js +7 -0
  135. package/dist/ssr/motion.js.map +1 -0
  136. package/dist/ssr/surfaces/LinkCard.js +26 -10
  137. package/dist/ssr/surfaces/LinkCard.js.map +1 -1
  138. package/dist/styles.css +1 -1
  139. package/dist/types/actions/Dropdown.d.ts +12 -0
  140. package/dist/types/actions/SpotlightSearch.d.ts +2 -0
  141. package/dist/types/content/Calendar.d.ts +16 -6
  142. package/dist/types/content/Kbd.d.ts +16 -0
  143. package/dist/types/content/LabelChip.d.ts +12 -0
  144. package/dist/types/content/MarkdownView.d.ts +2 -1
  145. package/dist/types/content/Steps.d.ts +12 -0
  146. package/dist/types/content/VirtualFeed.d.ts +8 -0
  147. package/dist/types/content/calendar-dialogs.d.ts +0 -2
  148. package/dist/types/content/calendar-month-scroll.d.ts +25 -0
  149. package/dist/types/content/calendar-time-view.d.ts +0 -1
  150. package/dist/types/content/index.d.ts +7 -1
  151. package/dist/types/content/markdown-info-blocks.d.ts +21 -4
  152. package/dist/types/feedback/prompts.d.ts +8 -2
  153. package/dist/types/index.d.ts +1 -0
  154. package/dist/types/inputs/Combobox.d.ts +1 -0
  155. package/dist/types/intl/index.d.ts +2 -0
  156. package/dist/types/intl/shortcut-messages.d.ts +35 -0
  157. package/dist/types/intl/shortcut.d.ts +29 -0
  158. package/dist/types/layout/AppWorkspace.d.ts +3 -1
  159. package/dist/types/layout/TableOfContents.d.ts +24 -0
  160. package/dist/types/layout/index.d.ts +2 -0
  161. package/dist/types/layout/scroll-fade.d.ts +2 -0
  162. package/dist/types/layout/sidebar-scroll-memory.d.ts +6 -0
  163. package/dist/types/layout/table-of-contents-messages.d.ts +4 -0
  164. package/dist/types/motion.d.ts +6 -0
  165. package/dist/types/surfaces/LinkCard.d.ts +3 -0
  166. package/package.json +3 -3
@@ -21,7 +21,7 @@ var _tmpl$ = /*#__PURE__*/_$template(`<i class="ti ti-loader-2 k2b-spin"aria-hid
21
21
  _tmpl$6 = /*#__PURE__*/_$template(`<button type=button class=k2b-choice-option role=option><!$><!/><span><strong></strong><!$><!/>`),
22
22
  _tmpl$7 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
23
23
  _tmpl$8 = /*#__PURE__*/_$template(`<small>`);
24
- import { createSignal, For, Show } from "solid-js";
24
+ import { createEffect, createSignal, For, Show } from "solid-js";
25
25
  import { createFieldMeta, Field, fieldControlAria } from "../internal/field";
26
26
  import { useUiMessages } from "../intl/messages";
27
27
  import { createChoiceLoader, createChoicePopover, nextEnabledChoiceIndex } from "./choice";
@@ -52,7 +52,7 @@ export function Combobox(props) {
52
52
  // chevron slot and stale results stay visible, so there is nothing to pad —
53
53
  // 150ms keeps typing responsive without hammering the loader.
54
54
  () => props.debounceMs ?? 150);
55
- const popover = createChoicePopover(() => Boolean(props.disabled));
55
+ const popover = createChoicePopover(() => Boolean(props.disabled || props.readOnly));
56
56
  const options = loader.options;
57
57
  const focusedOption = () => options()[focusedIndex()];
58
58
  const focus = index => {
@@ -64,7 +64,7 @@ export function Combobox(props) {
64
64
  /** `eager` loads the currently controlled query on open; typing supplies its
65
65
  * own query, so opening from `onInput` skips the duplicate request. */
66
66
  const open = (eager = true) => {
67
- if (props.disabled || popover.open()) return;
67
+ if (props.disabled || props.readOnly || popover.open()) return;
68
68
  popover.show();
69
69
  if (eager) loader.load(query(), true);
70
70
  focus(-1);
@@ -78,7 +78,15 @@ export function Combobox(props) {
78
78
  popover.hide();
79
79
  reset();
80
80
  };
81
+ createEffect(() => {
82
+ if (props.readOnly) {
83
+ popover.hide();
84
+ loader.cancel();
85
+ setFocusedIndex(-1);
86
+ }
87
+ });
81
88
  const select = option => {
89
+ if (props.disabled || props.readOnly) return;
82
90
  popover.hide();
83
91
  props.onSelect({
84
92
  id: option.value,
@@ -90,6 +98,7 @@ export function Combobox(props) {
90
98
  inputRef?.focus();
91
99
  };
92
100
  const onKeyDown = event => {
101
+ if (props.disabled || props.readOnly) return;
93
102
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
94
103
  event.preventDefault();
95
104
  if (!popover.open()) open();else focus(nextEnabledChoiceIndex(options(), focusedIndex(), event.key === "ArrowDown" ? 1 : -1));
@@ -155,6 +164,12 @@ export function Combobox(props) {
155
164
  get disabled() {
156
165
  return props.disabled;
157
166
  },
167
+ get readOnly() {
168
+ return props.readOnly;
169
+ },
170
+ get ["aria-readonly"]() {
171
+ return props.readOnly ? "true" : undefined;
172
+ },
158
173
  get ["aria-expanded"]() {
159
174
  return popover.open();
160
175
  },
@@ -164,6 +179,10 @@ export function Combobox(props) {
164
179
  }, () => fieldControlAria(meta, props), {
165
180
  "onClick": () => open(),
166
181
  "onInput": event => {
182
+ if (props.disabled || props.readOnly) {
183
+ event.currentTarget.value = query();
184
+ return;
185
+ }
167
186
  setQuery(event.currentTarget.value);
168
187
  open(false);
169
188
  loader.load(event.currentTarget.value);
@@ -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() && 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 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":[]}
@@ -12,6 +12,7 @@ var _tmpl$ = /*#__PURE__*/_$template(`<p class=k2b-qr-scanner__note>`),
12
12
  _tmpl$2 = /*#__PURE__*/_$template(`<div><div class=k2b-qr-scanner__preview><video muted playsinline></video></div><p role=status></p><!$><!/>`);
13
13
  import { timed } from "@k2b/stdlib/solid";
14
14
  import { createSignal, onCleanup, onMount, Show } from "solid-js";
15
+ import { prefersReducedMotion } from "../motion";
15
16
  import { useQrMessages } from "./qr-messages";
16
17
 
17
18
  /**
@@ -182,7 +183,7 @@ export function QrScanner(props) {
182
183
  for (const animation of scanner?.$overlay?.getAnimations({
183
184
  subtree: true
184
185
  }) ?? []) {
185
- if (motion.matches) animation.pause();else animation.play();
186
+ if (prefersReducedMotion()) animation.pause();else animation.play();
186
187
  }
187
188
  };
188
189
  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":"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","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":[]}
@@ -1,3 +1,4 @@
1
1
  export { Format } from "./Format";
2
2
  export { LocaleProvider, useLocale } from "./locale";
3
+ export { formatShortcut, keyboardPlatform } from "./shortcut";
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["intl/index.ts"],"sourcesContent":["export type {\n FormatBytesProps,\n FormatCurrencyProps,\n FormatDateProps,\n FormatDateTimeProps,\n FormatDurationMsProps,\n FormatDurationProps,\n FormatNumberProps,\n FormatPercentProps,\n FormatRelativeDateProps,\n FormatRelativeTimeProps,\n FormatTimeProps,\n} from \"./Format\";\nexport { Format } from \"./Format\";\nexport type { LocaleProviderProps } from \"./locale\";\nexport { LocaleProvider, useLocale } from \"./locale\";\n"],"mappings":"AAaA,SAAS,MAAM,QAAQ,UAAU;AAEjC,SAAS,cAAc,EAAE,SAAS,QAAQ,UAAU","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["intl/index.ts"],"sourcesContent":["export type {\n FormatBytesProps,\n FormatCurrencyProps,\n FormatDateProps,\n FormatDateTimeProps,\n FormatDurationMsProps,\n FormatDurationProps,\n FormatNumberProps,\n FormatPercentProps,\n FormatRelativeDateProps,\n FormatRelativeTimeProps,\n FormatTimeProps,\n} from \"./Format\";\nexport { Format } from \"./Format\";\nexport type { LocaleProviderProps } from \"./locale\";\nexport { LocaleProvider, useLocale } from \"./locale\";\n\nexport type { FormattedShortcut, KeyboardPlatform, ShortcutFormatOptions, ShortcutKey } from \"./shortcut\";\nexport { formatShortcut, keyboardPlatform } from \"./shortcut\";\n"],"mappings":"AAaA,SAAS,MAAM,QAAQ,UAAU;AAEjC,SAAS,cAAc,EAAE,SAAS,QAAQ,UAAU;AAGpD,SAAS,cAAc,EAAE,gBAAgB,QAAQ,YAAY","ignoreList":[]}
@@ -0,0 +1,77 @@
1
+ import { i18n } from "@k2b/stdlib";
2
+ const shortcutMessages = i18n.define({
3
+ baseLocale: "en",
4
+ messages: {
5
+ en: {
6
+ ctrlLabel: "Ctrl",
7
+ altLabel: "Alt",
8
+ shiftLabel: "Shift",
9
+ metaLabel: "Meta",
10
+ control: "Control",
11
+ appleControl: "Control",
12
+ option: "Option",
13
+ shift: "Shift",
14
+ command: "Command",
15
+ alt: "Alt",
16
+ meta: "Meta",
17
+ arrowup: "Up arrow",
18
+ arrowdown: "Down arrow",
19
+ arrowleft: "Left arrow",
20
+ arrowright: "Right arrow",
21
+ enter: "Enter",
22
+ escape: "Escape",
23
+ tab: "Tab",
24
+ space: "Space",
25
+ backspace: "Backspace",
26
+ delete: "Delete",
27
+ home: "Home",
28
+ end: "End",
29
+ pageup: "Page up",
30
+ pagedown: "Page down",
31
+ spaceLabel: "Space",
32
+ deleteLabel: "Del",
33
+ homeLabel: "Home",
34
+ endLabel: "End",
35
+ pageupLabel: "PgUp",
36
+ pagedownLabel: "PgDn"
37
+ },
38
+ de: {
39
+ ctrlLabel: "Strg",
40
+ altLabel: "Alt",
41
+ shiftLabel: "Umschalt",
42
+ metaLabel: "Meta",
43
+ control: "Steuerung",
44
+ appleControl: "Control",
45
+ option: "Wahltaste",
46
+ shift: "Umschalttaste",
47
+ command: "Befehlstaste",
48
+ alt: "Alt",
49
+ meta: "Meta",
50
+ arrowup: "Pfeil nach oben",
51
+ arrowdown: "Pfeil nach unten",
52
+ arrowleft: "Pfeil nach links",
53
+ arrowright: "Pfeil nach rechts",
54
+ enter: "Eingabetaste",
55
+ escape: "Escape",
56
+ tab: "Tabulatortaste",
57
+ space: "Leertaste",
58
+ backspace: "Rücktaste",
59
+ delete: "Entfernen",
60
+ home: "Pos 1",
61
+ end: "Ende",
62
+ pageup: "Bild auf",
63
+ pagedown: "Bild ab",
64
+ spaceLabel: "Leertaste",
65
+ deleteLabel: "Entf",
66
+ homeLabel: "Pos1",
67
+ endLabel: "Ende",
68
+ pageupLabel: "Bild↑",
69
+ pagedownLabel: "Bild↓"
70
+ }
71
+ }
72
+ });
73
+
74
+ /** Imperative UI inherits the document locale when no locale is supplied. */
75
+ export const resolveShortcutMessages = locale => shortcutMessages.resolve([locale ?? (typeof document === "undefined" ? shortcutMessages.baseLocale : document.documentElement.lang)]).t;
76
+ export const checkShortcutMessages = () => shortcutMessages.check();
77
+ //# sourceMappingURL=shortcut-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["intl/shortcut-messages.ts"],"sourcesContent":["import { i18n } from \"@k2b/stdlib\";\n\nconst shortcutMessages = i18n.define({\n baseLocale: \"en\",\n messages: {\n en: {\n ctrlLabel: \"Ctrl\",\n altLabel: \"Alt\",\n shiftLabel: \"Shift\",\n metaLabel: \"Meta\",\n control: \"Control\",\n appleControl: \"Control\",\n option: \"Option\",\n shift: \"Shift\",\n command: \"Command\",\n alt: \"Alt\",\n meta: \"Meta\",\n arrowup: \"Up arrow\",\n arrowdown: \"Down arrow\",\n arrowleft: \"Left arrow\",\n arrowright: \"Right arrow\",\n enter: \"Enter\",\n escape: \"Escape\",\n tab: \"Tab\",\n space: \"Space\",\n backspace: \"Backspace\",\n delete: \"Delete\",\n home: \"Home\",\n end: \"End\",\n pageup: \"Page up\",\n pagedown: \"Page down\",\n spaceLabel: \"Space\",\n deleteLabel: \"Del\",\n homeLabel: \"Home\",\n endLabel: \"End\",\n pageupLabel: \"PgUp\",\n pagedownLabel: \"PgDn\",\n },\n de: {\n ctrlLabel: \"Strg\",\n altLabel: \"Alt\",\n shiftLabel: \"Umschalt\",\n metaLabel: \"Meta\",\n control: \"Steuerung\",\n appleControl: \"Control\",\n option: \"Wahltaste\",\n shift: \"Umschalttaste\",\n command: \"Befehlstaste\",\n alt: \"Alt\",\n meta: \"Meta\",\n arrowup: \"Pfeil nach oben\",\n arrowdown: \"Pfeil nach unten\",\n arrowleft: \"Pfeil nach links\",\n arrowright: \"Pfeil nach rechts\",\n enter: \"Eingabetaste\",\n escape: \"Escape\",\n tab: \"Tabulatortaste\",\n space: \"Leertaste\",\n backspace: \"Rücktaste\",\n delete: \"Entfernen\",\n home: \"Pos 1\",\n end: \"Ende\",\n pageup: \"Bild auf\",\n pagedown: \"Bild ab\",\n spaceLabel: \"Leertaste\",\n deleteLabel: \"Entf\",\n homeLabel: \"Pos1\",\n endLabel: \"Ende\",\n pageupLabel: \"Bild↑\",\n pagedownLabel: \"Bild↓\",\n },\n },\n});\n\n/** Imperative UI inherits the document locale when no locale is supplied. */\nexport const resolveShortcutMessages = (locale?: string) =>\n shortcutMessages.resolve([locale ?? (typeof document === \"undefined\" ? shortcutMessages.baseLocale : document.documentElement.lang)]).t;\nexport const checkShortcutMessages = () => shortcutMessages.check();\n"],"mappings":"AAAA,SAAS,IAAI,QAAQ,aAAa;AAElC,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC;EACnC,UAAU,EAAE,IAAI;EAChB,QAAQ,EAAE;IACR,EAAE,EAAE;MACF,SAAS,EAAE,MAAM;MACjB,QAAQ,EAAE,KAAK;MACf,UAAU,EAAE,OAAO;MACnB,SAAS,EAAE,MAAM;MACjB,OAAO,EAAE,SAAS;MAClB,YAAY,EAAE,SAAS;MACvB,MAAM,EAAE,QAAQ;MAChB,KAAK,EAAE,OAAO;MACd,OAAO,EAAE,SAAS;MAClB,GAAG,EAAE,KAAK;MACV,IAAI,EAAE,MAAM;MACZ,OAAO,EAAE,UAAU;MACnB,SAAS,EAAE,YAAY;MACvB,SAAS,EAAE,YAAY;MACvB,UAAU,EAAE,aAAa;MACzB,KAAK,EAAE,OAAO;MACd,MAAM,EAAE,QAAQ;MAChB,GAAG,EAAE,KAAK;MACV,KAAK,EAAE,OAAO;MACd,SAAS,EAAE,WAAW;MACtB,MAAM,EAAE,QAAQ;MAChB,IAAI,EAAE,MAAM;MACZ,GAAG,EAAE,KAAK;MACV,MAAM,EAAE,SAAS;MACjB,QAAQ,EAAE,WAAW;MACrB,UAAU,EAAE,OAAO;MACnB,WAAW,EAAE,KAAK;MAClB,SAAS,EAAE,MAAM;MACjB,QAAQ,EAAE,KAAK;MACf,WAAW,EAAE,MAAM;MACnB,aAAa,EAAE;IACjB,CAAC;IACD,EAAE,EAAE;MACF,SAAS,EAAE,MAAM;MACjB,QAAQ,EAAE,KAAK;MACf,UAAU,EAAE,UAAU;MACtB,SAAS,EAAE,MAAM;MACjB,OAAO,EAAE,WAAW;MACpB,YAAY,EAAE,SAAS;MACvB,MAAM,EAAE,WAAW;MACnB,KAAK,EAAE,eAAe;MACtB,OAAO,EAAE,cAAc;MACvB,GAAG,EAAE,KAAK;MACV,IAAI,EAAE,MAAM;MACZ,OAAO,EAAE,iBAAiB;MAC1B,SAAS,EAAE,kBAAkB;MAC7B,SAAS,EAAE,kBAAkB;MAC7B,UAAU,EAAE,mBAAmB;MAC/B,KAAK,EAAE,cAAc;MACrB,MAAM,EAAE,QAAQ;MAChB,GAAG,EAAE,gBAAgB;MACrB,KAAK,EAAE,WAAW;MAClB,SAAS,EAAE,WAAW;MACtB,MAAM,EAAE,WAAW;MACnB,IAAI,EAAE,OAAO;MACb,GAAG,EAAE,MAAM;MACX,MAAM,EAAE,UAAU;MAClB,QAAQ,EAAE,SAAS;MACnB,UAAU,EAAE,WAAW;MACvB,WAAW,EAAE,MAAM;MACnB,SAAS,EAAE,MAAM;MACjB,QAAQ,EAAE,MAAM;MAChB,WAAW,EAAE,OAAO;MACpB,aAAa,EAAE;IACjB;EACF;AACF,CAAC,CAAC;;AAEF;AACA,OAAO,MAAM,uBAAuB,GAAI,MAAe,IACrD,gBAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,OAAO,QAAQ,KAAK,WAAW,GAAG,gBAAgB,CAAC,UAAU,GAAG,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACzI,OAAO,MAAM,qBAAqB,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC","ignoreList":[]}
@@ -0,0 +1,123 @@
1
+ import { resolveShortcutMessages } from "./shortcut-messages";
2
+ /**
3
+ * Apple keyboards name the platform modifier Command. `source` is a User-Agent or `navigator.platform` string.
4
+ * Without `source`, reads the runtime's navigator; server callers pass the request's User-Agent.
5
+ * @example keyboardPlatform("MacIntel") // "apple"
6
+ */
7
+ export const keyboardPlatform = source => /Mac|iPhone|iPad|iPod/.test(source ?? (typeof navigator === "undefined" ? "" : navigator.platform || navigator.userAgent)) ? "apple" : "other";
8
+ const modifiers = ["ctrl", "alt", "shift", "meta"];
9
+
10
+ /**
11
+ * Formats one plus-separated shortcut in canonical modifier order.
12
+ * @example formatShortcut("mod+alt+n", { platform: "apple" }).label // "⌥⌘N"
13
+ */
14
+ export const formatShortcut = (shortcut, options = {}) => {
15
+ const empty = {
16
+ keys: [],
17
+ label: "",
18
+ name: ""
19
+ };
20
+ const input = shortcut.trim().toLowerCase();
21
+ const plus = input === "+" || input.endsWith("++");
22
+ const parts = (plus ? input.slice(0, -1) : input).split("+");
23
+ const key = plus ? "+" : parts.pop() || (shortcut === " " || shortcut.endsWith("+ ") ? "space" : undefined);
24
+ if (plus) parts.pop();
25
+ if (!key || modifiers.some(modifier => key === modifier) || key === "mod" || parts.some(part => part !== "mod" && !modifiers.some(modifier => part === modifier))) return empty;
26
+ const apple = (options.platform ?? keyboardPlatform()) === "apple";
27
+ const t = resolveShortcutMessages(options.locale);
28
+ const selected = new Set(parts.map(part => part === "mod" ? apple ? "meta" : "ctrl" : part));
29
+ const labels = apple ? {
30
+ ctrl: "⌃",
31
+ alt: "⌥",
32
+ shift: "⇧",
33
+ meta: "⌘"
34
+ } : {
35
+ ctrl: t.ctrlLabel,
36
+ alt: t.altLabel,
37
+ shift: t.shiftLabel,
38
+ meta: t.metaLabel
39
+ };
40
+ const names = {
41
+ ctrl: apple ? t.appleControl : t.control,
42
+ alt: apple ? t.option : t.alt,
43
+ shift: t.shift,
44
+ meta: apple ? t.command : t.meta
45
+ };
46
+ const named = {
47
+ arrowup: {
48
+ label: "↑",
49
+ name: t.arrowup
50
+ },
51
+ arrowdown: {
52
+ label: "↓",
53
+ name: t.arrowdown
54
+ },
55
+ arrowleft: {
56
+ label: "←",
57
+ name: t.arrowleft
58
+ },
59
+ arrowright: {
60
+ label: "→",
61
+ name: t.arrowright
62
+ },
63
+ enter: {
64
+ label: "↵",
65
+ name: t.enter
66
+ },
67
+ escape: {
68
+ label: "Esc",
69
+ name: t.escape
70
+ },
71
+ esc: {
72
+ label: "Esc",
73
+ name: t.escape
74
+ },
75
+ tab: {
76
+ label: "Tab",
77
+ name: t.tab
78
+ },
79
+ space: {
80
+ label: t.spaceLabel,
81
+ name: t.space
82
+ },
83
+ backspace: {
84
+ label: "⌫",
85
+ name: t.backspace
86
+ },
87
+ delete: {
88
+ label: apple ? "⌦" : t.deleteLabel,
89
+ name: t.delete
90
+ },
91
+ home: {
92
+ label: t.homeLabel,
93
+ name: t.home
94
+ },
95
+ end: {
96
+ label: t.endLabel,
97
+ name: t.end
98
+ },
99
+ pageup: {
100
+ label: t.pageupLabel,
101
+ name: t.pageup
102
+ },
103
+ pagedown: {
104
+ label: t.pagedownLabel,
105
+ name: t.pagedown
106
+ }
107
+ };
108
+ const label = key.length === 1 || /^f(?:[1-9]|1[0-2])$/.test(key) ? key.toUpperCase() : key[0].toUpperCase() + key.slice(1);
109
+ const namedKey = Object.hasOwn(named, key) ? named[key] : undefined;
110
+ const keys = [...modifiers.filter(modifier => selected.has(modifier)).map(modifier => ({
111
+ label: labels[modifier],
112
+ name: names[modifier]
113
+ })), namedKey ?? {
114
+ label,
115
+ name: label
116
+ }];
117
+ return {
118
+ keys,
119
+ label: keys.map(key => key.label).join(apple ? "" : "+"),
120
+ name: keys.map(key => key.name).join(" ")
121
+ };
122
+ };
123
+ //# sourceMappingURL=shortcut.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["intl/shortcut.ts"],"sourcesContent":["import { resolveShortcutMessages } from \"./shortcut-messages\";\n\nexport type KeyboardPlatform = \"apple\" | \"other\";\n/**\n * Apple keyboards name the platform modifier Command. `source` is a User-Agent or `navigator.platform` string.\n * Without `source`, reads the runtime's navigator; server callers pass the request's User-Agent.\n * @example keyboardPlatform(\"MacIntel\") // \"apple\"\n */\nexport const keyboardPlatform = (source?: string): KeyboardPlatform =>\n /Mac|iPhone|iPad|iPod/.test(source ?? (typeof navigator === \"undefined\" ? \"\" : navigator.platform || navigator.userAgent))\n ? \"apple\"\n : \"other\";\n\nexport type ShortcutKey = {\n /** What the cap shows: \"⌘\", \"Strg\", \"K\", \"↵\". */\n label: string;\n /** What a screen reader says: \"Command\", \"Steuerung\", \"K\", \"Enter\". */\n name: string;\n};\nexport type FormattedShortcut = {\n keys: ShortcutKey[];\n /** \"⌘K\" on Apple, \"Ctrl+K\" / \"Strg+K\" elsewhere. */\n label: string;\n /** \"Command K\", \"Steuerung K\". */\n name: string;\n};\nexport type ShortcutFormatOptions = { platform?: KeyboardPlatform; locale?: string };\nconst modifiers = [\"ctrl\", \"alt\", \"shift\", \"meta\"] as const;\n\n/**\n * Formats one plus-separated shortcut in canonical modifier order.\n * @example formatShortcut(\"mod+alt+n\", { platform: \"apple\" }).label // \"⌥⌘N\"\n */\nexport const formatShortcut = (shortcut: string, options: ShortcutFormatOptions = {}): FormattedShortcut => {\n const empty = { keys: [], label: \"\", name: \"\" };\n const input = shortcut.trim().toLowerCase();\n const plus = input === \"+\" || input.endsWith(\"++\");\n const parts = (plus ? input.slice(0, -1) : input).split(\"+\");\n const key = plus ? \"+\" : parts.pop() || (shortcut === \" \" || shortcut.endsWith(\"+ \") ? \"space\" : undefined);\n if (plus) parts.pop();\n if (\n !key ||\n modifiers.some((modifier) => key === modifier) ||\n key === \"mod\" ||\n parts.some((part) => part !== \"mod\" && !modifiers.some((modifier) => part === modifier))\n )\n return empty;\n const apple = (options.platform ?? keyboardPlatform()) === \"apple\";\n const t = resolveShortcutMessages(options.locale);\n const selected = new Set(parts.map((part) => (part === \"mod\" ? (apple ? \"meta\" : \"ctrl\") : part)));\n const labels = apple\n ? { ctrl: \"⌃\", alt: \"⌥\", shift: \"⇧\", meta: \"⌘\" }\n : { ctrl: t.ctrlLabel, alt: t.altLabel, shift: t.shiftLabel, meta: t.metaLabel };\n const names = {\n ctrl: apple ? t.appleControl : t.control,\n alt: apple ? t.option : t.alt,\n shift: t.shift,\n meta: apple ? t.command : t.meta,\n };\n const named: Record<string, ShortcutKey> = {\n arrowup: { label: \"↑\", name: t.arrowup },\n arrowdown: { label: \"↓\", name: t.arrowdown },\n arrowleft: { label: \"←\", name: t.arrowleft },\n arrowright: { label: \"→\", name: t.arrowright },\n enter: { label: \"↵\", name: t.enter },\n escape: { label: \"Esc\", name: t.escape },\n esc: { label: \"Esc\", name: t.escape },\n tab: { label: \"Tab\", name: t.tab },\n space: { label: t.spaceLabel, name: t.space },\n backspace: { label: \"⌫\", name: t.backspace },\n delete: { label: apple ? \"⌦\" : t.deleteLabel, name: t.delete },\n home: { label: t.homeLabel, name: t.home },\n end: { label: t.endLabel, name: t.end },\n pageup: { label: t.pageupLabel, name: t.pageup },\n pagedown: { label: t.pagedownLabel, name: t.pagedown },\n };\n const label = key.length === 1 || /^f(?:[1-9]|1[0-2])$/.test(key) ? key.toUpperCase() : key[0]!.toUpperCase() + key.slice(1);\n const namedKey = Object.hasOwn(named, key) ? named[key] : undefined;\n const keys = [\n ...modifiers.filter((modifier) => selected.has(modifier)).map((modifier) => ({ label: labels[modifier], name: names[modifier] })),\n namedKey ?? { label, name: label },\n ];\n return { keys, label: keys.map((key) => key.label).join(apple ? \"\" : \"+\"), name: keys.map((key) => key.name).join(\" \") };\n};\n"],"mappings":"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","ignoreList":[]}