@fusion.dev/helix-headless 0.143.0 → 0.145.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 (314) hide show
  1. package/custom-elements.json +16990 -1383
  2. package/dist/accordion/item/accordion-item.d.ts +48 -12
  3. package/dist/accordion/item/accordion-item.js +26 -5
  4. package/dist/accordion/root/accordion-root.d.ts +6 -0
  5. package/dist/accordion/root/accordion-root.js +7 -1
  6. package/dist/announcement/root/announcement-root.d.ts +21 -1
  7. package/dist/announcement/root/announcement-root.js +36 -1
  8. package/dist/app-header/root/app-header-root.d.ts +7 -0
  9. package/dist/app-header/root/app-header-root.js +5 -1
  10. package/dist/app-header/sectioning.d.ts +7 -0
  11. package/dist/app-header/sectioning.js +26 -0
  12. package/dist/area-chart/root/area-chart-root.d.ts +12 -4
  13. package/dist/area-chart/root/area-chart-root.js +24 -7
  14. package/dist/avatar/root/avatar-root.d.ts +14 -0
  15. package/dist/avatar/root/avatar-root.js +23 -9
  16. package/dist/badge/root/badge-root.d.ts +11 -7
  17. package/dist/badge/root/badge-root.js +1 -1
  18. package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
  19. package/dist/bar-chart/root/bar-chart-root.js +17 -10
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +32 -1
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +25 -2
  22. package/dist/button/root/button-root.d.ts +47 -1
  23. package/dist/button/root/button-root.js +38 -4
  24. package/dist/callout/root/callout-root.d.ts +18 -1
  25. package/dist/callout/root/callout-root.js +15 -4
  26. package/dist/canvas/root/canvas-root.d.ts +38 -3
  27. package/dist/canvas/root/canvas-root.js +121 -40
  28. package/dist/card/root/card-root.d.ts +30 -1
  29. package/dist/card/root/card-root.js +38 -5
  30. package/dist/carousel/root/carousel-root.d.ts +90 -0
  31. package/dist/carousel/root/carousel-root.js +134 -12
  32. package/dist/chat/composer/chat-composer.d.ts +38 -6
  33. package/dist/chat/composer/chat-composer.js +42 -9
  34. package/dist/chat/message/chat-message.d.ts +32 -1
  35. package/dist/chat/message/chat-message.js +25 -9
  36. package/dist/chat/naming.d.ts +22 -0
  37. package/dist/chat/naming.js +12 -0
  38. package/dist/chat/relative-clock.d.ts +39 -0
  39. package/dist/chat/relative-clock.js +31 -0
  40. package/dist/chat/root/chat-root.d.ts +36 -0
  41. package/dist/chat/root/chat-root.js +24 -1
  42. package/dist/checkbox/root/checkbox-root.js +2 -0
  43. package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
  44. package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
  45. package/dist/code-block/lines.d.ts +13 -0
  46. package/dist/code-block/lines.js +2 -0
  47. package/dist/code-block/root/code-block-root.d.ts +38 -1
  48. package/dist/code-block/root/code-block-root.js +28 -7
  49. package/dist/color-input/root/color-input-root.d.ts +34 -1
  50. package/dist/color-input/root/color-input-root.js +23 -4
  51. package/dist/color-picker/channels.d.ts +26 -0
  52. package/dist/color-picker/channels.js +84 -0
  53. package/dist/color-picker/root/color-picker-root.d.ts +81 -5
  54. package/dist/color-picker/root/color-picker-root.js +103 -10
  55. package/dist/combobox/root/combobox-root.d.ts +38 -3
  56. package/dist/combobox/root/combobox-root.js +64 -2
  57. package/dist/command/root/command-root.d.ts +30 -4
  58. package/dist/command/root/command-root.js +33 -3
  59. package/dist/copy-button/from.d.ts +22 -0
  60. package/dist/copy-button/from.js +28 -0
  61. package/dist/copy-button/root/copy-button-root.d.ts +47 -1
  62. package/dist/copy-button/root/copy-button-root.js +44 -8
  63. package/dist/data-table/root/data-table-root.d.ts +150 -2
  64. package/dist/data-table/root/data-table-root.js +303 -29
  65. package/dist/data-table/tree.d.ts +40 -0
  66. package/dist/data-table/tree.js +55 -0
  67. package/dist/date-input/root/date-input-root.d.ts +73 -4
  68. package/dist/date-input/root/date-input-root.js +93 -43
  69. package/dist/date-picker/root/date-picker-root.d.ts +145 -1
  70. package/dist/date-picker/root/date-picker-root.js +239 -48
  71. package/dist/description-list/item/description-list-item.d.ts +17 -2
  72. package/dist/description-list/item/description-list-item.js +15 -2
  73. package/dist/description-list/root/description-list-root.d.ts +27 -1
  74. package/dist/description-list/root/description-list-root.js +26 -1
  75. package/dist/dialog/description.d.ts +9 -0
  76. package/dist/dialog/description.js +14 -0
  77. package/dist/dialog/root/dialog-root.d.ts +64 -1
  78. package/dist/dialog/root/dialog-root.js +49 -23
  79. package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
  80. package/dist/donut-chart/root/donut-chart-root.js +21 -6
  81. package/dist/drawer/root/drawer-root.d.ts +63 -2
  82. package/dist/drawer/root/drawer-root.js +95 -19
  83. package/dist/drawer/swipe-start.d.ts +9 -0
  84. package/dist/drawer/swipe-start.js +12 -0
  85. package/dist/drilldown/item/drilldown-item.d.ts +11 -6
  86. package/dist/drilldown/item/drilldown-item.js +8 -5
  87. package/dist/drilldown/root/drilldown-root.d.ts +11 -0
  88. package/dist/drilldown/root/drilldown-root.js +8 -3
  89. package/dist/element-events.generated.js +61 -13
  90. package/dist/empty/root/empty-root.d.ts +1 -1
  91. package/dist/file-input/root/file-input-root.d.ts +47 -0
  92. package/dist/file-input/root/file-input-root.js +60 -22
  93. package/dist/file-input/screen.d.ts +35 -0
  94. package/dist/file-input/screen.js +49 -0
  95. package/dist/filter-input/menu/filter-menu.d.ts +3 -0
  96. package/dist/filter-input/menu/filter-menu.js +8 -4
  97. package/dist/filter-input/root/filter-input-root.d.ts +38 -4
  98. package/dist/filter-input/root/filter-input-root.js +67 -3
  99. package/dist/filter-input/suggest.d.ts +17 -0
  100. package/dist/filter-input/suggest.js +23 -0
  101. package/dist/format-date/options.d.ts +33 -0
  102. package/dist/format-date/options.js +42 -0
  103. package/dist/format-date/root/format-date-root.d.ts +51 -0
  104. package/dist/format-date/root/format-date-root.js +58 -38
  105. package/dist/heatmap/root/heatmap-root.d.ts +23 -6
  106. package/dist/heatmap/root/heatmap-root.js +24 -13
  107. package/dist/icon/index.d.ts +2 -0
  108. package/dist/icon/index.js +2 -0
  109. package/dist/icon/registry.d.ts +37 -0
  110. package/dist/icon/registry.js +83 -0
  111. package/dist/icon/root/icon-root.d.ts +30 -4
  112. package/dist/icon/root/icon-root.js +41 -6
  113. package/dist/icon/root/index.d.ts +2 -0
  114. package/dist/icon/root/index.js +2 -0
  115. package/dist/icon/set.d.ts +1 -0
  116. package/dist/icon/set.js +3 -0
  117. package/dist/image/root/image-root.d.ts +14 -0
  118. package/dist/image/root/image-root.js +23 -2
  119. package/dist/input/root/input-root.d.ts +11 -0
  120. package/dist/input/root/input-root.js +16 -1
  121. package/dist/kbd/root/kbd-root.d.ts +20 -13
  122. package/dist/kbd/root/kbd-root.js +35 -37
  123. package/dist/kbd/shortcut.d.ts +18 -0
  124. package/dist/kbd/shortcut.js +35 -0
  125. package/dist/legend/hidden-series.d.ts +8 -0
  126. package/dist/legend/hidden-series.js +13 -0
  127. package/dist/legend/root/legend-root.d.ts +56 -0
  128. package/dist/legend/root/legend-root.js +54 -5
  129. package/dist/lightbox/item/lightbox-item.d.ts +15 -0
  130. package/dist/lightbox/item/lightbox-item.js +10 -1
  131. package/dist/lightbox/root/lightbox-root.d.ts +61 -1
  132. package/dist/lightbox/root/lightbox-root.js +94 -4
  133. package/dist/line-chart/root/line-chart-root.d.ts +11 -3
  134. package/dist/line-chart/root/line-chart-root.js +19 -5
  135. package/dist/list/item/list-item.d.ts +68 -1
  136. package/dist/list/item/list-item.js +66 -4
  137. package/dist/list/numbering.d.ts +14 -0
  138. package/dist/list/numbering.js +8 -0
  139. package/dist/list/root/list-root.d.ts +106 -9
  140. package/dist/list/root/list-root.js +189 -4
  141. package/dist/list/selection.d.ts +19 -0
  142. package/dist/list/selection.js +21 -0
  143. package/dist/listbox/root/listbox-root.d.ts +74 -3
  144. package/dist/listbox/root/listbox-root.js +86 -9
  145. package/dist/loader/busy.d.ts +17 -0
  146. package/dist/loader/busy.js +24 -0
  147. package/dist/loader/root/loader-root.d.ts +37 -0
  148. package/dist/loader/root/loader-root.js +52 -3
  149. package/dist/map/root/map-root.d.ts +6 -2
  150. package/dist/map/root/map-root.js +5 -2
  151. package/dist/megamenu/root/megamenu-root.d.ts +21 -0
  152. package/dist/megamenu/root/megamenu-root.js +16 -2
  153. package/dist/menu/index.d.ts +1 -0
  154. package/dist/menu/index.js +1 -0
  155. package/dist/menu/item/menu-item.d.ts +38 -11
  156. package/dist/menu/item/menu-item.js +33 -1
  157. package/dist/menu/root/menu-root.d.ts +25 -1
  158. package/dist/menu/root/menu-root.js +63 -6
  159. package/dist/menu/section/menu-section.d.ts +55 -0
  160. package/dist/menu/section/menu-section.js +60 -0
  161. package/dist/meter/root/meter-root.d.ts +7 -0
  162. package/dist/meter/root/meter-root.js +6 -3
  163. package/dist/navigation/root/navigation-root.d.ts +16 -1
  164. package/dist/navigation/root/navigation-root.js +27 -1
  165. package/dist/notifications/feed-keys.d.ts +10 -0
  166. package/dist/notifications/feed-keys.js +14 -0
  167. package/dist/notifications/notification/notification.d.ts +17 -0
  168. package/dist/notifications/notification/notification.js +21 -2
  169. package/dist/notifications/root/notifications-root.d.ts +60 -0
  170. package/dist/notifications/root/notifications-root.js +76 -1
  171. package/dist/number-input/root/number-input-root.d.ts +47 -0
  172. package/dist/number-input/root/number-input-root.js +58 -22
  173. package/dist/object-inspector/root/object-inspector-root.d.ts +98 -16
  174. package/dist/object-inspector/root/object-inspector-root.js +180 -80
  175. package/dist/object-inspector/rows.d.ts +57 -0
  176. package/dist/object-inspector/rows.js +86 -0
  177. package/dist/option/root/option-root.d.ts +54 -5
  178. package/dist/option/root/option-root.js +34 -5
  179. package/dist/outline/levels.d.ts +31 -0
  180. package/dist/outline/levels.js +23 -0
  181. package/dist/outline/root/outline-root.d.ts +68 -3
  182. package/dist/outline/root/outline-root.js +73 -8
  183. package/dist/pagination/size/pagination-size.d.ts +11 -2
  184. package/dist/pagination/size/pagination-size.js +19 -4
  185. package/dist/pin-input/format.d.ts +7 -0
  186. package/dist/pin-input/format.js +16 -0
  187. package/dist/pin-input/root/pin-input-root.d.ts +21 -0
  188. package/dist/pin-input/root/pin-input-root.js +29 -2
  189. package/dist/popover/root/popover-root.d.ts +62 -4
  190. package/dist/popover/root/popover-root.js +53 -5
  191. package/dist/progress/root/progress-root.d.ts +7 -0
  192. package/dist/progress/root/progress-root.js +9 -3
  193. package/dist/qr-code/export.d.ts +31 -0
  194. package/dist/qr-code/export.js +10 -0
  195. package/dist/qr-code/root/qr-code-root.d.ts +26 -0
  196. package/dist/qr-code/root/qr-code-root.js +49 -2
  197. package/dist/radio/group/radio-group.d.ts +17 -0
  198. package/dist/radio/group/radio-group.js +14 -1
  199. package/dist/rating/root/rating-root.d.ts +25 -0
  200. package/dist/rating/root/rating-root.js +46 -4
  201. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  202. package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
  203. package/dist/scroll-area/root/scroll-area-root.d.ts +113 -0
  204. package/dist/scroll-area/root/scroll-area-root.js +218 -3
  205. package/dist/scroll-area/scrollbar.d.ts +65 -0
  206. package/dist/scroll-area/scrollbar.js +24 -0
  207. package/dist/select/root/select-root.d.ts +24 -0
  208. package/dist/select/root/select-root.js +21 -7
  209. package/dist/skeleton/root/skeleton-root.d.ts +25 -0
  210. package/dist/skeleton/root/skeleton-root.js +17 -2
  211. package/dist/slider/root/slider-root.d.ts +39 -0
  212. package/dist/slider/root/slider-root.js +45 -9
  213. package/dist/splitter/root/splitter-root.d.ts +13 -0
  214. package/dist/splitter/root/splitter-root.js +20 -2
  215. package/dist/splitter/snap.d.ts +9 -0
  216. package/dist/splitter/snap.js +12 -0
  217. package/dist/stat/root/stat-root.d.ts +2 -0
  218. package/dist/stat/root/stat-root.js +1 -1
  219. package/dist/steps/reach.d.ts +15 -0
  220. package/dist/steps/reach.js +12 -0
  221. package/dist/steps/root/steps-root.d.ts +30 -0
  222. package/dist/steps/root/steps-root.js +53 -0
  223. package/dist/steps/step/step.d.ts +39 -2
  224. package/dist/steps/step/step.js +29 -7
  225. package/dist/switch/root/switch-root.d.ts +12 -2
  226. package/dist/switch/root/switch-root.js +6 -1
  227. package/dist/table/column/table-column.d.ts +11 -0
  228. package/dist/table/column/table-column.js +6 -1
  229. package/dist/table/root/table-root.d.ts +101 -0
  230. package/dist/table/root/table-root.js +166 -5
  231. package/dist/table/row/table-row.d.ts +47 -1
  232. package/dist/table/row/table-row.js +39 -2
  233. package/dist/tabs/panel/tab-panel.d.ts +9 -5
  234. package/dist/tabs/panel/tab-panel.js +3 -4
  235. package/dist/tabs/root/tabs-root.d.ts +25 -1
  236. package/dist/tabs/root/tabs-root.js +21 -3
  237. package/dist/tabs/tab/tab.d.ts +36 -4
  238. package/dist/tabs/tab/tab.js +46 -3
  239. package/dist/tag/group/tag-group.d.ts +107 -0
  240. package/dist/tag/group/tag-group.js +157 -0
  241. package/dist/tag/index.d.ts +1 -0
  242. package/dist/tag/index.js +1 -0
  243. package/dist/tag/root/tag-root.d.ts +60 -0
  244. package/dist/tag/root/tag-root.js +74 -18
  245. package/dist/tag/selection.d.ts +10 -0
  246. package/dist/tag/selection.js +12 -0
  247. package/dist/textarea/grow.d.ts +22 -0
  248. package/dist/textarea/grow.js +20 -0
  249. package/dist/textarea/root/textarea-root.d.ts +9 -0
  250. package/dist/textarea/root/textarea-root.js +9 -1
  251. package/dist/time-input/root/time-input-root.d.ts +44 -5
  252. package/dist/time-input/root/time-input-root.js +69 -39
  253. package/dist/time-picker/root/time-picker-root.d.ts +54 -8
  254. package/dist/time-picker/root/time-picker-root.js +121 -91
  255. package/dist/timeline/group/timeline-group.d.ts +46 -0
  256. package/dist/timeline/group/timeline-group.js +37 -0
  257. package/dist/timeline/index.d.ts +1 -0
  258. package/dist/timeline/index.js +1 -0
  259. package/dist/timeline/root/timeline-root.d.ts +10 -3
  260. package/dist/toast/hotkey.d.ts +22 -0
  261. package/dist/toast/hotkey.js +23 -0
  262. package/dist/toast/item/toast-item.d.ts +26 -18
  263. package/dist/toast/item/toast-item.js +55 -48
  264. package/dist/toast/root/toast-root.d.ts +42 -8
  265. package/dist/toast/root/toast-root.js +64 -4
  266. package/dist/toggle/group/toggle-group.d.ts +57 -15
  267. package/dist/toggle/group/toggle-group.js +69 -9
  268. package/dist/toggle/root/toggle-root.d.ts +12 -1
  269. package/dist/toggle/root/toggle-root.js +11 -3
  270. package/dist/toolbar/root/toolbar-root.d.ts +13 -0
  271. package/dist/toolbar/root/toolbar-root.js +15 -5
  272. package/dist/tooltip/focus-target.d.ts +6 -0
  273. package/dist/tooltip/focus-target.js +9 -0
  274. package/dist/tooltip/root/tooltip-root.d.ts +42 -17
  275. package/dist/tooltip/root/tooltip-root.js +50 -9
  276. package/dist/tour/naming.d.ts +30 -0
  277. package/dist/tour/naming.js +10 -0
  278. package/dist/tour/root/tour-root.d.ts +35 -0
  279. package/dist/tour/root/tour-root.js +56 -5
  280. package/dist/tour/stop/tour-stop.d.ts +53 -8
  281. package/dist/tour/stop/tour-stop.js +45 -15
  282. package/dist/tour/wait.d.ts +11 -0
  283. package/dist/tour/wait.js +19 -0
  284. package/dist/tree/item/tree-item.d.ts +54 -1
  285. package/dist/tree/item/tree-item.js +89 -20
  286. package/dist/tree/root/tree-root.d.ts +37 -0
  287. package/dist/tree/root/tree-root.js +51 -6
  288. package/dist/tree/selection.d.ts +24 -0
  289. package/dist/tree/selection.js +26 -0
  290. package/dist/trend/root/trend-root.d.ts +13 -0
  291. package/dist/trend/root/trend-root.js +14 -9
  292. package/dist/video/media-events.d.ts +49 -0
  293. package/dist/video/media-events.js +39 -0
  294. package/dist/video/root/video-root.d.ts +99 -13
  295. package/dist/video/root/video-root.js +84 -47
  296. package/dist/video-playlist/menu/video-playlist-menu.d.ts +3 -0
  297. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  298. package/dist/video-playlist/order.d.ts +41 -0
  299. package/dist/video-playlist/order.js +34 -0
  300. package/dist/video-playlist/root/video-playlist-root.d.ts +70 -2
  301. package/dist/video-playlist/root/video-playlist-root.js +60 -16
  302. package/dist/virtual-list/controls.d.ts +4 -0
  303. package/dist/virtual-list/controls.js +30 -0
  304. package/dist/virtual-list/fill.d.ts +30 -0
  305. package/dist/virtual-list/fill.js +39 -0
  306. package/dist/virtual-list/keys.d.ts +15 -0
  307. package/dist/virtual-list/keys.js +24 -0
  308. package/dist/virtual-list/root/virtual-list-root.d.ts +155 -23
  309. package/dist/virtual-list/root/virtual-list-root.js +175 -34
  310. package/lint.json +1 -1
  311. package/package.json +5 -5
  312. package/types/jsx.d.ts +8871 -1286
  313. package/types/svelte.d.ts +3 -0
  314. package/types/vue.d.ts +1918 -269
@@ -4,11 +4,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, drawIcon, fillTemplate, HelixBeforeChangeEvent, HelixFormElement, MixinField, requestChange, restoreValue, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, drawIcon, drawStatus, fillTemplate, formatNumbers, HelixBeforeChangeEvent, HelixFormElement, HelixRejectEvent, MixinField, requestChange, restoreValue, StatusController, } from '@fusion.dev/helix-core';
8
8
  import { trash } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { html } from 'lit/static-html.js';
12
+ import { screenFiles } from '../screen.js';
12
13
  import '../../tag/index.js';
13
14
  const named = (label, name) => label.includes('{name}') ? fillTemplate(label, { name }) : `${label} ${name}`;
14
15
  const size = (bytes, locale) => {
@@ -33,22 +34,6 @@ const kind = (file) => {
33
34
  const subtype = file.type.split('/')[1]?.replace(/^x-/, '').split('+')[0] ?? '';
34
35
  return /^[a-z0-9]{1,6}$/i.test(subtype) ? subtype.toUpperCase() : 'File';
35
36
  };
36
- const accepts = (file, accept) => {
37
- const tokens = accept
38
- .split(',')
39
- .map((token) => token.trim().toLowerCase())
40
- .filter(Boolean);
41
- if (tokens.length === 0) {
42
- return true;
43
- }
44
- const name = file.name.toLowerCase();
45
- const type = file.type.toLowerCase();
46
- return tokens.some((token) => token.startsWith('.')
47
- ? name.endsWith(token)
48
- : token.endsWith('/*')
49
- ? type.startsWith(token.slice(0, -1))
50
- : type === token);
51
- };
52
37
  let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(HelixFormElement) {
53
38
  constructor() {
54
39
  super(...arguments);
@@ -57,15 +42,26 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
57
42
  target: () => this.partNamed('control'),
58
43
  });
59
44
  this.accept = '';
45
+ this.directory = false;
60
46
  this.locale = '';
61
47
  this.multiple = false;
48
+ this.rejectedText = 'Not added: {names}';
62
49
  this.removeLabel = 'Remove {name}';
63
50
  this.triggerLabel = 'Choose files';
51
+ this.status = new StatusController(this);
52
+ this.handlePaste = (event) => {
53
+ const pasted = [...(event.clipboardData?.files ?? [])];
54
+ if (this.isDisabled || pasted.length === 0) {
55
+ return;
56
+ }
57
+ event.preventDefault();
58
+ this.take(pasted, this.multiple ? this.files : []);
59
+ };
64
60
  this.dropping = false;
65
61
  this.committed = [];
66
62
  this.handleChange = (event) => {
67
63
  event.stopPropagation();
68
- this.commit(this.files);
64
+ this.take(this.files, [], true);
69
65
  };
70
66
  this.handleDragOver = (event) => {
71
67
  if (this.isDisabled) {
@@ -84,11 +80,11 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
84
80
  return;
85
81
  }
86
82
  event.preventDefault();
87
- const dropped = [...event.dataTransfer.files].filter((file) => accepts(file, this.accept));
83
+ const dropped = [...event.dataTransfer.files];
88
84
  if (dropped.length === 0) {
89
85
  return;
90
86
  }
91
- this.commit(this.multiple ? [...this.files, ...dropped] : dropped.slice(-1));
87
+ this.take(dropped, this.multiple ? this.files : []);
92
88
  };
93
89
  this.handleRemove = (event) => {
94
90
  const at = Number(event.currentTarget.dataset.at);
@@ -102,6 +98,30 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
102
98
  });
103
99
  };
104
100
  }
101
+ take(arriving, keeping, picked = false) {
102
+ const { kept, refused } = screenFiles(arriving, keeping, {
103
+ accept: this.accept,
104
+ maxFileSize: this.maxFileSize,
105
+ maxFiles: this.maxFiles,
106
+ multiple: this.multiple || picked,
107
+ validate: this.validate,
108
+ });
109
+ const same = (one, two) => one.length === two.length && one.every((file, at) => file === two[at]);
110
+ if (picked || !(same(kept, this.committed) && same(kept, this.files))) {
111
+ this.commit(kept);
112
+ }
113
+ if (refused.length > 0) {
114
+ this.refuse(refused);
115
+ }
116
+ }
117
+ refuse(refused) {
118
+ this.dispatchEvent(new HelixRejectEvent({ files: refused }));
119
+ const words = fillTemplate(this.rejectedText, {
120
+ count: formatNumbers(this.lang)(refused.length),
121
+ names: new Intl.ListFormat(this.lang || undefined, { style: 'short', type: 'unit' }).format(refused.map((one) => one.file.name)),
122
+ });
123
+ this.status.say(words);
124
+ }
105
125
  get files() {
106
126
  return [...(this.control?.files ?? [])];
107
127
  }
@@ -190,10 +210,10 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
190
210
  }
191
211
  renderMeta(file) {
192
212
  return html `<${this.tag('tag')} part="size"
193
- exportparts="dismiss: size-dismiss, dismiss-mark: size-dismiss-mark, label: size-label, start: size-start">${size(file.size, this.locale)}</${this.tag('tag')}>
213
+ exportparts="cell: size-cell, dismiss: size-dismiss, dismiss-mark: size-dismiss-mark, label: size-label, link: size-link, start: size-start">${size(file.size, this.locale)}</${this.tag('tag')}>
194
214
  <${this.tag('tag')}
195
215
  part="kind"
196
- exportparts="dismiss: kind-dismiss, dismiss-mark: kind-dismiss-mark, label: kind-label, start: kind-start">${kind(file)}</${this.tag('tag')}>`;
216
+ exportparts="cell: kind-cell, dismiss: kind-dismiss, dismiss-mark: kind-dismiss-mark, label: kind-label, link: kind-link, start: kind-start">${kind(file)}</${this.tag('tag')}>`;
197
217
  }
198
218
  render() {
199
219
  const files = this.files;
@@ -204,6 +224,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
204
224
  @dragleave=${this.handleDragLeave}
205
225
  @dragover=${this.handleDragOver}
206
226
  @drop=${this.handleDrop}
227
+ @paste=${this.handlePaste}
207
228
  >
208
229
  <input
209
230
  part="control"
@@ -217,6 +238,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
217
238
  ?disabled=${this.isDisabled}
218
239
  ?multiple=${this.multiple}
219
240
  ?required=${this.required}
241
+ ?webkitdirectory=${this.directory}
220
242
  @change=${this.handleChange}
221
243
  @input=${this.stopInner}
222
244
  />
@@ -228,6 +250,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
228
250
  one ordering decision this component makes differently from the way it
229
251
  reads. Reasoning above renderMeta. -->
230
252
  ${this.renderHint()}${this.renderFeedback()}
253
+ ${drawStatus(this.status)}
231
254
  ${files.length === 0
232
255
  ? nothing
233
256
  : html `<ul part="list">
@@ -258,18 +281,33 @@ __decorate([
258
281
  __decorate([
259
282
  property()
260
283
  ], HelixHeadlessFileInput.prototype, "capture", void 0);
284
+ __decorate([
285
+ property({ type: Boolean })
286
+ ], HelixHeadlessFileInput.prototype, "directory", void 0);
261
287
  __decorate([
262
288
  property()
263
289
  ], HelixHeadlessFileInput.prototype, "locale", void 0);
290
+ __decorate([
291
+ property({ attribute: 'max-files', type: Number })
292
+ ], HelixHeadlessFileInput.prototype, "maxFiles", void 0);
293
+ __decorate([
294
+ property({ attribute: 'max-file-size', type: Number })
295
+ ], HelixHeadlessFileInput.prototype, "maxFileSize", void 0);
264
296
  __decorate([
265
297
  property({ reflect: true, type: Boolean })
266
298
  ], HelixHeadlessFileInput.prototype, "multiple", void 0);
299
+ __decorate([
300
+ property({ attribute: 'rejected-text' })
301
+ ], HelixHeadlessFileInput.prototype, "rejectedText", void 0);
267
302
  __decorate([
268
303
  property({ attribute: 'remove-label' })
269
304
  ], HelixHeadlessFileInput.prototype, "removeLabel", void 0);
270
305
  __decorate([
271
306
  property({ attribute: 'trigger-label' })
272
307
  ], HelixHeadlessFileInput.prototype, "triggerLabel", void 0);
308
+ __decorate([
309
+ property({ attribute: false })
310
+ ], HelixHeadlessFileInput.prototype, "validate", void 0);
273
311
  __decorate([
274
312
  state()
275
313
  ], HelixHeadlessFileInput.prototype, "dropping", void 0);
@@ -0,0 +1,35 @@
1
+ import { type RefusedFile } from '@fusion.dev/helix-core';
2
+ /**
3
+ * Which of the files arriving at a file field it takes, and why it refuses the
4
+ * rest: the rules `accept`, `max-file-size`, `max-files` and `validate` set.
5
+ * Zag's file upload screens a drop the same way and reports each refused file
6
+ * with its reason.
7
+ */
8
+ export type { Refusal, RefusedFile } from '@fusion.dev/helix-core';
9
+ /** The rules a field screens arriving files by. */
10
+ export interface ScreenRules {
11
+ /** A native `accept` list. Empty takes every type. */
12
+ readonly accept: string;
13
+ /** The most bytes one file may have. */
14
+ readonly maxFileSize?: number | undefined;
15
+ /** The most files the field holds, counting the ones it has. */
16
+ readonly maxFiles?: number | undefined;
17
+ /** Whether the field holds more than one file. */
18
+ readonly multiple: boolean;
19
+ /** Your own check, which returns why a file is refused, or nothing. */
20
+ readonly validate?: ((file: File) => string | null | undefined) | undefined;
21
+ }
22
+ /**
23
+ * Whether a file is one `accept` lists: an extension, a type, or a family
24
+ * such as `image/*`, as the platform reads it.
25
+ */
26
+ export declare const accepts: (file: File, accept: string) => boolean;
27
+ /**
28
+ * Screens arriving files against the rules, given the files the field keeps.
29
+ * `kept` is what the field holds afterward: the files it keeps and the
30
+ * arrivals it took, or for a single-file field the last arrival it took.
31
+ */
32
+ export declare const screenFiles: (arriving: File[], keeping: File[], rules: ScreenRules) => {
33
+ kept: File[];
34
+ refused: RefusedFile[];
35
+ };
@@ -0,0 +1,49 @@
1
+ import { commaList } from '@fusion.dev/helix-core';
2
+ export const accepts = (file, accept) => {
3
+ const tokens = commaList(accept).map((token) => token.toLowerCase());
4
+ if (tokens.length === 0) {
5
+ return true;
6
+ }
7
+ const name = file.name.toLowerCase();
8
+ const type = file.type.toLowerCase();
9
+ return tokens.some((token) => token.startsWith('.')
10
+ ? name.endsWith(token)
11
+ : token.endsWith('/*')
12
+ ? type.startsWith(token.slice(0, -1))
13
+ : type === token);
14
+ };
15
+ const refusal = (file, rules) => {
16
+ if (!accepts(file, rules.accept)) {
17
+ return { file, reason: 'type' };
18
+ }
19
+ if (rules.maxFileSize !== undefined && file.size > rules.maxFileSize) {
20
+ return { file, reason: 'size' };
21
+ }
22
+ const message = rules.validate?.(file);
23
+ return message ? { file, message, reason: 'invalid' } : undefined;
24
+ };
25
+ export const screenFiles = (arriving, keeping, rules) => {
26
+ const refused = [];
27
+ const taken = [];
28
+ for (const file of arriving) {
29
+ const refusedFor = refusal(file, rules);
30
+ if (refusedFor) {
31
+ refused.push(refusedFor);
32
+ }
33
+ else {
34
+ taken.push(file);
35
+ }
36
+ }
37
+ if (!rules.multiple) {
38
+ const last = taken.at(-1);
39
+ for (const file of taken.slice(0, -1)) {
40
+ refused.push({ file, reason: 'count' });
41
+ }
42
+ return { kept: last ? [last] : keeping.slice(0, 1), refused };
43
+ }
44
+ const room = rules.maxFiles === undefined ? taken.length : Math.max(0, rules.maxFiles - keeping.length);
45
+ for (const file of taken.slice(room)) {
46
+ refused.push({ file, reason: 'count' });
47
+ }
48
+ return { kept: [...keeping, ...taken.slice(0, room)], refused };
49
+ };
@@ -66,8 +66,11 @@ import '../../menu/index.js';
66
66
  * @csspart divider-content - The content set into the line.
67
67
  * @csspart divider-line - The line itself.
68
68
  * @csspart entry - One row: a field, its comparison, an operator, or a value.
69
+ * @csspart entry-end - The trailing column of a row, which holds its shortcut.
69
70
  * @csspart entry-item - The row itself.
70
71
  * @csspart entry-mark - The mark at the end of a row: a check mark on a chosen value, and a caret on a field.
72
+ * @csspart entry-shortcut - The key drawn for a row's shortcut. The filter menu sets none, so it renders only where a subclass does.
73
+ * @csspart entry-shortcut-kbd - The key inside it.
71
74
  * @csspart entry-start - The leading column.
72
75
  * @csspart menu - The menu the fields are offered in.
73
76
  * @csspart menu-back - The button that goes back from a field's page.
@@ -137,7 +137,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
137
137
  const offered = using?.arity === 'none' ? [] : values;
138
138
  return html `<${item}
139
139
  part="entry"
140
- exportparts="item: entry-item, mark: entry-mark, start: entry-start"
140
+ exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
141
+ shortcut-kbd: entry-shortcut-kbd, start: entry-start"
141
142
  data-facet="field"
142
143
  data-field=${name}
143
144
  >
@@ -145,7 +146,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
145
146
  ${operators.length > 1
146
147
  ? html `<${item}
147
148
  part="entry"
148
- exportparts="item: entry-item, mark: entry-mark, start: entry-start"
149
+ exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
150
+ shortcut-kbd: entry-shortcut-kbd, start: entry-start"
149
151
  slot="submenu"
150
152
  data-facet="comparison"
151
153
  data-field=${name}
@@ -153,7 +155,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
153
155
  ${using?.label ?? ''}
154
156
  ${operators.map((operator) => html `<${item}
155
157
  part="entry"
156
- exportparts="item: entry-item, mark: entry-mark, start: entry-start"
158
+ exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
159
+ shortcut-kbd: entry-shortcut-kbd, start: entry-start"
157
160
  slot="submenu"
158
161
  checkable
159
162
  exclusive
@@ -175,7 +178,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
175
178
  : nothing}
176
179
  ${offered.map((option) => html `<${item}
177
180
  part="entry"
178
- exportparts="item: entry-item, mark: entry-mark, start: entry-start"
181
+ exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
182
+ shortcut-kbd: entry-shortcut-kbd, start: entry-start"
179
183
  slot="submenu"
180
184
  checkable
181
185
  ?exclusive=${using?.arity === 'one'}
@@ -13,8 +13,10 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
13
13
  * its tokens show a selection it owns, drawn from the options it was given.
14
14
  *
15
15
  * `value` is the typed text. Every keystroke fires `input` with the text on the
16
- * element, so a table can narrow as the user types. Tokens are not reported,
17
- * because they are markup you already hold. Backspace in an empty field fires
16
+ * element, so a table can narrow as the user types. A form receives the text
17
+ * under `name`, and then one more entry under `name` for each token: its
18
+ * `value` attribute, or for a `FilterChip`, its field and values written
19
+ * `field:values` as the field reads them typed. Backspace in an empty field fires
18
20
  * `helix-dismiss` on the last token, the same event its remove control fires,
19
21
  * so one handler covers the pointer and the keyboard.
20
22
  *
@@ -38,8 +40,11 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
38
40
  * moves through them with the arrow keys, and presses one on Enter. The option
39
41
  * fires its own `helix-select`, and you decide what choosing it means. Keys
40
42
  * before a colon and values after it use the same panel with different
41
- * contents. `readFilterText` is exported so you can narrow the panel against
42
- * partly typed text. The panel opens when it has options and closes when it has
43
+ * contents. `filter-suggestions` narrows the panel to the options whose words
44
+ * hold the term at the caret, after its colon, ignoring case and accents, and
45
+ * hides the rest. `readFilterText` is exported so you can narrow it yourself
46
+ * against partly typed text. While the panel is open, a reader hears how many
47
+ * suggestions it offers. The panel opens when it has options and closes when it has
43
48
  * none, and a choice does not close it, so after picking a key you see its
44
49
  * values next. Use `selectionStart` and `setSelectionRange` to write a
45
50
  * completion back and put the caret after it, or the next parse reads the wrong
@@ -93,6 +98,7 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
93
98
  * @csspart hint - The help text under the field, shown while there is no error.
94
99
  * @csspart label - The label above the field.
95
100
  * @csspart lead - The box before the text that holds the `start` slot.
101
+ * @csspart live - The live region that says how many suggestions the open panel offers. It is rendered from the first time the panel opens with suggestions.
96
102
  * @csspart rest - The count of tokens that did not fit. It is a `<button>` with `expandable` and a `<span>` without it.
97
103
  * @csspart surface - The floating panel the suggestions sit in.
98
104
  * @csspart tags - The row the tokens sit in.
@@ -118,12 +124,40 @@ export declare class HelixHeadlessFilterInput extends HelixHeadlessFilterInput_b
118
124
  * an icon.
119
125
  */
120
126
  clearLabel: string;
127
+ /**
128
+ * Whether the panel narrows itself to the suggestions that hold the term
129
+ * being typed, as React Aria's Autocomplete filters. Off, the panel shows
130
+ * what you slot and you narrow it.
131
+ */
132
+ filterSuggestions: boolean;
121
133
  /**
122
134
  * The text shown while the field is empty.
123
135
  *
124
136
  * Not a label: it goes when typing starts.
125
137
  */
126
138
  placeholder: string;
139
+ /** What a reader hears for one suggestion, with `{count}` filled in. */
140
+ suggestionLabel: string;
141
+ /** What a reader hears for any other number of suggestions, with `{count}` filled in. */
142
+ suggestionsLabel: string;
143
+ /** The live region, rendered from the first count said. */
144
+ private readonly status;
145
+ /** The count last said, so the same panel is said once. */
146
+ private saidCount;
147
+ /** The options this element hid while narrowing, so only those are shown again. */
148
+ private readonly narrowed;
149
+ /**
150
+ * Hides the slotted options whose words do not hold the term at the caret,
151
+ * under `filter-suggestions`, and shows again the ones it hid before.
152
+ */
153
+ private narrow;
154
+ /** Says how many suggestions the open panel offers, when that changes. */
155
+ private sayCount;
156
+ /**
157
+ * What the form receives: the text, then one entry for each token that
158
+ * says what it filters. See the class description.
159
+ */
160
+ private formValue;
127
161
  /** How many tokens did not fit, which is what the row's count renders. */
128
162
  private over;
129
163
  /**
@@ -4,13 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, drawIcon, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, scrollItemIntoView, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, countLabel, customElement, drawIcon, drawStatus, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, StatusController, scrollItemIntoView, } from '@fusion.dev/helix-core';
8
8
  import { x } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { HelixHeadlessFilterField } from '../field/filter-field.js';
12
12
  import { readFilterText } from '../grammar.js';
13
13
  import { HelixHeadlessFilterMenu } from '../menu/filter-menu.js';
14
+ import { currentTerm, suggests, tokenEntry } from '../suggest.js';
14
15
  const asksToGo = (node) => typeof node?.dismiss === 'function';
15
16
  const isOption = (node) => 'active' in node && 'value' in node;
16
17
  let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinControllerOpen(MixinTokens(MixinControllerValue(MixinField(HelixFormElement)))) {
@@ -22,7 +23,13 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
22
23
  this.announcesInput = true;
23
24
  this.clearable = false;
24
25
  this.clearLabel = 'Clear';
26
+ this.filterSuggestions = false;
25
27
  this.placeholder = '';
28
+ this.suggestionLabel = '{count} suggestion';
29
+ this.suggestionsLabel = '{count} suggestions';
30
+ this.status = new StatusController(this);
31
+ this.saidCount = -1;
32
+ this.narrowed = new Set();
26
33
  this.over = 0;
27
34
  this.activeAt = -1;
28
35
  this.suggesting = false;
@@ -154,6 +161,51 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
154
161
  this.over = this.hideOverflow(this.tokens);
155
162
  };
156
163
  }
164
+ narrow() {
165
+ const slot = this.renderRoot?.querySelector('slot[name="suggestions"]');
166
+ const term = this.filterSuggestions
167
+ ? currentTerm(this.value, this.control?.selectionStart ?? this.value.length)
168
+ : '';
169
+ for (const node of slot?.assignedElements({ flatten: true }) ?? []) {
170
+ const options = isOption(node) ? [node] : [...node.children].filter(isOption);
171
+ for (const option of options) {
172
+ const hide = !suggests(option.textContent ?? '', term);
173
+ if (hide && !option.hidden) {
174
+ option.hidden = true;
175
+ this.narrowed.add(option);
176
+ }
177
+ else if (!hide && this.narrowed.has(option)) {
178
+ option.hidden = false;
179
+ this.narrowed.delete(option);
180
+ }
181
+ }
182
+ }
183
+ }
184
+ sayCount() {
185
+ const count = this.open && this.suggesting ? this.suggestions.length : -1;
186
+ if (count === this.saidCount) {
187
+ return;
188
+ }
189
+ this.saidCount = count;
190
+ if (count < 0) {
191
+ return;
192
+ }
193
+ this.status.sayNow(countLabel(count, this.suggestionLabel, this.suggestionsLabel, this.lang));
194
+ }
195
+ formValue() {
196
+ const entries = (this.tokens?.assignedElements({ flatten: true }) ?? [])
197
+ .map(tokenEntry)
198
+ .filter((entry) => entry !== null);
199
+ if (this.name === '' || entries.length === 0) {
200
+ return this.value;
201
+ }
202
+ const data = new FormData();
203
+ data.append(this.name, this.value);
204
+ for (const entry of entries) {
205
+ data.append(this.name, entry);
206
+ }
207
+ return data;
208
+ }
157
209
  static { this.shadowRootOptions = {
158
210
  ...HelixFormElement.shadowRootOptions,
159
211
  delegatesFocus: true,
@@ -181,7 +233,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
181
233
  }
182
234
  }
183
235
  }
184
- return found.filter((option) => !option.disabled);
236
+ return found.filter((option) => !option.disabled && !option.hidden);
185
237
  }
186
238
  get selectionDirection() {
187
239
  return this.control?.selectionDirection ?? 'none';
@@ -264,6 +316,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
264
316
  from < 0 ? (by > 0 ? 0 : options.length - 1) : (from + by + options.length) % options.length;
265
317
  }
266
318
  refreshSuggesting() {
319
+ this.narrow();
267
320
  const options = this.suggestions;
268
321
  this.suggesting = options.length > 0;
269
322
  this.activeAt = this.activeAt < options.length ? this.activeAt : -1;
@@ -311,7 +364,8 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
311
364
  super.updated(changed);
312
365
  this.releaseCaret?.();
313
366
  this.releaseCaret = undefined;
314
- this.internals.setFormValue(this.value);
367
+ this.internals.setFormValue(this.formValue());
368
+ this.sayCount();
315
369
  const control = this.control;
316
370
  if (control) {
317
371
  this.copyValidity(control);
@@ -391,6 +445,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
391
445
  >
392
446
  <slot name="suggestions" @slotchange=${this.handleSuggestions}></slot>
393
447
  </div>
448
+ ${drawStatus(this.status)}
394
449
  ${this.renderHint()}${this.renderFeedback()}`;
395
450
  }
396
451
  };
@@ -400,9 +455,18 @@ __decorate([
400
455
  __decorate([
401
456
  property({ attribute: 'clear-label' })
402
457
  ], HelixHeadlessFilterInput.prototype, "clearLabel", void 0);
458
+ __decorate([
459
+ property({ attribute: 'filter-suggestions', type: Boolean })
460
+ ], HelixHeadlessFilterInput.prototype, "filterSuggestions", void 0);
403
461
  __decorate([
404
462
  property()
405
463
  ], HelixHeadlessFilterInput.prototype, "placeholder", void 0);
464
+ __decorate([
465
+ property({ attribute: 'suggestion-label' })
466
+ ], HelixHeadlessFilterInput.prototype, "suggestionLabel", void 0);
467
+ __decorate([
468
+ property({ attribute: 'suggestions-label' })
469
+ ], HelixHeadlessFilterInput.prototype, "suggestionsLabel", void 0);
406
470
  __decorate([
407
471
  state()
408
472
  ], HelixHeadlessFilterInput.prototype, "over", void 0);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * What a filter field narrows its suggestions by, and what it sends a form,
3
+ * kept apart from the element so each can be read and tested alone.
4
+ */
5
+ /**
6
+ * The term being typed at the caret: the run of text since the last space,
7
+ * and after its colon where it has one, so `status:ope` narrows by `ope`.
8
+ */
9
+ export declare const currentTerm: (text: string, caret?: number) => string;
10
+ /** Whether a suggestion's words hold the term, ignoring case and accents. */
11
+ export declare const suggests: (words: string, term: string) => boolean;
12
+ /**
13
+ * What a token sends a form: its `value` attribute, or for a token naming a
14
+ * field, the field and the values it shows, written `field:values` the way the
15
+ * field reads them typed. Nothing for a token that says neither.
16
+ */
17
+ export declare const tokenEntry: (token: Element) => string | null;
@@ -0,0 +1,23 @@
1
+ export const currentTerm = (text, caret = text.length) => {
2
+ const before = text.slice(0, caret);
3
+ const word = before.slice(before.search(/\S*$/u));
4
+ const colon = word.indexOf(':');
5
+ return (colon === -1 ? word : word.slice(colon + 1)).replace(/^-/u, '');
6
+ };
7
+ export const suggests = (words, term) => {
8
+ const plain = (text) => text.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase();
9
+ return term === '' || plain(words).includes(plain(term));
10
+ };
11
+ export const tokenEntry = (token) => {
12
+ const written = token.getAttribute('value');
13
+ if (written !== null) {
14
+ return written;
15
+ }
16
+ const field = token.getAttribute('name');
17
+ const values = (token.textContent ?? '').replace(/\s+/gu, ' ').trim();
18
+ if (!field || values === '') {
19
+ return null;
20
+ }
21
+ const negated = token.getAttribute('operator')?.startsWith('not') ? '-' : '';
22
+ return `${negated}${field}:${values.replace(/\s*,\s*/gu, ',')}`;
23
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What a Format Date was asked for, in its own spelling: the `Intl` options a
3
+ * consumer set, and whether the timestamp is a day with no time of day.
4
+ */
5
+ export interface Asked {
6
+ dateOnly: boolean;
7
+ dateStyle?: 'full' | 'long' | 'medium' | 'short' | undefined;
8
+ day?: 'numeric' | '2-digit' | undefined;
9
+ era?: 'long' | 'narrow' | 'short' | undefined;
10
+ hour?: 'numeric' | '2-digit' | undefined;
11
+ hourCycle?: 'h11' | 'h12' | 'h23' | 'h24' | undefined;
12
+ minute?: 'numeric' | '2-digit' | undefined;
13
+ month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow' | undefined;
14
+ second?: 'numeric' | '2-digit' | undefined;
15
+ timeStyle?: 'full' | 'long' | 'medium' | 'short' | undefined;
16
+ timeZone?: string | undefined;
17
+ timeZoneName?: 'long' | 'short' | undefined;
18
+ weekday?: 'long' | 'short' | 'narrow' | undefined;
19
+ year?: 'numeric' | '2-digit' | undefined;
20
+ }
21
+ /**
22
+ * The `Intl.DateTimeFormat` options for what was asked, and none that was not.
23
+ *
24
+ * **A style wins over the fields.** `dateStyle` and `timeStyle` are each
25
+ * locale's own whole pattern, and `Intl` throws a `TypeError` when one is
26
+ * given beside `month` or `hour`, so the fields are left out wherever a style
27
+ * is set. `hourCycle` and `timeZone` go with either.
28
+ *
29
+ * **A day with no time is read in UTC** unless a zone is given. Parsed, it is
30
+ * midnight UTC, and read in a zone west of it that is the day before: measured
31
+ * before this, `2026-08-04` read "August 3, 2026" in Los Angeles.
32
+ */
33
+ export declare const formatOptions: (asked: Asked) => Intl.DateTimeFormatOptions;
@@ -0,0 +1,42 @@
1
+ const FIELDS = [
2
+ 'day',
3
+ 'era',
4
+ 'hour',
5
+ 'minute',
6
+ 'month',
7
+ 'second',
8
+ 'timeZoneName',
9
+ 'weekday',
10
+ 'year',
11
+ ];
12
+ export const formatOptions = (asked) => {
13
+ const options = {};
14
+ const styled = asked.dateStyle !== undefined || asked.timeStyle !== undefined;
15
+ if (styled) {
16
+ if (asked.dateStyle) {
17
+ options.dateStyle = asked.dateStyle;
18
+ }
19
+ if (asked.timeStyle) {
20
+ options.timeStyle = asked.timeStyle;
21
+ }
22
+ }
23
+ else {
24
+ for (const field of FIELDS) {
25
+ const value = asked[field];
26
+ if (value) {
27
+ ;
28
+ options[field] = value;
29
+ }
30
+ }
31
+ }
32
+ if (asked.hourCycle) {
33
+ options.hourCycle = asked.hourCycle;
34
+ }
35
+ if (asked.timeZone) {
36
+ options.timeZone = asked.timeZone;
37
+ }
38
+ else if (asked.dateOnly) {
39
+ options.timeZone = 'UTC';
40
+ }
41
+ return options;
42
+ };