@k2b/ui 0.41.0 → 0.43.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 (74) hide show
  1. package/dist/base.css +22 -0
  2. package/dist/browser/actions/Dropdown.js +220 -110
  3. package/dist/browser/actions/Dropdown.js.map +1 -1
  4. package/dist/browser/actions/index.js.map +1 -1
  5. package/dist/browser/actions/menu-search.js +166 -0
  6. package/dist/browser/actions/menu-search.js.map +1 -0
  7. package/dist/browser/actions/menu-sheet.js +107 -13
  8. package/dist/browser/actions/menu-sheet.js.map +1 -1
  9. package/dist/browser/content/CalendarWeekView.js +13 -1
  10. package/dist/browser/content/CalendarWeekView.js.map +1 -1
  11. package/dist/browser/content/CountryMap.js +241 -0
  12. package/dist/browser/content/CountryMap.js.map +1 -0
  13. package/dist/browser/content/world-countries.js +9 -0
  14. package/dist/browser/content/world-countries.js.map +1 -0
  15. package/dist/browser/country-map.js +3 -0
  16. package/dist/browser/country-map.js.map +1 -0
  17. package/dist/browser/inputs/IconChoices.js +121 -0
  18. package/dist/browser/inputs/IconChoices.js.map +1 -0
  19. package/dist/browser/inputs/index.js +1 -0
  20. package/dist/browser/inputs/index.js.map +1 -1
  21. package/dist/browser/intl/messages.js +6 -0
  22. package/dist/browser/intl/messages.js.map +1 -1
  23. package/dist/browser/layout/AppWorkspace.js +118 -93
  24. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  25. package/dist/browser/text-editor/format.js +11 -7
  26. package/dist/browser/text-editor/format.js.map +1 -1
  27. package/dist/browser/text-editor/table.js +39 -11
  28. package/dist/browser/text-editor/table.js.map +1 -1
  29. package/dist/browser/text-editor.js +1 -1
  30. package/dist/browser/text-editor.js.map +1 -1
  31. package/dist/ssr/actions/Dropdown.js +118 -40
  32. package/dist/ssr/actions/Dropdown.js.map +1 -1
  33. package/dist/ssr/actions/index.js.map +1 -1
  34. package/dist/ssr/actions/menu-search.js +113 -0
  35. package/dist/ssr/actions/menu-search.js.map +1 -0
  36. package/dist/ssr/actions/menu-sheet.js +82 -13
  37. package/dist/ssr/actions/menu-sheet.js.map +1 -1
  38. package/dist/ssr/content/CalendarWeekView.js +13 -1
  39. package/dist/ssr/content/CalendarWeekView.js.map +1 -1
  40. package/dist/ssr/content/CountryMap.js +135 -0
  41. package/dist/ssr/content/CountryMap.js.map +1 -0
  42. package/dist/ssr/content/world-countries.js +9 -0
  43. package/dist/ssr/content/world-countries.js.map +1 -0
  44. package/dist/ssr/country-map.js +3 -0
  45. package/dist/ssr/country-map.js.map +1 -0
  46. package/dist/ssr/inputs/IconChoices.js +59 -0
  47. package/dist/ssr/inputs/IconChoices.js.map +1 -0
  48. package/dist/ssr/inputs/index.js +1 -0
  49. package/dist/ssr/inputs/index.js.map +1 -1
  50. package/dist/ssr/intl/messages.js +6 -0
  51. package/dist/ssr/intl/messages.js.map +1 -1
  52. package/dist/ssr/layout/AppWorkspace.js +40 -20
  53. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  54. package/dist/ssr/text-editor/format.js +11 -7
  55. package/dist/ssr/text-editor/format.js.map +1 -1
  56. package/dist/ssr/text-editor/table.js +39 -11
  57. package/dist/ssr/text-editor/table.js.map +1 -1
  58. package/dist/ssr/text-editor.js +1 -1
  59. package/dist/ssr/text-editor.js.map +1 -1
  60. package/dist/styles.css +1 -1
  61. package/dist/types/actions/Dropdown.d.ts +34 -3
  62. package/dist/types/actions/index.d.ts +1 -1
  63. package/dist/types/actions/menu-search.d.ts +27 -0
  64. package/dist/types/actions/menu-sheet.d.ts +5 -1
  65. package/dist/types/content/CountryMap.d.ts +21 -0
  66. package/dist/types/content/world-countries.d.ts +6 -0
  67. package/dist/types/country-map.d.ts +2 -0
  68. package/dist/types/inputs/IconChoices.d.ts +19 -0
  69. package/dist/types/inputs/index.d.ts +2 -0
  70. package/dist/types/intl/messages.d.ts +3 -0
  71. package/dist/types/layout/AppWorkspace.d.ts +6 -1
  72. package/dist/types/text-editor/table.d.ts +2 -0
  73. package/dist/types/text-editor.d.ts +1 -1
  74. package/package.json +9 -1
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["actions/index.ts"],"sourcesContent":["export type { ButtonAlign, ButtonLinkProps, ButtonProps, ButtonSize, ButtonVariant, IconButtonLinkProps, IconButtonProps } from \"./Button\";\nexport { Button, ButtonLink, IconButton, IconButtonLink } from \"./Button\";\nexport type { ContextMenuProps } from \"./ContextMenu\";\nexport { ContextMenu } from \"./ContextMenu\";\nexport type { CopyButtonProps, CopyButtonValue } from \"./CopyButton\";\nexport { CopyButton } from \"./CopyButton\";\nexport type { DisclosureProps } from \"./Disclosure\";\nexport { Disclosure } from \"./Disclosure\";\nexport type { DragPayload, DragSourceProps, DropTargetProps } from \"./DragAndDrop\";\nexport { activeDrag, DragSource, DropTarget, useKeyboardDrag } from \"./DragAndDrop\";\nexport type {\n DropdownAction,\n DropdownActionBase,\n DropdownAnchor,\n DropdownChoice,\n DropdownItemProps,\n DropdownPosition,\n DropdownProps,\n DropdownReturnFocus,\n DropdownSection,\n DropdownTriggerProps,\n} from \"./Dropdown\";\nexport { Dropdown, DropdownItem, dropdownPosition } from \"./Dropdown\";\nexport type { FilterChipOption, FilterChipProps, FilterChipSection, FilterToggleProps } from \"./FilterChip\";\nexport { FilterChip, FilterToggle } from \"./FilterChip\";\nexport type { GestureKind, GestureMenuAction, GestureMenuItem, GestureMenuProps, GestureMenuSection } from \"./GestureMenu\";\nexport { GestureMenu } from \"./GestureMenu\";\nexport type { RemoveButtonProps } from \"./RemoveButton\";\nexport { RemoveButton } from \"./RemoveButton\";\nexport type { SegmentedControlProps, SegmentOption } from \"./SegmentedControl\";\nexport { SegmentedControl } from \"./SegmentedControl\";\nexport type { SelectionToolbarProps } from \"./SelectionToolbar\";\nexport { SelectionToolbar } from \"./SelectionToolbar\";\nexport type { SplitButtonProps } from \"./SplitButton\";\nexport { SplitButton } from \"./SplitButton\";\nexport type {\n SpotlightButtonProps,\n SpotlightButtonVariant,\n SpotlightSearchOptions,\n SpotlightSearchResolver,\n} from \"./SpotlightSearch\";\nexport {\n openSpotlightSearch,\n SpotlightButton,\n} from \"./SpotlightSearch\";\nexport type { TabOption, TabsItemProps, TabsProps } from \"./Tabs\";\nexport { Tabs } from \"./Tabs\";\nexport type { ToolbarActionProps, ToolbarGroupProps, ToolbarLabelProps, ToolbarProps, ToolbarSeparatorProps } from \"./Toolbar\";\nexport { Toolbar } from \"./Toolbar\";\nexport type { ViewTab, ViewTabsProps } from \"./ViewTabs\";\nexport { ViewTabs } from \"./ViewTabs\";\n"],"mappings":"AACA,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,QAAQ,UAAU;AAEzE,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,QAAQ,eAAe;AAanF,SAAS,QAAQ,EAAE,YAAY,EAAE,gBAAgB,QAAQ,YAAY;AAErE,SAAS,UAAU,EAAE,YAAY,QAAQ,cAAc;AAEvD,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,YAAY,QAAQ,gBAAgB;AAE7C,SAAS,gBAAgB,QAAQ,oBAAoB;AAErD,SAAS,gBAAgB,QAAQ,oBAAoB;AAErD,SAAS,WAAW,QAAQ,eAAe;AAO3C,SACE,mBAAmB,EACnB,eAAe,QACV,mBAAmB;AAE1B,SAAS,IAAI,QAAQ,QAAQ;AAE7B,SAAS,OAAO,QAAQ,WAAW;AAEnC,SAAS,QAAQ,QAAQ,YAAY","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["actions/index.ts"],"sourcesContent":["export type { ButtonAlign, ButtonLinkProps, ButtonProps, ButtonSize, ButtonVariant, IconButtonLinkProps, IconButtonProps } from \"./Button\";\nexport { Button, ButtonLink, IconButton, IconButtonLink } from \"./Button\";\nexport type { ContextMenuProps } from \"./ContextMenu\";\nexport { ContextMenu } from \"./ContextMenu\";\nexport type { CopyButtonProps, CopyButtonValue } from \"./CopyButton\";\nexport { CopyButton } from \"./CopyButton\";\nexport type { DisclosureProps } from \"./Disclosure\";\nexport { Disclosure } from \"./Disclosure\";\nexport type { DragPayload, DragSourceProps, DropTargetProps } from \"./DragAndDrop\";\nexport { activeDrag, DragSource, DropTarget, useKeyboardDrag } from \"./DragAndDrop\";\nexport type {\n DropdownAction,\n DropdownActionBase,\n DropdownAnchor,\n DropdownChoice,\n DropdownItemProps,\n DropdownPosition,\n DropdownProps,\n DropdownReturnFocus,\n DropdownSearch,\n DropdownSection,\n DropdownTriggerProps,\n} from \"./Dropdown\";\nexport { Dropdown, DropdownItem, dropdownPosition } from \"./Dropdown\";\nexport type { FilterChipOption, FilterChipProps, FilterChipSection, FilterToggleProps } from \"./FilterChip\";\nexport { FilterChip, FilterToggle } from \"./FilterChip\";\nexport type { GestureKind, GestureMenuAction, GestureMenuItem, GestureMenuProps, GestureMenuSection } from \"./GestureMenu\";\nexport { GestureMenu } from \"./GestureMenu\";\nexport type { RemoveButtonProps } from \"./RemoveButton\";\nexport { RemoveButton } from \"./RemoveButton\";\nexport type { SegmentedControlProps, SegmentOption } from \"./SegmentedControl\";\nexport { SegmentedControl } from \"./SegmentedControl\";\nexport type { SelectionToolbarProps } from \"./SelectionToolbar\";\nexport { SelectionToolbar } from \"./SelectionToolbar\";\nexport type { SplitButtonProps } from \"./SplitButton\";\nexport { SplitButton } from \"./SplitButton\";\nexport type {\n SpotlightButtonProps,\n SpotlightButtonVariant,\n SpotlightSearchOptions,\n SpotlightSearchResolver,\n} from \"./SpotlightSearch\";\nexport {\n openSpotlightSearch,\n SpotlightButton,\n} from \"./SpotlightSearch\";\nexport type { TabOption, TabsItemProps, TabsProps } from \"./Tabs\";\nexport { Tabs } from \"./Tabs\";\nexport type { ToolbarActionProps, ToolbarGroupProps, ToolbarLabelProps, ToolbarProps, ToolbarSeparatorProps } from \"./Toolbar\";\nexport { Toolbar } from \"./Toolbar\";\nexport type { ViewTab, ViewTabsProps } from \"./ViewTabs\";\nexport { ViewTabs } from \"./ViewTabs\";\n"],"mappings":"AACA,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,QAAQ,UAAU;AAEzE,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,QAAQ,eAAe;AAcnF,SAAS,QAAQ,EAAE,YAAY,EAAE,gBAAgB,QAAQ,YAAY;AAErE,SAAS,UAAU,EAAE,YAAY,QAAQ,cAAc;AAEvD,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,YAAY,QAAQ,gBAAgB;AAE7C,SAAS,gBAAgB,QAAQ,oBAAoB;AAErD,SAAS,gBAAgB,QAAQ,oBAAoB;AAErD,SAAS,WAAW,QAAQ,eAAe;AAO3C,SACE,mBAAmB,EACnB,eAAe,QACV,mBAAmB;AAE1B,SAAS,IAAI,QAAQ,QAAQ;AAE7B,SAAS,OAAO,QAAQ,WAAW;AAEnC,SAAS,QAAQ,QAAQ,YAAY","ignoreList":[]}
@@ -0,0 +1,113 @@
1
+ import { createComponent as _$createComponent } from "solid-js/web";
2
+ import { ssr as _$ssr } from "solid-js/web";
3
+ import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
4
+ import { escape as _$escape } from "solid-js/web";
5
+ var _tmpl$ = ["<label class=\"k2b-dropdown__search\"><i class=\"ti ti-search\" aria-hidden=\"true\"></i><input type=\"search\" role=\"combobox\"", " aria-expanded=\"true\"", " aria-autocomplete=\"list\" autocomplete=\"off\" spellcheck=\"false\" enterkeyhint=\"go\"", "></label>"],
6
+ _tmpl$2 = ["<div", " role=\"listbox\"", " class=\"k2b-dropdown__list\">", "</div>"],
7
+ _tmpl$3 = ["<span class=\"k2b-sr-only\" role=\"status\">", "</span>"],
8
+ _tmpl$4 = ["<p class=\"k2b-dropdown__empty\">", "</p>"];
9
+ import { createEffect, createMemo, createUniqueId, on, onMount, Show } from "solid-js";
10
+ import { useLocale } from "../intl/locale";
11
+ import { useUiMessages } from "../intl/messages";
12
+ const entriesOf = items => items.flatMap(item => "items" in item ? item.items : [item]);
13
+
14
+ /** The actions and choices whose label or description contains the query, kept in their sections. */
15
+ export const filterDropdownItems = (items, query, locale) => {
16
+ const needle = query.trim().toLocaleLowerCase(locale);
17
+ const matches = entry => `${entry.label} ${entry.description ?? ""}`.toLocaleLowerCase(locale).includes(needle);
18
+ return items.flatMap(item => {
19
+ if (!("items" in item)) return matches(item) ? [item] : [];
20
+ const kept = item.items.filter(matches);
21
+ return kept.length ? [{
22
+ ...item,
23
+ items: kept
24
+ }] : [];
25
+ });
26
+ };
27
+ const optionsOf = list => list ? Array.from(list.querySelectorAll("[role='option']:not([aria-disabled='true'])")) : [];
28
+ /**
29
+ * A search field over a list box of the menu's entries: the combobox pattern. The field keeps the focus, the marked
30
+ * entry is its active descendant, and a polite status says how many entries match.
31
+ */
32
+ export function MenuSearch(props) {
33
+ const messages = useUiMessages();
34
+ const locale = useLocale();
35
+ const id = createUniqueId().replace(/[^a-zA-Z0-9_-]/g, "-");
36
+ const listId = `k2b-dropdown-search-${id}`;
37
+ let input;
38
+ let list;
39
+ let active = 0;
40
+ const blank = () => props.query().trim() === "";
41
+ const shown = createMemo(() => blank() ? props.items : props.search.filter?.(props.query()) ?? filterDropdownItems(props.items, props.query(), locale()));
42
+ const matches = () => entriesOf(shown()).filter(entry => !entry.disabled).length;
43
+ const status = () => blank() ? "" : props.search.status?.(props.query(), matches()) ?? messages().searchMatches({
44
+ count: matches()
45
+ });
46
+ const mark = index => {
47
+ // An entry that became disabled keeps no stale ID or mark.
48
+ for (const stale of list.querySelectorAll("[role='option']")) {
49
+ stale.removeAttribute("id");
50
+ stale.removeAttribute("aria-selected");
51
+ stale.removeAttribute("data-active");
52
+ }
53
+ const options = optionsOf(list);
54
+ active = options.length ? (index + options.length) % options.length : 0;
55
+ options.forEach((option, position) => {
56
+ option.id = `${listId}-${position}`;
57
+ option.setAttribute("aria-selected", String(position === active));
58
+ option.toggleAttribute("data-active", position === active);
59
+ });
60
+ const current = options[active];
61
+ if (current) {
62
+ input.setAttribute("aria-activedescendant", current.id);
63
+ current.scrollIntoView?.({
64
+ block: "nearest"
65
+ });
66
+ } else input.removeAttribute("aria-activedescendant");
67
+ };
68
+ // A new query shows new entries: the first of them is marked again, after they rendered.
69
+ createEffect(on(shown, () => queueMicrotask(() => mark(0)), {
70
+ defer: true
71
+ }));
72
+ onMount(() => {
73
+ mark(0);
74
+ props.api?.({
75
+ focus: which => {
76
+ input.focus({
77
+ preventScroll: true
78
+ });
79
+ mark(which === "first" ? 0 : -1);
80
+ }
81
+ });
82
+ });
83
+ const onKeyDown = event => {
84
+ if (event.isComposing) return;
85
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
86
+ event.preventDefault();
87
+ mark(active + (event.key === "ArrowDown" ? 1 : -1));
88
+ } else if (event.key === "Enter") {
89
+ event.preventDefault();
90
+ optionsOf(list)[active]?.click();
91
+ }
92
+ };
93
+ return [_$ssr(_tmpl$, _$ssrAttribute("aria-label", _$escape(props.search.label, true), false), _$ssrAttribute("aria-controls", _$escape(listId, true), false), _$ssrAttribute("placeholder", _$escape(props.search.placeholder ?? props.search.label, true), false) + _$ssrAttribute("value", _$escape(props.query(), true), false)), _$ssr(_tmpl$2, _$ssrAttribute("id", _$escape(listId, true), false), _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(_$createComponent(Show, {
94
+ get when() {
95
+ return shown().length;
96
+ },
97
+ get fallback() {
98
+ return _$ssr(_tmpl$4, _$escape(messages().noResults));
99
+ },
100
+ get children() {
101
+ return _$createComponent(props.Items, {
102
+ get items() {
103
+ return shown();
104
+ },
105
+ get close() {
106
+ return props.close;
107
+ },
108
+ role: "option"
109
+ });
110
+ }
111
+ }))), _$ssr(_tmpl$3, _$escape(status()))];
112
+ }
113
+ //# sourceMappingURL=menu-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["actions/menu-search.tsx"],"sourcesContent":["import { type Accessor, createEffect, createMemo, createUniqueId, type JSX, on, onMount, Show } from \"solid-js\";\nimport { useLocale } from \"../intl/locale\";\nimport { useUiMessages } from \"../intl/messages\";\nimport type { DropdownItem, DropdownSearch } from \"./Dropdown\";\n\ntype Entry = Exclude<DropdownItem, { items: unknown }>;\n\nconst entriesOf = (items: readonly DropdownItem[]): Entry[] => items.flatMap((item) => (\"items\" in item ? item.items : [item]));\n\n/** The actions and choices whose label or description contains the query, kept in their sections. */\nexport const filterDropdownItems = (items: readonly DropdownItem[], query: string, locale?: string): DropdownItem[] => {\n const needle = query.trim().toLocaleLowerCase(locale);\n const matches = (entry: Entry) => `${entry.label} ${entry.description ?? \"\"}`.toLocaleLowerCase(locale).includes(needle);\n return items.flatMap((item): DropdownItem[] => {\n if (!(\"items\" in item)) return matches(item) ? [item] : [];\n const kept = item.items.filter(matches);\n return kept.length ? [{ ...item, items: kept }] : [];\n });\n};\n\nconst optionsOf = (list: HTMLElement | undefined): HTMLElement[] =>\n list ? Array.from(list.querySelectorAll<HTMLElement>(\"[role='option']:not([aria-disabled='true'])\")) : [];\n\nexport type MenuSearchApi = {\n /** Moves the focus into the field and marks the first or last entry. */\n focus: (mark: \"first\" | \"last\") => void;\n};\n\n/**\n * A search field over a list box of the menu's entries: the combobox pattern. The field keeps the focus, the marked\n * entry is its active descendant, and a polite status says how many entries match.\n */\nexport function MenuSearch(props: {\n Items: (props: { items: readonly DropdownItem[]; close: (restoreFocus?: boolean) => void; role?: \"menuitem\" | \"option\" }) => JSX.Element;\n items: readonly DropdownItem[];\n search: DropdownSearch;\n /** Names the list box. */\n label: string;\n query: Accessor<string>;\n setQuery: (query: string) => void;\n close: (restoreFocus?: boolean) => void;\n api?: (api: MenuSearchApi) => void;\n}): JSX.Element {\n const messages = useUiMessages();\n const locale = useLocale();\n const id = createUniqueId().replace(/[^a-zA-Z0-9_-]/g, \"-\");\n const listId = `k2b-dropdown-search-${id}`;\n let input!: HTMLInputElement;\n let list!: HTMLDivElement;\n let active = 0;\n const blank = () => props.query().trim() === \"\";\n const shown = createMemo(() =>\n blank() ? props.items : (props.search.filter?.(props.query()) ?? filterDropdownItems(props.items, props.query(), locale())),\n );\n const matches = () => entriesOf(shown()).filter((entry) => !entry.disabled).length;\n const status = () => (blank() ? \"\" : (props.search.status?.(props.query(), matches()) ?? messages().searchMatches({ count: matches() })));\n\n const mark = (index: number) => {\n // An entry that became disabled keeps no stale ID or mark.\n for (const stale of list.querySelectorAll<HTMLElement>(\"[role='option']\")) {\n stale.removeAttribute(\"id\");\n stale.removeAttribute(\"aria-selected\");\n stale.removeAttribute(\"data-active\");\n }\n const options = optionsOf(list);\n active = options.length ? (index + options.length) % options.length : 0;\n options.forEach((option, position) => {\n option.id = `${listId}-${position}`;\n option.setAttribute(\"aria-selected\", String(position === active));\n option.toggleAttribute(\"data-active\", position === active);\n });\n const current = options[active];\n if (current) {\n input.setAttribute(\"aria-activedescendant\", current.id);\n current.scrollIntoView?.({ block: \"nearest\" });\n } else input.removeAttribute(\"aria-activedescendant\");\n };\n // A new query shows new entries: the first of them is marked again, after they rendered.\n createEffect(on(shown, () => queueMicrotask(() => mark(0)), { defer: true }));\n onMount(() => {\n mark(0);\n props.api?.({\n focus: (which) => {\n input.focus({ preventScroll: true });\n mark(which === \"first\" ? 0 : -1);\n },\n });\n });\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.isComposing) return;\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n mark(active + (event.key === \"ArrowDown\" ? 1 : -1));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n optionsOf(list)[active]?.click();\n }\n };\n\n return (\n <>\n <label class=\"k2b-dropdown__search\">\n <i class=\"ti ti-search\" aria-hidden=\"true\" />\n <input\n ref={input}\n type=\"search\"\n role=\"combobox\"\n aria-label={props.search.label}\n aria-expanded=\"true\"\n aria-controls={listId}\n aria-autocomplete=\"list\"\n autocomplete=\"off\"\n spellcheck={false}\n enterkeyhint=\"go\"\n placeholder={props.search.placeholder ?? props.search.label}\n value={props.query()}\n onInput={(event) => props.setQuery(event.currentTarget.value)}\n onKeyDown={onKeyDown}\n />\n </label>\n <div\n ref={list}\n id={listId}\n role=\"listbox\"\n aria-label={props.label}\n class=\"k2b-dropdown__list\"\n // A press on an entry leaves the focus in the field, also for an entry that keeps the menu open.\n onMouseDown={(event) => event.preventDefault()}\n onPointerMove={(event) => {\n const option = (event.target as Element).closest<HTMLElement>(\"[role='option']\");\n const index = option ? optionsOf(list).indexOf(option) : -1;\n if (index >= 0 && index !== active) mark(index);\n }}\n >\n <Show when={shown().length} fallback={<p class=\"k2b-dropdown__empty\">{messages().noResults}</p>}>\n <props.Items items={shown()} close={props.close} role=\"option\" />\n </Show>\n </div>\n <span class=\"k2b-sr-only\" role=\"status\">\n {status()}\n </span>\n </>\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -2,12 +2,15 @@ import { ssr as _$ssr } from "solid-js/web";
2
2
  import { createComponent as _$createComponent } from "solid-js/web";
3
3
  import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
4
4
  import { escape as _$escape } from "solid-js/web";
5
- var _tmpl$ = ["<div class=\"k2b-gesture-menu__sheet-menu\" role=\"menu\"", ">", "</div>"];
6
- import { onCleanup, onMount } from "solid-js";
5
+ var _tmpl$ = ["<div class=\"k2b-gesture-menu__sheet-menu\" role=\"menu\"", ">", "</div>"],
6
+ _tmpl$2 = ["<div class=\"k2b-gesture-menu__sheet-menu\" role=\"dialog\"", " data-search", ">", "</div>"],
7
+ _tmpl$3 = ["<p class=\"k2b-dropdown__note\">", "</p>"];
8
+ import { createSignal, onCleanup, onMount, Show } from "solid-js";
7
9
  import { dialogCore } from "../feedback/dialog-core";
8
10
  import { LocaleProvider } from "../intl/locale";
9
11
  import BottomSheet, { bottomSheetOptions } from "../layout/BottomSheet";
10
12
  import { focusMenuRow, focusRowSegment } from "./menu-rows";
13
+ import { filterDropdownItems, MenuSearch } from "./menu-search";
11
14
 
12
15
  /** Phones, where a menu opens as a bottom sheet instead of beside its trigger. */
13
16
  export const PHONE_SHEET_QUERY = "(max-width: 47.999rem)";
@@ -78,13 +81,40 @@ function SheetMenu(props) {
78
81
  props.dialog.addEventListener("keydown", onSheetKeyDown);
79
82
  onCleanup(() => props.dialog.removeEventListener("keydown", onSheetKeyDown));
80
83
  });
81
- return _$ssr(_tmpl$, _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(_$createComponent(props.Items, {
84
+ return _$ssr(_tmpl$, _$ssrAttribute("aria-label", _$escape(props.label, true), false) + _$ssrAttribute("data-icons", props.icons === "tile" ? "tile" : _$escape(undefined, true), false), _$escape(_$createComponent(props.Items, {
82
85
  get items() {
83
86
  return props.items;
84
87
  },
85
88
  close: () => props.close()
86
89
  })));
87
90
  }
91
+ /** A searchable menu in the sheet: the sheet's first field, so it takes the focus as the sheet opens, as in the menu. */
92
+ function SheetSearch(props) {
93
+ const [query, setQuery] = createSignal("");
94
+ const filter = props.search.filter;
95
+ return _$ssr(_tmpl$2, _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$ssrAttribute("data-icons", props.icons === "tile" ? "tile" : _$escape(undefined, true), false), _$escape(_$createComponent(MenuSearch, {
96
+ get Items() {
97
+ return props.Items;
98
+ },
99
+ get items() {
100
+ return props.items;
101
+ },
102
+ get search() {
103
+ return {
104
+ ...props.search,
105
+ // The sheet's own items are wrapped already; a host's filter returns its unwrapped entries.
106
+ filter: value => filter ? props.wrap(filter(value)) : filterDropdownItems(props.items, value, props.locale)
107
+ };
108
+ },
109
+ get label() {
110
+ return props.label;
111
+ },
112
+ query: query,
113
+ setQuery: setQuery,
114
+ close: () => props.close()
115
+ })));
116
+ }
117
+
88
118
  /** Opens menu items as a bottom sheet; resolves after the sheet closed and the chosen action ran. */
89
119
  export const openMenuSheet = options => {
90
120
  let chosen;
@@ -103,19 +133,58 @@ export const openMenuSheet = options => {
103
133
  get children() {
104
134
  return _$createComponent(BottomSheet.Body, {
105
135
  get children() {
106
- return [options.top?.(() => close()), _$createComponent(SheetMenu, {
107
- get Items() {
108
- return options.Items;
136
+ return [options.top?.(() => close()), _$createComponent(Show, {
137
+ get when() {
138
+ return options.search;
109
139
  },
110
- get items() {
111
- return afterClose(options.items, choose);
140
+ get fallback() {
141
+ return _$createComponent(SheetMenu, {
142
+ get Items() {
143
+ return options.Items;
144
+ },
145
+ get items() {
146
+ return afterClose(options.items, choose);
147
+ },
148
+ get label() {
149
+ return options.label;
150
+ },
151
+ close: () => close(),
152
+ get dialog() {
153
+ return context.dialog;
154
+ },
155
+ get icons() {
156
+ return options.icons;
157
+ }
158
+ });
112
159
  },
113
- get label() {
114
- return options.label;
160
+ children: search => _$createComponent(SheetSearch, {
161
+ get Items() {
162
+ return options.Items;
163
+ },
164
+ get items() {
165
+ return afterClose(options.items, choose);
166
+ },
167
+ get search() {
168
+ return search();
169
+ },
170
+ wrap: items => afterClose(items, choose),
171
+ get label() {
172
+ return options.label;
173
+ },
174
+ get locale() {
175
+ return options.locale;
176
+ },
177
+ close: () => close(),
178
+ get icons() {
179
+ return options.icons;
180
+ }
181
+ })
182
+ }), _$createComponent(Show, {
183
+ get when() {
184
+ return options.note;
115
185
  },
116
- close: () => close(),
117
- get dialog() {
118
- return context.dialog;
186
+ get children() {
187
+ return _$ssr(_tmpl$3, _$escape(options.note));
119
188
  }
120
189
  })];
121
190
  }
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["actions/menu-sheet.tsx"],"sourcesContent":["import { type JSX, onCleanup, onMount } from \"solid-js\";\nimport { dialogCore } from \"../feedback/dialog-core\";\nimport { LocaleProvider } from \"../intl/locale\";\nimport BottomSheet, { bottomSheetOptions } from \"../layout/BottomSheet\";\nimport type { DropdownAction, DropdownChoice, DropdownItem } from \"./Dropdown\";\nimport { focusMenuRow, focusRowSegment } from \"./menu-rows\";\n\n/** Phones, where a menu opens as a bottom sheet instead of beside its trigger. */\nexport const PHONE_SHEET_QUERY = \"(max-width: 47.999rem)\";\n\nexport const prefersMenuSheet = (): boolean => typeof matchMedia === \"function\" && matchMedia(PHONE_SHEET_QUERY).matches;\n\ntype SheetEntry = DropdownAction | DropdownChoice;\ntype SheetItem = SheetEntry | { items: readonly SheetEntry[] };\n\n/**\n * Follows a link the way a click on it would, so a page's own link handling, such as progressive navigation, still\n * applies; it runs only after the sheet has left the history.\n */\nconst followLink = (href: string) => {\n const link = document.createElement(\"a\");\n link.href = href;\n link.hidden = true;\n document.body.append(link);\n link.click();\n link.remove();\n};\n\n/**\n * The sheet's items hand their action to `run` instead of running it while the sheet closes: `dialogCore` settles the\n * sheet after it left the history, and only then may an action navigate or open the next dialog.\n * A choice or action that keeps the menu open, or an `immediate` action, runs at once.\n */\nconst afterClose = <T extends DropdownItem>(items: readonly T[], run: (action: () => void) => void): T[] => {\n const entry = (item: SheetEntry): SheetEntry => {\n // A plain link would navigate while the sheet's history entry is still being removed, and that Back could\n // undo the navigation; a plain click instead follows it once the sheet is gone. Modified clicks and external\n // links keep the browser's own behavior.\n if (!item.action && \"href\" in item && item.href && !(\"external\" in item && item.external)) {\n const href = item.href;\n return { ...item, action: () => run(() => followLink(href)) } as SheetEntry;\n }\n const action = item.action;\n if (!action || (\"closeOnSelect\" in item && item.closeOnSelect === false) || (\"immediate\" in item && item.immediate)) return item;\n return { ...item, action: () => run(action) } as SheetEntry;\n };\n return items.map((item) => {\n const sheetItem = item as SheetItem;\n return (\"items\" in sheetItem ? { ...sheetItem, items: sheetItem.items.map(entry) } : entry(sheetItem)) as T;\n });\n};\n\nconst menuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(menu.querySelectorAll<HTMLElement>(\"[role^='menuitem']:not([aria-disabled='true'])\"));\n\n/**\n * The menu inside the sheet: the same items, at touch size, with arrow keys for a connected keyboard. Focus starts on\n * the sheet itself, which shows no ring on a phone; the first arrow key moves it into the menu.\n */\nfunction SheetMenu(props: {\n Items: MenuSheetOptions[\"Items\"];\n items: readonly DropdownItem[];\n label: string;\n close: () => void;\n dialog: HTMLDialogElement;\n}): JSX.Element {\n let menu!: HTMLDivElement;\n const onKeyDown = (event: KeyboardEvent) => {\n const items = menuItems(menu);\n if (!focusMenuRow(items, event.key) && !focusRowSegment(items, event.key)) return;\n event.preventDefault();\n };\n // Keys pressed on the sheet itself, while it is the top level of the shared dialog.\n const onSheetKeyDown = (event: KeyboardEvent) => {\n if (event.target === props.dialog && props.dialog.lastElementChild?.contains(menu)) onKeyDown(event);\n };\n onMount(() => {\n props.dialog.addEventListener(\"keydown\", onSheetKeyDown);\n onCleanup(() => props.dialog.removeEventListener(\"keydown\", onSheetKeyDown));\n });\n return (\n <div ref={menu} class=\"k2b-gesture-menu__sheet-menu\" role=\"menu\" aria-label={props.label} onKeyDown={onKeyDown}>\n <props.Items items={props.items} close={() => props.close()} />\n </div>\n );\n}\n\nexport type MenuSheetOptions = {\n /** Renders the rows: the dropdown's own `DropdownItems`, passed in so this module does not import it back. */\n Items: (props: { items: readonly DropdownItem[]; close: (restoreFocus?: boolean) => void }) => JSX.Element;\n items: readonly DropdownItem[];\n /** Names the sheet and its menu. */\n label: string;\n /** The render locale of the opener; the sheet renders outside its tree. */\n locale: string;\n /** Shown above the menu, for example a row of quick reactions. `close` closes the sheet. */\n top?: (close: () => void) => JSX.Element;\n signal?: AbortSignal;\n};\n\n/** Opens menu items as a bottom sheet; resolves after the sheet closed and the chosen action ran. */\nexport const openMenuSheet = (options: MenuSheetOptions): Promise<void> => {\n let chosen: (() => void) | undefined;\n const choose = (action: () => void) => {\n chosen = action;\n };\n return dialogCore\n .open<void>(\n (close, context) => (\n <LocaleProvider locale={options.locale}>\n <BottomSheet onDismiss={context.requestDismiss}>\n <BottomSheet.Body>\n {options.top?.(() => close())}\n <SheetMenu\n Items={options.Items}\n items={afterClose(options.items, choose)}\n label={options.label}\n close={() => close()}\n dialog={context.dialog}\n />\n </BottomSheet.Body>\n </BottomSheet>\n </LocaleProvider>\n ),\n { ...bottomSheetOptions, ariaLabel: options.label, history: true, signal: options.signal },\n )\n .then(() => chosen?.());\n};\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["actions/menu-sheet.tsx"],"sourcesContent":["import { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { dialogCore } from \"../feedback/dialog-core\";\nimport { LocaleProvider } from \"../intl/locale\";\nimport BottomSheet, { bottomSheetOptions } from \"../layout/BottomSheet\";\nimport type { DropdownAction, DropdownChoice, DropdownItem, DropdownSearch } from \"./Dropdown\";\nimport { focusMenuRow, focusRowSegment } from \"./menu-rows\";\nimport { filterDropdownItems, MenuSearch } from \"./menu-search\";\n\n/** Phones, where a menu opens as a bottom sheet instead of beside its trigger. */\nexport const PHONE_SHEET_QUERY = \"(max-width: 47.999rem)\";\n\nexport const prefersMenuSheet = (): boolean => typeof matchMedia === \"function\" && matchMedia(PHONE_SHEET_QUERY).matches;\n\ntype SheetEntry = DropdownAction | DropdownChoice;\ntype SheetItem = SheetEntry | { items: readonly SheetEntry[] };\n\n/**\n * Follows a link the way a click on it would, so a page's own link handling, such as progressive navigation, still\n * applies; it runs only after the sheet has left the history.\n */\nconst followLink = (href: string) => {\n const link = document.createElement(\"a\");\n link.href = href;\n link.hidden = true;\n document.body.append(link);\n link.click();\n link.remove();\n};\n\n/**\n * The sheet's items hand their action to `run` instead of running it while the sheet closes: `dialogCore` settles the\n * sheet after it left the history, and only then may an action navigate or open the next dialog.\n * A choice or action that keeps the menu open, or an `immediate` action, runs at once.\n */\nconst afterClose = <T extends DropdownItem>(items: readonly T[], run: (action: () => void) => void): T[] => {\n const entry = (item: SheetEntry): SheetEntry => {\n // A plain link would navigate while the sheet's history entry is still being removed, and that Back could\n // undo the navigation; a plain click instead follows it once the sheet is gone. Modified clicks and external\n // links keep the browser's own behavior.\n if (!item.action && \"href\" in item && item.href && !(\"external\" in item && item.external)) {\n const href = item.href;\n return { ...item, action: () => run(() => followLink(href)) } as SheetEntry;\n }\n const action = item.action;\n if (!action || (\"closeOnSelect\" in item && item.closeOnSelect === false) || (\"immediate\" in item && item.immediate)) return item;\n return { ...item, action: () => run(action) } as SheetEntry;\n };\n return items.map((item) => {\n const sheetItem = item as SheetItem;\n return (\"items\" in sheetItem ? { ...sheetItem, items: sheetItem.items.map(entry) } : entry(sheetItem)) as T;\n });\n};\n\nconst menuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(menu.querySelectorAll<HTMLElement>(\"[role^='menuitem']:not([aria-disabled='true'])\"));\n\n/**\n * The menu inside the sheet: the same items, at touch size, with arrow keys for a connected keyboard. Focus starts on\n * the sheet itself, which shows no ring on a phone; the first arrow key moves it into the menu.\n */\nfunction SheetMenu(props: {\n Items: MenuSheetOptions[\"Items\"];\n items: readonly DropdownItem[];\n label: string;\n close: () => void;\n dialog: HTMLDialogElement;\n icons?: \"plain\" | \"tile\";\n}): JSX.Element {\n let menu!: HTMLDivElement;\n const onKeyDown = (event: KeyboardEvent) => {\n const items = menuItems(menu);\n if (!focusMenuRow(items, event.key) && !focusRowSegment(items, event.key)) return;\n event.preventDefault();\n };\n // Keys pressed on the sheet itself, while it is the top level of the shared dialog.\n const onSheetKeyDown = (event: KeyboardEvent) => {\n if (event.target === props.dialog && props.dialog.lastElementChild?.contains(menu)) onKeyDown(event);\n };\n onMount(() => {\n props.dialog.addEventListener(\"keydown\", onSheetKeyDown);\n onCleanup(() => props.dialog.removeEventListener(\"keydown\", onSheetKeyDown));\n });\n return (\n <div\n ref={menu}\n class=\"k2b-gesture-menu__sheet-menu\"\n role=\"menu\"\n aria-label={props.label}\n data-icons={props.icons === \"tile\" ? \"tile\" : undefined}\n onKeyDown={onKeyDown}\n >\n <props.Items items={props.items} close={() => props.close()} />\n </div>\n );\n}\n\nexport type MenuSheetOptions = {\n /** Renders the rows: the dropdown's own `DropdownItems`, passed in so this module does not import it back. */\n Items: (props: { items: readonly DropdownItem[]; close: (restoreFocus?: boolean) => void }) => JSX.Element;\n items: readonly DropdownItem[];\n /** Names the sheet and its menu. */\n label: string;\n /** The render locale of the opener; the sheet renders outside its tree. */\n locale: string;\n /** Shown above the menu, for example a row of quick reactions. `close` closes the sheet. */\n top?: (close: () => void) => JSX.Element;\n search?: DropdownSearch;\n icons?: \"plain\" | \"tile\";\n /** A quiet sentence below the entries. */\n note?: string;\n signal?: AbortSignal;\n};\n\n/** A searchable menu in the sheet: the sheet's first field, so it takes the focus as the sheet opens, as in the menu. */\nfunction SheetSearch(props: {\n Items: MenuSheetOptions[\"Items\"];\n items: readonly DropdownItem[];\n search: DropdownSearch;\n wrap: <T extends DropdownItem>(items: readonly T[]) => T[];\n label: string;\n locale: string;\n close: () => void;\n icons?: \"plain\" | \"tile\";\n}): JSX.Element {\n const [query, setQuery] = createSignal(\"\");\n const filter = props.search.filter;\n return (\n <div\n class=\"k2b-gesture-menu__sheet-menu\"\n role=\"dialog\"\n aria-label={props.label}\n data-search=\"\"\n data-icons={props.icons === \"tile\" ? \"tile\" : undefined}\n >\n <MenuSearch\n Items={props.Items}\n items={props.items}\n search={{\n ...props.search,\n // The sheet's own items are wrapped already; a host's filter returns its unwrapped entries.\n filter: (value) => (filter ? props.wrap(filter(value)) : filterDropdownItems(props.items, value, props.locale)),\n }}\n label={props.label}\n query={query}\n setQuery={setQuery}\n close={() => props.close()}\n />\n </div>\n );\n}\n\n/** Opens menu items as a bottom sheet; resolves after the sheet closed and the chosen action ran. */\nexport const openMenuSheet = (options: MenuSheetOptions): Promise<void> => {\n let chosen: (() => void) | undefined;\n const choose = (action: () => void) => {\n chosen = action;\n };\n return dialogCore\n .open<void>(\n (close, context) => (\n <LocaleProvider locale={options.locale}>\n <BottomSheet onDismiss={context.requestDismiss}>\n <BottomSheet.Body>\n {options.top?.(() => close())}\n <Show\n when={options.search}\n fallback={\n <SheetMenu\n Items={options.Items}\n items={afterClose(options.items, choose)}\n label={options.label}\n close={() => close()}\n dialog={context.dialog}\n icons={options.icons}\n />\n }\n >\n {(search) => (\n <SheetSearch\n Items={options.Items}\n items={afterClose(options.items, choose)}\n search={search()}\n wrap={(items) => afterClose(items, choose)}\n label={options.label}\n locale={options.locale}\n close={() => close()}\n icons={options.icons}\n />\n )}\n </Show>\n <Show when={options.note}>\n <p class=\"k2b-dropdown__note\">{options.note}</p>\n </Show>\n </BottomSheet.Body>\n </BottomSheet>\n </LocaleProvider>\n ),\n { ...bottomSheetOptions, ariaLabel: options.label, history: true, signal: options.signal },\n )\n .then(() => chosen?.());\n};\n"],"mappings":"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","ignoreList":[]}
@@ -181,6 +181,7 @@ export const CalendarWeekView = props => {
181
181
  let lastWheel = 0;
182
182
  let fingers = 0;
183
183
  let restTimer = 0;
184
+ let restFrame = 0;
184
185
  let ready = false;
185
186
  /** Whether a wheel or a finger moved the strip since it last settled, and the way it last moved. */
186
187
  let gesture = false;
@@ -204,6 +205,15 @@ export const CalendarWeekView = props => {
204
205
  settling = false;
205
206
  }, 450);
206
207
  };
208
+ /**
209
+ * A busy page runs the rest timer before the scroll event of a reveal still on its way, and a settle then would stop
210
+ * that reveal short of what it brings into view. Two frames tell: a scroll still moving reports before them and starts over.
211
+ */
212
+ const settleOnceStill = () => {
213
+ restFrame = requestAnimationFrame(() => {
214
+ restFrame = requestAnimationFrame(settle);
215
+ });
216
+ };
207
217
  const onWheel = event => {
208
218
  if (!scroller || event.ctrlKey || view.dragging()) return;
209
219
  // A gesture that goes on takes over from a glide onto a day start; the strip settles again once it rests.
@@ -262,7 +272,8 @@ export const CalendarWeekView = props => {
262
272
  rememberAnchor();
263
273
  });
264
274
  window.clearTimeout(restTimer);
265
- restTimer = window.setTimeout(settle, fingers > 0 ? 160 : 140);
275
+ cancelAnimationFrame(restFrame);
276
+ restTimer = window.setTimeout(settleOnceStill, fingers > 0 ? 160 : 140);
266
277
  };
267
278
 
268
279
  // Days loaded before the strip's first day shift it; the view keeps its days in place.
@@ -339,6 +350,7 @@ export const CalendarWeekView = props => {
339
350
  onCleanup(() => {
340
351
  observer?.disconnect();
341
352
  window.clearTimeout(restTimer);
353
+ cancelAnimationFrame(restFrame);
342
354
  window.clearTimeout(reportTimer);
343
355
  window.clearTimeout(glideTimer);
344
356
  if (frame) cancelAnimationFrame(frame);