@syware/jlib 0.0.0-stage → 0.1.3

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 (331) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -2
  3. package/dist/all.d.ts +1 -0
  4. package/dist/all.js +75 -0
  5. package/dist/atoms/jl-avatar.css_raw.js +3 -0
  6. package/dist/atoms/jl-avatar.d.ts +52 -0
  7. package/dist/atoms/jl-avatar.js +167 -0
  8. package/dist/atoms/jl-badge.css_raw.js +3 -0
  9. package/dist/atoms/jl-badge.d.ts +32 -0
  10. package/dist/atoms/jl-badge.js +60 -0
  11. package/dist/atoms/jl-button.css_raw.js +3 -0
  12. package/dist/atoms/jl-button.d.ts +70 -0
  13. package/dist/atoms/jl-button.js +146 -0
  14. package/dist/atoms/jl-checkbox.css_raw.js +3 -0
  15. package/dist/atoms/jl-checkbox.d.ts +61 -0
  16. package/dist/atoms/jl-checkbox.js +149 -0
  17. package/dist/atoms/jl-chip.css_raw.js +3 -0
  18. package/dist/atoms/jl-chip.d.ts +59 -0
  19. package/dist/atoms/jl-chip.js +164 -0
  20. package/dist/atoms/jl-divider.css_raw.js +3 -0
  21. package/dist/atoms/jl-divider.d.ts +26 -0
  22. package/dist/atoms/jl-divider.js +59 -0
  23. package/dist/atoms/jl-icon.css_raw.js +3 -0
  24. package/dist/atoms/jl-icon.d.ts +25 -0
  25. package/dist/atoms/jl-icon.js +41 -0
  26. package/dist/atoms/jl-input.css_raw.js +3 -0
  27. package/dist/atoms/jl-input.d.ts +112 -0
  28. package/dist/atoms/jl-input.js +202 -0
  29. package/dist/atoms/jl-kbd.css_raw.js +3 -0
  30. package/dist/atoms/jl-kbd.d.ts +17 -0
  31. package/dist/atoms/jl-kbd.js +62 -0
  32. package/dist/atoms/jl-link.css_raw.js +3 -0
  33. package/dist/atoms/jl-link.d.ts +37 -0
  34. package/dist/atoms/jl-link.js +86 -0
  35. package/dist/atoms/jl-progress-ring.css_raw.js +3 -0
  36. package/dist/atoms/jl-progress-ring.d.ts +49 -0
  37. package/dist/atoms/jl-progress-ring.js +132 -0
  38. package/dist/atoms/jl-progress.css_raw.js +3 -0
  39. package/dist/atoms/jl-progress.d.ts +43 -0
  40. package/dist/atoms/jl-progress.js +109 -0
  41. package/dist/atoms/jl-rating.css_raw.js +3 -0
  42. package/dist/atoms/jl-rating.d.ts +73 -0
  43. package/dist/atoms/jl-rating.js +184 -0
  44. package/dist/atoms/jl-select.css_raw.js +3 -0
  45. package/dist/atoms/jl-select.d.ts +70 -0
  46. package/dist/atoms/jl-select.js +146 -0
  47. package/dist/atoms/jl-skeleton.css_raw.js +3 -0
  48. package/dist/atoms/jl-skeleton.d.ts +34 -0
  49. package/dist/atoms/jl-skeleton.js +63 -0
  50. package/dist/atoms/jl-slider.css_raw.js +3 -0
  51. package/dist/atoms/jl-slider.d.ts +65 -0
  52. package/dist/atoms/jl-slider.js +143 -0
  53. package/dist/atoms/jl-spinner.css_raw.js +3 -0
  54. package/dist/atoms/jl-spinner.d.ts +16 -0
  55. package/dist/atoms/jl-spinner.js +26 -0
  56. package/dist/atoms/jl-switch.css_raw.js +3 -0
  57. package/dist/atoms/jl-switch.d.ts +57 -0
  58. package/dist/atoms/jl-switch.js +125 -0
  59. package/dist/atoms/jl-textarea.css_raw.js +3 -0
  60. package/dist/atoms/jl-textarea.d.ts +81 -0
  61. package/dist/atoms/jl-textarea.js +148 -0
  62. package/dist/base/jl-element.d.ts +16 -0
  63. package/dist/base/jl-element.js +35 -0
  64. package/dist/base/jl-form-control.d.ts +29 -0
  65. package/dist/base/jl-form-control.js +44 -0
  66. package/dist/index.d.ts +119 -0
  67. package/dist/index.js +72 -0
  68. package/dist/lib/arrow.d.ts +6 -0
  69. package/dist/lib/arrow.js +15 -0
  70. package/dist/lib/attributes.d.ts +9 -0
  71. package/dist/lib/attributes.js +22 -0
  72. package/dist/lib/avatar.d.ts +11 -0
  73. package/dist/lib/avatar.js +37 -0
  74. package/dist/lib/calendar.d.ts +48 -0
  75. package/dist/lib/calendar.js +166 -0
  76. package/dist/lib/css-length.d.ts +2 -0
  77. package/dist/lib/css-length.js +8 -0
  78. package/dist/lib/define.d.ts +9 -0
  79. package/dist/lib/define.js +9 -0
  80. package/dist/lib/dom.d.ts +4 -0
  81. package/dist/lib/dom.js +16 -0
  82. package/dist/lib/files.d.ts +18 -0
  83. package/dist/lib/files.js +89 -0
  84. package/dist/lib/floating.d.ts +13 -0
  85. package/dist/lib/floating.js +127 -0
  86. package/dist/lib/fuzzy.d.ts +26 -0
  87. package/dist/lib/fuzzy.js +149 -0
  88. package/dist/lib/highlight.d.ts +5 -0
  89. package/dist/lib/highlight.js +267 -0
  90. package/dist/lib/hotkey.d.ts +9 -0
  91. package/dist/lib/hotkey.js +101 -0
  92. package/dist/lib/icon-set.d.ts +95 -0
  93. package/dist/lib/icon-set.js +97 -0
  94. package/dist/lib/icons.d.ts +12 -0
  95. package/dist/lib/icons.js +39 -0
  96. package/dist/lib/indicator.d.ts +6 -0
  97. package/dist/lib/indicator.js +18 -0
  98. package/dist/lib/keep-in-view.d.ts +5 -0
  99. package/dist/lib/keep-in-view.js +15 -0
  100. package/dist/lib/key-shortcut.d.ts +20 -0
  101. package/dist/lib/key-shortcut.js +100 -0
  102. package/dist/lib/keycaps.d.ts +8 -0
  103. package/dist/lib/keycaps.js +162 -0
  104. package/dist/lib/locale.d.ts +4 -0
  105. package/dist/lib/locale.js +13 -0
  106. package/dist/lib/nav-tree.d.ts +9 -0
  107. package/dist/lib/nav-tree.js +30 -0
  108. package/dist/lib/numeric.d.ts +20 -0
  109. package/dist/lib/numeric.js +50 -0
  110. package/dist/lib/option-filter.d.ts +29 -0
  111. package/dist/lib/option-filter.js +108 -0
  112. package/dist/lib/option-groups.d.ts +6 -0
  113. package/dist/lib/option-groups.js +26 -0
  114. package/dist/lib/options.d.ts +5 -0
  115. package/dist/lib/options.js +24 -0
  116. package/dist/lib/pagination.d.ts +9 -0
  117. package/dist/lib/pagination.js +52 -0
  118. package/dist/lib/palette.d.ts +8 -0
  119. package/dist/lib/palette.js +46 -0
  120. package/dist/lib/progress.d.ts +20 -0
  121. package/dist/lib/progress.js +33 -0
  122. package/dist/lib/rating.d.ts +7 -0
  123. package/dist/lib/rating.js +26 -0
  124. package/dist/lib/roving-index.d.ts +10 -0
  125. package/dist/lib/roving-index.js +32 -0
  126. package/dist/lib/roving.d.ts +11 -0
  127. package/dist/lib/roving.js +21 -0
  128. package/dist/lib/slots.d.ts +2 -0
  129. package/dist/lib/slots.js +6 -0
  130. package/dist/lib/sparkline.d.ts +11 -0
  131. package/dist/lib/sparkline.js +42 -0
  132. package/dist/lib/split.d.ts +12 -0
  133. package/dist/lib/split.js +37 -0
  134. package/dist/lib/surface.d.ts +7 -0
  135. package/dist/lib/surface.js +28 -0
  136. package/dist/lib/table.d.ts +30 -0
  137. package/dist/lib/table.js +110 -0
  138. package/dist/lib/tags.d.ts +14 -0
  139. package/dist/lib/tags.js +29 -0
  140. package/dist/lib/text.d.ts +5 -0
  141. package/dist/lib/text.js +13 -0
  142. package/dist/lib/tokens.d.ts +5 -0
  143. package/dist/lib/tokens.js +15 -0
  144. package/dist/lib/tree.d.ts +16 -0
  145. package/dist/lib/tree.js +89 -0
  146. package/dist/lib/trend.d.ts +3 -0
  147. package/dist/lib/trend.js +15 -0
  148. package/dist/lib/trigger.d.ts +11 -0
  149. package/dist/lib/trigger.js +34 -0
  150. package/dist/molecules/jl-accordion-item.css_raw.js +3 -0
  151. package/dist/molecules/jl-accordion-item.d.ts +34 -0
  152. package/dist/molecules/jl-accordion-item.js +82 -0
  153. package/dist/molecules/jl-accordion.css_raw.js +3 -0
  154. package/dist/molecules/jl-accordion.d.ts +27 -0
  155. package/dist/molecules/jl-accordion.js +61 -0
  156. package/dist/molecules/jl-alert.css_raw.js +3 -0
  157. package/dist/molecules/jl-alert.d.ts +38 -0
  158. package/dist/molecules/jl-alert.js +131 -0
  159. package/dist/molecules/jl-avatar-group.css_raw.js +3 -0
  160. package/dist/molecules/jl-avatar-group.d.ts +28 -0
  161. package/dist/molecules/jl-avatar-group.js +69 -0
  162. package/dist/molecules/jl-breadcrumb.css_raw.js +3 -0
  163. package/dist/molecules/jl-breadcrumb.d.ts +35 -0
  164. package/dist/molecules/jl-breadcrumb.js +126 -0
  165. package/dist/molecules/jl-calendar.css_raw.js +3 -0
  166. package/dist/molecules/jl-calendar.d.ts +101 -0
  167. package/dist/molecules/jl-calendar.js +314 -0
  168. package/dist/molecules/jl-card.css_raw.js +3 -0
  169. package/dist/molecules/jl-card.d.ts +46 -0
  170. package/dist/molecules/jl-card.js +172 -0
  171. package/dist/molecules/jl-code-block.css_raw.js +3 -0
  172. package/dist/molecules/jl-code-block.d.ts +44 -0
  173. package/dist/molecules/jl-code-block.js +106 -0
  174. package/dist/molecules/jl-combobox.css_raw.js +3 -0
  175. package/dist/molecules/jl-combobox.d.ts +120 -0
  176. package/dist/molecules/jl-combobox.js +450 -0
  177. package/dist/molecules/jl-copy-button.css_raw.js +3 -0
  178. package/dist/molecules/jl-copy-button.d.ts +31 -0
  179. package/dist/molecules/jl-copy-button.js +69 -0
  180. package/dist/molecules/jl-date-picker.css_raw.js +3 -0
  181. package/dist/molecules/jl-date-picker.d.ts +109 -0
  182. package/dist/molecules/jl-date-picker.js +322 -0
  183. package/dist/molecules/jl-empty.css_raw.js +3 -0
  184. package/dist/molecules/jl-empty.d.ts +32 -0
  185. package/dist/molecules/jl-empty.js +94 -0
  186. package/dist/molecules/jl-field.css_raw.js +3 -0
  187. package/dist/molecules/jl-field.d.ts +49 -0
  188. package/dist/molecules/jl-field.js +147 -0
  189. package/dist/molecules/jl-file-drop.css_raw.js +3 -0
  190. package/dist/molecules/jl-file-drop.d.ts +93 -0
  191. package/dist/molecules/jl-file-drop.js +366 -0
  192. package/dist/molecules/jl-list-item.css_raw.js +3 -0
  193. package/dist/molecules/jl-list-item.d.ts +56 -0
  194. package/dist/molecules/jl-list-item.js +176 -0
  195. package/dist/molecules/jl-menu.css_raw.js +3 -0
  196. package/dist/molecules/jl-menu.d.ts +62 -0
  197. package/dist/molecules/jl-menu.js +288 -0
  198. package/dist/molecules/jl-number-input.css_raw.js +3 -0
  199. package/dist/molecules/jl-number-input.d.ts +81 -0
  200. package/dist/molecules/jl-number-input.js +220 -0
  201. package/dist/molecules/jl-pagination.css_raw.js +3 -0
  202. package/dist/molecules/jl-pagination.d.ts +59 -0
  203. package/dist/molecules/jl-pagination.js +172 -0
  204. package/dist/molecules/jl-popover.css_raw.js +3 -0
  205. package/dist/molecules/jl-popover.d.ts +49 -0
  206. package/dist/molecules/jl-popover.js +152 -0
  207. package/dist/molecules/jl-radio-group.css_raw.js +3 -0
  208. package/dist/molecules/jl-radio-group.d.ts +71 -0
  209. package/dist/molecules/jl-radio-group.js +202 -0
  210. package/dist/molecules/jl-search.css_raw.js +3 -0
  211. package/dist/molecules/jl-search.d.ts +71 -0
  212. package/dist/molecules/jl-search.js +210 -0
  213. package/dist/molecules/jl-segmented.css_raw.js +3 -0
  214. package/dist/molecules/jl-segmented.d.ts +65 -0
  215. package/dist/molecules/jl-segmented.js +187 -0
  216. package/dist/molecules/jl-stat.css_raw.js +3 -0
  217. package/dist/molecules/jl-stat.d.ts +55 -0
  218. package/dist/molecules/jl-stat.js +169 -0
  219. package/dist/molecules/jl-steps.css_raw.js +3 -0
  220. package/dist/molecules/jl-steps.d.ts +44 -0
  221. package/dist/molecules/jl-steps.js +118 -0
  222. package/dist/molecules/jl-tab-panel.css_raw.js +3 -0
  223. package/dist/molecules/jl-tab-panel.d.ts +35 -0
  224. package/dist/molecules/jl-tab-panel.js +35 -0
  225. package/dist/molecules/jl-tabs.css_raw.js +3 -0
  226. package/dist/molecules/jl-tabs.d.ts +51 -0
  227. package/dist/molecules/jl-tabs.js +198 -0
  228. package/dist/molecules/jl-tag-input.css_raw.js +3 -0
  229. package/dist/molecules/jl-tag-input.d.ts +103 -0
  230. package/dist/molecules/jl-tag-input.js +429 -0
  231. package/dist/molecules/jl-timeline.css_raw.js +3 -0
  232. package/dist/molecules/jl-timeline.d.ts +28 -0
  233. package/dist/molecules/jl-timeline.js +81 -0
  234. package/dist/molecules/jl-toast.css_raw.js +3 -0
  235. package/dist/molecules/jl-toast.d.ts +75 -0
  236. package/dist/molecules/jl-toast.js +274 -0
  237. package/dist/molecules/jl-tooltip.css_raw.js +3 -0
  238. package/dist/molecules/jl-tooltip.d.ts +44 -0
  239. package/dist/molecules/jl-tooltip.js +160 -0
  240. package/dist/organisms/jl-command-palette.css_raw.js +3 -0
  241. package/dist/organisms/jl-command-palette.d.ts +60 -0
  242. package/dist/organisms/jl-command-palette.js +285 -0
  243. package/dist/organisms/jl-confirm.css_raw.js +3 -0
  244. package/dist/organisms/jl-confirm.d.ts +56 -0
  245. package/dist/organisms/jl-confirm.js +132 -0
  246. package/dist/organisms/jl-dialog.css_raw.js +3 -0
  247. package/dist/organisms/jl-dialog.d.ts +43 -0
  248. package/dist/organisms/jl-dialog.js +120 -0
  249. package/dist/organisms/jl-drawer.css_raw.js +3 -0
  250. package/dist/organisms/jl-drawer.d.ts +50 -0
  251. package/dist/organisms/jl-drawer.js +141 -0
  252. package/dist/organisms/jl-navbar.css_raw.js +3 -0
  253. package/dist/organisms/jl-navbar.d.ts +32 -0
  254. package/dist/organisms/jl-navbar.js +121 -0
  255. package/dist/organisms/jl-page-header.css_raw.js +3 -0
  256. package/dist/organisms/jl-page-header.d.ts +26 -0
  257. package/dist/organisms/jl-page-header.js +85 -0
  258. package/dist/organisms/jl-side-nav.css_raw.js +3 -0
  259. package/dist/organisms/jl-side-nav.d.ts +60 -0
  260. package/dist/organisms/jl-side-nav.js +269 -0
  261. package/dist/organisms/jl-table.css_raw.js +3 -0
  262. package/dist/organisms/jl-table.d.ts +109 -0
  263. package/dist/organisms/jl-table.js +318 -0
  264. package/dist/organisms/jl-toaster.css_raw.js +3 -0
  265. package/dist/organisms/jl-toaster.d.ts +48 -0
  266. package/dist/organisms/jl-toaster.js +142 -0
  267. package/dist/organisms/jl-tree.css_raw.js +3 -0
  268. package/dist/organisms/jl-tree.d.ts +59 -0
  269. package/dist/organisms/jl-tree.js +198 -0
  270. package/dist/styles/base.css_raw.js +3 -0
  271. package/dist/styles/control.css_raw.js +3 -0
  272. package/dist/styles/floating.css_raw.js +3 -0
  273. package/dist/styles/shared.d.ts +8 -0
  274. package/dist/styles/shared.js +14 -0
  275. package/dist/styles/tokens.css_raw.js +3 -0
  276. package/dist/templates/jl-app-shell.css_raw.js +3 -0
  277. package/dist/templates/jl-app-shell.d.ts +37 -0
  278. package/dist/templates/jl-app-shell.js +150 -0
  279. package/dist/templates/jl-split-view.css_raw.js +3 -0
  280. package/dist/templates/jl-split-view.d.ts +49 -0
  281. package/dist/templates/jl-split-view.js +149 -0
  282. package/dist/tokens.css +188 -0
  283. package/dist/types/calendar.types.d.ts +11 -0
  284. package/dist/types/files.types.d.ts +23 -0
  285. package/dist/types/floating.types.d.ts +13 -0
  286. package/dist/types/fuzzy.types.d.ts +10 -0
  287. package/dist/types/highlight.types.d.ts +6 -0
  288. package/dist/types/hotkey.types.d.ts +19 -0
  289. package/dist/types/jl-accordion.types.d.ts +2 -0
  290. package/dist/types/jl-avatar.types.d.ts +10 -0
  291. package/dist/types/jl-breadcrumb.types.d.ts +6 -0
  292. package/dist/types/jl-button.types.d.ts +3 -0
  293. package/dist/types/jl-card.types.d.ts +2 -0
  294. package/dist/types/jl-chip.types.d.ts +1 -0
  295. package/dist/types/jl-combobox.types.d.ts +8 -0
  296. package/dist/types/jl-command-palette.types.d.ts +22 -0
  297. package/dist/types/jl-confirm.types.d.ts +10 -0
  298. package/dist/types/jl-dialog.types.d.ts +1 -0
  299. package/dist/types/jl-divider.types.d.ts +2 -0
  300. package/dist/types/jl-drawer.types.d.ts +4 -0
  301. package/dist/types/jl-empty.types.d.ts +2 -0
  302. package/dist/types/jl-field.types.d.ts +2 -0
  303. package/dist/types/jl-input.types.d.ts +1 -0
  304. package/dist/types/jl-link.types.d.ts +2 -0
  305. package/dist/types/jl-menu.types.d.ts +22 -0
  306. package/dist/types/jl-navbar.types.d.ts +6 -0
  307. package/dist/types/jl-progress.types.d.ts +3 -0
  308. package/dist/types/jl-radio-group.types.d.ts +10 -0
  309. package/dist/types/jl-segmented.types.d.ts +7 -0
  310. package/dist/types/jl-select.types.d.ts +12 -0
  311. package/dist/types/jl-side-nav.types.d.ts +17 -0
  312. package/dist/types/jl-skeleton.types.d.ts +2 -0
  313. package/dist/types/jl-slider.types.d.ts +5 -0
  314. package/dist/types/jl-split-view.types.d.ts +2 -0
  315. package/dist/types/jl-stat.types.d.ts +2 -0
  316. package/dist/types/jl-steps.types.d.ts +8 -0
  317. package/dist/types/jl-switch.types.d.ts +1 -0
  318. package/dist/types/jl-table.types.d.ts +30 -0
  319. package/dist/types/jl-tabs.types.d.ts +2 -0
  320. package/dist/types/jl-timeline.types.d.ts +10 -0
  321. package/dist/types/jl-toast.types.d.ts +2 -0
  322. package/dist/types/jl-toaster.types.d.ts +23 -0
  323. package/dist/types/jl-tree.types.d.ts +26 -0
  324. package/dist/types/key-shortcut.types.d.ts +9 -0
  325. package/dist/types/keycaps.types.d.ts +7 -0
  326. package/dist/types/option-filter.types.d.ts +10 -0
  327. package/dist/types/options.types.d.ts +4 -0
  328. package/dist/types/pagination.types.d.ts +8 -0
  329. package/dist/types/roving-index.types.d.ts +2 -0
  330. package/dist/types/sparkline.types.d.ts +11 -0
  331. package/package.json +102 -4
@@ -0,0 +1,49 @@
1
+ import { JlElement } from '../base/jl-element';
2
+ import type { FieldOrientation } from '../types/jl-field.types';
3
+ export declare const FIELD_ORIENTATIONS: readonly FieldOrientation[];
4
+ /**
5
+ * A control with its label, a hint under it, and an error that replaces the hint. Pressing the label focuses the
6
+ * control; an `error` draws the control as invalid. The control goes in the default slot.
7
+ */
8
+ export declare class JlField extends JlElement {
9
+ static readonly selector = "jl-field";
10
+ readonly label: Readonly<Readonly<{
11
+ get(): string;
12
+ set(setter: string | ((prevState: string) => string)): void;
13
+ }>>;
14
+ /** Help under the control, hidden while there is an error. */
15
+ readonly hint: Readonly<Readonly<{
16
+ get(): string;
17
+ set(setter: string | ((prevState: string) => string)): void;
18
+ }>>;
19
+ /** What is wrong with the value, in the danger colour; the control is drawn as invalid meanwhile. */
20
+ readonly error: Readonly<Readonly<{
21
+ get(): string;
22
+ set(setter: string | ((prevState: string) => string)): void;
23
+ }>>;
24
+ /** Adds an asterisk to the label. Set `required` on the control too, for the form to check it. */
25
+ readonly required: Readonly<Readonly<{
26
+ get(): boolean;
27
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
28
+ }>>;
29
+ readonly orientation: Readonly<Readonly<{
30
+ get(): FieldOrientation;
31
+ set(setter: ((prevState: FieldOrientation) => FieldOrientation) | FieldOrientation): void;
32
+ }>>;
33
+ private readonly _state;
34
+ private readonly _refs;
35
+ templateHtml(): Node;
36
+ templateCss(): string;
37
+ onConnect(): void;
38
+ /** The control in the slot: the first element the page put there. */
39
+ private control;
40
+ private focusControl;
41
+ private renderMessages;
42
+ private syncControl;
43
+ /** Names the control for screen readers with the field's label, unless it has a label of its own. */
44
+ private syncLabel;
45
+ /** Marks the control invalid, remembering it unless the page had marked it already. */
46
+ private invalidate;
47
+ /** Marks the control invalid while there is an error, and clears only what the field itself marked. */
48
+ private syncInvalid;
49
+ }
@@ -0,0 +1,147 @@
1
+ import { BASE_STYLES } from "../styles/shared.js";
2
+ import { JlElement } from "../base/jl-element.js";
3
+ import { oneOf } from "../lib/attributes.js";
4
+ import { registerIcons } from "../lib/icons.js";
5
+ import { JlIcon } from "../atoms/jl-icon.js";
6
+ import jl_field_default from "./jl-field.css_raw.js";
7
+ import { css } from "@jadis/core";
8
+ import { CircleAlert } from "lucide";
9
+ import { jsx, jsxs } from "@jadis/core/jsx-runtime";
10
+ // src/molecules/jl-field.tsx
11
+ registerIcons({ "circle-alert": CircleAlert });
12
+ var FIELD_ORIENTATIONS = ["vertical", "horizontal"];
13
+ var ERROR_ICON_SIZE = 14;
14
+ var VISIBLE_LABEL_TAGS = ["jl-checkbox", "jl-switch"];
15
+ function inputOf(element, name) {
16
+ const candidate = Reflect.get(element, name);
17
+ return typeof candidate === "object" && candidate !== null && typeof Reflect.get(candidate, "set") === "function" ? candidate : null;
18
+ }
19
+ /**
20
+ * A control with its label, a hint under it, and an error that replaces the hint. Pressing the label focuses the
21
+ * control; an `error` draws the control as invalid. The control goes in the default slot.
22
+ */
23
+ var JlField = class extends JlElement {
24
+ static selector = "jl-field";
25
+ label = this.useChange("", (value) => {
26
+ this._refs.label.textContent = value;
27
+ this._refs.labelRow.hidden = value === "";
28
+ this.syncControl();
29
+ }, { attribute: "label" });
30
+ /** Help under the control, hidden while there is an error. */
31
+ hint = this.useChange("", () => this.renderMessages(), { attribute: "hint" });
32
+ /** What is wrong with the value, in the danger colour; the control is drawn as invalid meanwhile. */
33
+ error = this.useChange("", () => {
34
+ this.renderMessages();
35
+ this.syncControl();
36
+ }, { attribute: "error" });
37
+ /** Adds an asterisk to the label. Set `required` on the control too, for the form to check it. */
38
+ required = this.useChange(false, (value) => {
39
+ this._refs.asterisk.hidden = !value;
40
+ }, { attribute: "required" });
41
+ orientation = this.useChange("vertical", (value) => this.reflect("orientation", value), {
42
+ attribute: "orientation",
43
+ parse: oneOf(FIELD_ORIENTATIONS, "vertical")
44
+ });
45
+ _state = { invalidated: null };
46
+ _refs = this.useRefs((ref) => ({
47
+ asterisk: ref(".asterisk"),
48
+ errorRegion: ref(".error-region"),
49
+ hint: ref(".hint"),
50
+ label: ref(".label"),
51
+ labelRow: ref(".label-row"),
52
+ slot: ref("slot")
53
+ }));
54
+ templateHtml() {
55
+ return /* @__PURE__ */ jsxs("div", {
56
+ class: "field",
57
+ attrs: { part: "base" },
58
+ children: [/* @__PURE__ */ jsxs("div", {
59
+ class: "label-row",
60
+ hidden: true,
61
+ children: [/* @__PURE__ */ jsx("span", { class: "label" }), /* @__PURE__ */ jsx("span", {
62
+ class: "asterisk",
63
+ "aria-hidden": "true",
64
+ hidden: true,
65
+ children: "*"
66
+ })]
67
+ }), /* @__PURE__ */ jsxs("div", {
68
+ class: "body",
69
+ children: [
70
+ /* @__PURE__ */ jsx("slot", {}),
71
+ /* @__PURE__ */ jsx("p", {
72
+ class: "hint",
73
+ hidden: true
74
+ }),
75
+ /* @__PURE__ */ jsx("div", {
76
+ class: "error-region",
77
+ "aria-live": "polite"
78
+ })
79
+ ]
80
+ })]
81
+ });
82
+ }
83
+ templateCss() {
84
+ return css`${BASE_STYLES}${jl_field_default}`;
85
+ }
86
+ onConnect() {
87
+ this.on(this._refs.labelRow, "click", () => this.focusControl());
88
+ this.syncControl();
89
+ this.on(this._refs.slot, "slotchange", () => this.syncControl());
90
+ }
91
+ /** The control in the slot: the first element the page put there. */
92
+ control() {
93
+ const [first] = this._refs.slot.assignedElements({ flatten: true });
94
+ return first instanceof HTMLElement ? first : null;
95
+ }
96
+ focusControl() {
97
+ this.control()?.focus();
98
+ }
99
+ renderMessages() {
100
+ const error = this.error.get();
101
+ const hint = this.hint.get();
102
+ this._refs.hint.textContent = hint;
103
+ this._refs.hint.hidden = hint === "" || error !== "";
104
+ this.reflect("has-error", error !== "");
105
+ this._refs.errorRegion.replaceChildren(...error === "" ? [] : [/* @__PURE__ */ jsxs("p", {
106
+ class: "error",
107
+ children: [/* @__PURE__ */ jsx(JlIcon, {
108
+ name: "circle-alert",
109
+ size: ERROR_ICON_SIZE
110
+ }), /* @__PURE__ */ jsx("span", { children: error })]
111
+ })]);
112
+ }
113
+ syncControl() {
114
+ const control = this.control();
115
+ if (control === null) return;
116
+ this.syncLabel(control);
117
+ this.syncInvalid(control);
118
+ }
119
+ /** Names the control for screen readers with the field's label, unless it has a label of its own. */
120
+ syncLabel(control) {
121
+ const input = inputOf(control, "label");
122
+ const ownLabel = control.hasAttribute("label") || VISIBLE_LABEL_TAGS.includes(control.localName);
123
+ if (input === null || ownLabel) return;
124
+ input.set(this.label.get());
125
+ }
126
+ /** Marks the control invalid, remembering it unless the page had marked it already. */
127
+ invalidate(control, input) {
128
+ if (input.get()) return;
129
+ input.set(true);
130
+ this._state.invalidated = control;
131
+ }
132
+ /** Marks the control invalid while there is an error, and clears only what the field itself marked. */
133
+ syncInvalid(control) {
134
+ const input = inputOf(control, "invalid");
135
+ if (input === null) return;
136
+ if (this.error.get() !== "") {
137
+ this.invalidate(control, input);
138
+ return;
139
+ }
140
+ if (this._state.invalidated === control) {
141
+ input.set(false);
142
+ this._state.invalidated = null;
143
+ }
144
+ }
145
+ };
146
+ JlField.register();
147
+ export { FIELD_ORIENTATIONS, JlField };
@@ -0,0 +1,3 @@
1
+ // src/molecules/jl-file-drop.css?raw
2
+ var jl_file_drop_default = ":host {\n display: block;\n /* As a grid or flex item, the field may be narrower than its text field would like. */\n min-width: 0;\n --pad: 24px;\n --badge: 44px;\n}\n\n:host([size=\"sm\"]) {\n --pad: 14px;\n --badge: 34px;\n}\n\n:host([size=\"lg\"]) {\n --pad: 36px;\n --badge: 52px;\n}\n\n.zone {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n padding: var(--pad);\n border: 1.5px dashed var(--jl-line-strong);\n border-radius: var(--jl-radius);\n background: var(--jl-surface);\n color: var(--jl-text);\n text-align: center;\n cursor: pointer;\n transition:\n border-color var(--jl-fast) var(--jl-ease),\n background var(--jl-fast) var(--jl-ease);\n}\n\n.zone:hover {\n border-color: color-mix(in srgb, var(--jl-line-strong) 60%, var(--jl-text) 18%);\n background: var(--jl-surface-2);\n}\n\n:host([dragging]) .zone {\n border-color: var(--jl-accent);\n background: var(--jl-accent-soft);\n}\n\n:host([invalid]) .zone {\n border-color: var(--jl-danger);\n}\n\n:host([disabled]) .zone {\n border-color: var(--jl-line-strong);\n background: var(--jl-surface-2);\n color: var(--jl-muted);\n cursor: not-allowed;\n}\n\n.zone-icon {\n align-items: center;\n justify-content: center;\n width: var(--badge);\n height: var(--badge);\n margin-bottom: 6px;\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface-3);\n color: var(--jl-muted);\n transition:\n background var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n:host([dragging]) .zone-icon {\n background: var(--jl-surface);\n color: var(--jl-accent);\n}\n\n.text {\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 1.4;\n}\n\n:host([size=\"sm\"]) .text {\n font-size: var(--jl-font-sm);\n}\n\n.hint {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n line-height: 1.4;\n}\n\n.browse {\n display: inline-flex;\n align-items: center;\n height: var(--jl-control-sm);\n margin-top: 8px;\n padding: 0 12px;\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--jl-radius-sm);\n background: var(--jl-surface);\n font-size: var(--jl-font-xs);\n font-weight: 600;\n cursor: pointer;\n transition: background var(--jl-fast) var(--jl-ease);\n}\n\n.browse:hover {\n background: var(--jl-surface-3);\n}\n\n.browse:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.refused,\n.files {\n margin: 8px 0 0;\n padding: 0;\n list-style: none;\n}\n\n.refused {\n display: grid;\n gap: 4px;\n color: var(--jl-danger);\n font-size: var(--jl-font-sm);\n line-height: 1.45;\n}\n\n.refusal {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n}\n\n.refusal jl-icon {\n margin-top: 3px;\n}\n\n.refused-name {\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n.files {\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius);\n}\n\n.file {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 40px;\n padding: 6px 8px 6px 12px;\n}\n\n.file + .file {\n border-top: 1px solid var(--jl-line);\n}\n\n.file-icon {\n color: var(--jl-faint);\n}\n\n.file-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n font-size: var(--jl-font-sm);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.file-size {\n color: var(--jl-muted);\n font-family: var(--jl-font-mono);\n font-size: var(--jl-font-xs);\n white-space: nowrap;\n}\n\n.file-remove {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n color: var(--jl-faint);\n cursor: pointer;\n}\n\n.file-remove:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n/* \"images, PDF · up to 5 MB\": the hint starts with a capital. */\n.hint::first-letter {\n text-transform: uppercase;\n}\n";
3
+ export { jl_file_drop_default as default };
@@ -0,0 +1,93 @@
1
+ import { JlFormControl } from '../base/jl-form-control';
2
+ import type { ControlSize } from '../types/options.types';
3
+ /**
4
+ * A zone to drop files on, with a button that opens the file chooser. The chosen files are listed under it with
5
+ * their size and a button to take each one back; refused files are listed with the reason. The form gets the
6
+ * files under the field's name, as from a native file input.
7
+ */
8
+ export declare class JlFileDrop extends JlFormControl {
9
+ static readonly selector = "jl-file-drop";
10
+ readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
11
+ change: File[];
12
+ }>>;
13
+ /** The types it takes, as a native file input's `accept`: `image/*,.pdf`. */
14
+ readonly accept: Readonly<Readonly<{
15
+ get(): string;
16
+ set(setter: string | ((prevState: string) => string)): void;
17
+ }>>;
18
+ /** Takes several files; otherwise a new file replaces the one there. */
19
+ readonly multiple: Readonly<Readonly<{
20
+ get(): boolean;
21
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
22
+ }>>;
23
+ /** The largest file it takes, in bytes; 0 for no limit. */
24
+ readonly maxSize: Readonly<Readonly<{
25
+ get(): number;
26
+ set(setter: number | ((prevState: number) => number)): void;
27
+ }>>;
28
+ /** The text in the zone; by default it says to drop a file, or files. */
29
+ readonly text: Readonly<Readonly<{
30
+ get(): string;
31
+ set(setter: string | ((prevState: string) => string)): void;
32
+ }>>;
33
+ /** The name of the zone for screen readers, when its text is not enough. */
34
+ readonly label: Readonly<Readonly<{
35
+ get(): string;
36
+ set(setter: string | ((prevState: string) => string)): void;
37
+ }>>;
38
+ readonly required: Readonly<Readonly<{
39
+ get(): boolean;
40
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
41
+ }>>;
42
+ readonly disabled: Readonly<Readonly<{
43
+ get(): boolean;
44
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
45
+ }>>;
46
+ /** Draws the zone in the danger colour, for an error the page found itself. */
47
+ readonly invalid: Readonly<Readonly<{
48
+ get(): boolean;
49
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
50
+ }>>;
51
+ readonly size: Readonly<Readonly<{
52
+ get(): ControlSize;
53
+ set(setter: ((prevState: ControlSize) => ControlSize) | ControlSize): void;
54
+ }>>;
55
+ private readonly _ids;
56
+ private readonly _state;
57
+ private readonly _nameWatch;
58
+ private readonly _refs;
59
+ /** The files chosen, in the order they came. */
60
+ get files(): File[];
61
+ focus(options?: FocusOptions): void;
62
+ /** Opens the file chooser. */
63
+ browse(): void;
64
+ /** Takes every file back, and empties the list of refused ones. */
65
+ clear(): void;
66
+ templateHtml(): Node;
67
+ templateCss(): string;
68
+ onConnect(): void;
69
+ onDisconnect(): void;
70
+ protected resetValue(): void;
71
+ protected setDisabledByForm(disabled: boolean): void;
72
+ private canDrop;
73
+ private onDragEnter;
74
+ private onDragOver;
75
+ private onDragLeave;
76
+ private onDrop;
77
+ private onPicked;
78
+ private onFilesClick;
79
+ private focusAfterRemoval;
80
+ private rules;
81
+ /** Files dropped or picked: the good ones join the list (or replace the file there), the others say why not. */
82
+ private take;
83
+ private setFiles;
84
+ private renderFiles;
85
+ private renderRefused;
86
+ private renderText;
87
+ /** What it takes, under the text: `PNG, JPG · up to 5 MB`. */
88
+ private renderHint;
89
+ /** Each file is its own entry under the field's name, as from a native file input. */
90
+ private renderFormValue;
91
+ private renderValidity;
92
+ private renderDisabled;
93
+ }
@@ -0,0 +1,366 @@
1
+ import { BASE_STYLES } from "../styles/shared.js";
2
+ import { JlFormControl } from "../base/jl-form-control.js";
3
+ import { clampedNumber, oneOf } from "../lib/attributes.js";
4
+ import { registerIcons } from "../lib/icons.js";
5
+ import { CONTROL_SIZES } from "../lib/options.js";
6
+ import { setOrRemove, uniqueId } from "../lib/dom.js";
7
+ import { JlIcon } from "../atoms/jl-icon.js";
8
+ import { pageLocale } from "../lib/locale.js";
9
+ import { checkFiles, describeAccept, formatFileSize, isSameFile } from "../lib/files.js";
10
+ import jl_file_drop_default from "./jl-file-drop.css_raw.js";
11
+ import { css } from "@jadis/core";
12
+ import { CircleAlert, File, Image, Upload, X } from "lucide";
13
+ import { Fragment, jsx, jsxs } from "@jadis/core/jsx-runtime";
14
+ // src/molecules/jl-file-drop.tsx
15
+ registerIcons({
16
+ "circle-alert": CircleAlert,
17
+ file: File,
18
+ image: Image,
19
+ upload: Upload,
20
+ x: X
21
+ });
22
+ var ZONE_ICON_SIZES = {
23
+ lg: 26,
24
+ md: 22,
25
+ sm: 18
26
+ };
27
+ var FILE_ICON_SIZE = 16;
28
+ var REMOVE_ICON_SIZE = 14;
29
+ var ALERT_ICON_SIZE = 14;
30
+ var NO_LIMIT = 0;
31
+ var FILES_TYPE = "Files";
32
+ var IMAGE_FAMILY = "image/";
33
+ var VALUE_MISSING = "Choose a file.";
34
+ var DEFAULT_TEXT = {
35
+ multiple: "Drop files here",
36
+ single: "Drop a file here"
37
+ };
38
+ var REASONS = {
39
+ count: () => "is one too many: the field takes a single file",
40
+ size: (rules) => `is larger than ${formatFileSize(rules.maxSize, pageLocale())}`,
41
+ type: (rules) => `is not an accepted type (${describeAccept(rules.accept)})`
42
+ };
43
+ var hasFiles = (event) => event.dataTransfer?.types.includes(FILES_TYPE) ?? false;
44
+ /**
45
+ * A zone to drop files on, with a button that opens the file chooser. The chosen files are listed under it with
46
+ * their size and a button to take each one back; refused files are listed with the reason. The form gets the
47
+ * files under the field's name, as from a native file input.
48
+ */
49
+ var JlFileDrop = class extends JlFormControl {
50
+ static selector = "jl-file-drop";
51
+ events = this.useEvents();
52
+ /** The types it takes, as a native file input's `accept`: `image/*,.pdf`. */
53
+ accept = this.useChange("", (value) => {
54
+ this._refs.picker.accept = value;
55
+ this.renderHint();
56
+ }, { attribute: "accept" });
57
+ /** Takes several files; otherwise a new file replaces the one there. */
58
+ multiple = this.useChange(false, (value) => {
59
+ this._refs.picker.multiple = value;
60
+ this.renderText();
61
+ }, { attribute: "multiple" });
62
+ /** The largest file it takes, in bytes; 0 for no limit. */
63
+ maxSize = this.useChange(NO_LIMIT, () => this.renderHint(), {
64
+ attribute: "max-size",
65
+ parse: clampedNumber(NO_LIMIT, 0, Number.MAX_SAFE_INTEGER)
66
+ });
67
+ /** The text in the zone; by default it says to drop a file, or files. */
68
+ text = this.useChange("", () => this.renderText(), { attribute: "text" });
69
+ /** The name of the zone for screen readers, when its text is not enough. */
70
+ label = this.useChange("", (value) => setOrRemove(this._refs.zone, "aria-label", value), { attribute: "label" });
71
+ required = this.useChange(false, (value) => {
72
+ this.reflect("required", value);
73
+ this.renderValidity();
74
+ }, { attribute: "required" });
75
+ disabled = this.useChange(false, (value) => this.renderDisabled(value), { attribute: "disabled" });
76
+ /** Draws the zone in the danger colour, for an error the page found itself. */
77
+ invalid = this.useChange(false, (value) => this.reflect("invalid", value), { attribute: "invalid" });
78
+ size = this.useChange("md", (value) => {
79
+ this.reflect("size", value);
80
+ this._refs.zoneIcon.size.set(ZONE_ICON_SIZES[value]);
81
+ }, {
82
+ attribute: "size",
83
+ parse: oneOf(CONTROL_SIZES, "md")
84
+ });
85
+ _ids = {
86
+ hint: uniqueId("jl-file-drop-hint"),
87
+ text: uniqueId("jl-file-drop-text")
88
+ };
89
+ _state = {
90
+ depth: 0,
91
+ files: []
92
+ };
93
+ _nameWatch = new MutationObserver(() => this.renderFormValue());
94
+ _refs = this.useRefs((ref) => ({
95
+ browse: ref(".browse"),
96
+ files: ref(".files"),
97
+ hint: ref(".hint"),
98
+ picker: ref(".picker"),
99
+ refused: ref(".refused"),
100
+ text: ref(".text"),
101
+ zone: ref(".zone"),
102
+ zoneIcon: ref(".zone-icon")
103
+ }));
104
+ /** The files chosen, in the order they came. */
105
+ get files() {
106
+ return [...this._state.files];
107
+ }
108
+ focus(options) {
109
+ this._refs.browse.focus(options);
110
+ }
111
+ /** Opens the file chooser. */
112
+ browse() {
113
+ this.whenRendered(() => {
114
+ if (!this.disabled.get()) this._refs.picker.click();
115
+ });
116
+ }
117
+ /** Takes every file back, and empties the list of refused ones. */
118
+ clear() {
119
+ this.whenRendered(() => {
120
+ this.renderRefused([]);
121
+ this.setFiles([]);
122
+ });
123
+ }
124
+ templateHtml() {
125
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
126
+ /* @__PURE__ */ jsxs("div", {
127
+ class: "zone",
128
+ role: "group",
129
+ "aria-labelledby": this._ids.text,
130
+ "aria-describedby": this._ids.hint,
131
+ attrs: { part: "base" },
132
+ children: [
133
+ /* @__PURE__ */ jsx(JlIcon, {
134
+ class: "zone-icon",
135
+ name: "upload",
136
+ size: ZONE_ICON_SIZES.md
137
+ }),
138
+ /* @__PURE__ */ jsx("p", {
139
+ class: "text",
140
+ id: this._ids.text
141
+ }),
142
+ /* @__PURE__ */ jsx("p", {
143
+ class: "hint",
144
+ id: this._ids.hint,
145
+ hidden: true
146
+ }),
147
+ /* @__PURE__ */ jsx("button", {
148
+ class: "browse",
149
+ type: "button",
150
+ "aria-describedby": this._ids.hint,
151
+ children: "Browse"
152
+ })
153
+ ]
154
+ }),
155
+ /* @__PURE__ */ jsx("input", {
156
+ class: "picker",
157
+ type: "file",
158
+ tabIndex: -1,
159
+ hidden: true
160
+ }),
161
+ /* @__PURE__ */ jsx("ul", {
162
+ class: "refused",
163
+ "aria-live": "polite",
164
+ hidden: true
165
+ }),
166
+ /* @__PURE__ */ jsx("ul", {
167
+ class: "files",
168
+ "aria-label": "Chosen files",
169
+ hidden: true
170
+ })
171
+ ] });
172
+ }
173
+ templateCss() {
174
+ return css`${BASE_STYLES}${jl_file_drop_default}`;
175
+ }
176
+ onConnect() {
177
+ const zone = this._refs.zone;
178
+ this._nameWatch.observe(this, {
179
+ attributeFilter: ["name"],
180
+ attributes: true
181
+ });
182
+ this.renderFormValue();
183
+ this.on(zone, "click", () => this.browse());
184
+ this.on(zone, "dragenter", (event) => this.onDragEnter(event));
185
+ this.on(zone, "dragover", (event) => this.onDragOver(event));
186
+ this.on(zone, "dragleave", () => this.onDragLeave());
187
+ this.on(zone, "drop", (event) => this.onDrop(event));
188
+ this.on(this._refs.picker, "change", () => this.onPicked());
189
+ this.on(this._refs.files, "click", (event) => this.onFilesClick(event));
190
+ }
191
+ onDisconnect() {
192
+ this._nameWatch.disconnect();
193
+ }
194
+ resetValue() {
195
+ this.renderRefused([]);
196
+ this.setFiles([]);
197
+ }
198
+ setDisabledByForm(disabled) {
199
+ this.whenRendered(() => {
200
+ this._refs.browse.disabled = disabled || this.disabled.get();
201
+ });
202
+ }
203
+ canDrop(event) {
204
+ return hasFiles(event) && !this.disabled.get();
205
+ }
206
+ onDragEnter(event) {
207
+ if (!this.canDrop(event)) return;
208
+ event.preventDefault();
209
+ this._state.depth += 1;
210
+ this.reflect("dragging", true);
211
+ }
212
+ onDragOver(event) {
213
+ if (!this.canDrop(event)) return;
214
+ event.preventDefault();
215
+ if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "copy";
216
+ }
217
+ onDragLeave() {
218
+ this._state.depth = Math.max(0, this._state.depth - 1);
219
+ if (this._state.depth === 0) this.reflect("dragging", false);
220
+ }
221
+ onDrop(event) {
222
+ event.preventDefault();
223
+ this._state.depth = 0;
224
+ this.reflect("dragging", false);
225
+ if (this.disabled.get()) return;
226
+ this.take([...event.dataTransfer?.files ?? []]);
227
+ }
228
+ onPicked() {
229
+ const picker = this._refs.picker;
230
+ this.take([...picker.files ?? []]);
231
+ picker.value = "";
232
+ }
233
+ onFilesClick(event) {
234
+ const button = event.target.closest(".file-remove");
235
+ if (button === null) return;
236
+ const index = Number(button.dataset.index);
237
+ this.setFiles(this._state.files.filter((_, position) => position !== index));
238
+ this.events.emit("change", this.files);
239
+ this.focusAfterRemoval(index);
240
+ }
241
+ focusAfterRemoval(index) {
242
+ const buttons = this._refs.files.querySelectorAll(".file-remove");
243
+ const next = buttons[Math.min(index, buttons.length - 1)];
244
+ if (next === void 0) {
245
+ this.focus();
246
+ return;
247
+ }
248
+ next.focus();
249
+ }
250
+ rules() {
251
+ return {
252
+ accept: this.accept.get(),
253
+ maxSize: this.maxSize.get(),
254
+ multiple: this.multiple.get()
255
+ };
256
+ }
257
+ /** Files dropped or picked: the good ones join the list (or replace the file there), the others say why not. */
258
+ take(incoming) {
259
+ if (incoming.length === 0) return;
260
+ const rules = this.rules();
261
+ const kept = this._state.files;
262
+ const fresh = rules.multiple ? incoming.filter((file) => !kept.some((known) => isSameFile(known, file))) : incoming;
263
+ const check = checkFiles(fresh, rules);
264
+ this.renderRefused(check.refused);
265
+ if (check.accepted.length === 0) return;
266
+ this.setFiles(rules.multiple ? [...kept, ...check.accepted] : check.accepted);
267
+ this.events.emit("change", this.files);
268
+ }
269
+ setFiles(files) {
270
+ this._state.files = files;
271
+ this.renderFiles();
272
+ this.renderFormValue();
273
+ this.renderValidity();
274
+ }
275
+ renderFiles() {
276
+ const files = this._state.files;
277
+ const editable = !this.disabled.get();
278
+ const locale = pageLocale();
279
+ this._refs.files.hidden = files.length === 0;
280
+ this._refs.files.replaceChildren(...files.map((file, index) => /* @__PURE__ */ jsxs("li", {
281
+ class: "file",
282
+ children: [
283
+ /* @__PURE__ */ jsx(JlIcon, {
284
+ class: "file-icon",
285
+ name: file.type.startsWith(IMAGE_FAMILY) ? "image" : "file",
286
+ size: FILE_ICON_SIZE
287
+ }),
288
+ /* @__PURE__ */ jsx("span", {
289
+ class: "file-name",
290
+ title: file.name,
291
+ children: file.name
292
+ }),
293
+ /* @__PURE__ */ jsx("span", {
294
+ class: "file-size",
295
+ children: formatFileSize(file.size, locale)
296
+ }),
297
+ editable ? /* @__PURE__ */ jsx("button", {
298
+ class: "file-remove",
299
+ type: "button",
300
+ "aria-label": `Remove ${file.name}`,
301
+ "data-index": String(index),
302
+ children: /* @__PURE__ */ jsx(JlIcon, {
303
+ name: "x",
304
+ size: REMOVE_ICON_SIZE
305
+ })
306
+ }) : null
307
+ ]
308
+ })));
309
+ }
310
+ renderRefused(refused) {
311
+ const rules = this.rules();
312
+ this._refs.refused.hidden = refused.length === 0;
313
+ this._refs.refused.replaceChildren(...refused.map(({ file, reason }) => /* @__PURE__ */ jsxs("li", {
314
+ class: "refusal",
315
+ children: [/* @__PURE__ */ jsx(JlIcon, {
316
+ name: "circle-alert",
317
+ size: ALERT_ICON_SIZE
318
+ }), /* @__PURE__ */ jsxs("span", { children: [
319
+ /* @__PURE__ */ jsx("span", {
320
+ class: "refused-name",
321
+ children: file.name
322
+ }),
323
+ " ",
324
+ REASONS[reason](rules)
325
+ ] })]
326
+ })));
327
+ }
328
+ renderText() {
329
+ const text = this.text.get();
330
+ const fallback = this.multiple.get() ? DEFAULT_TEXT.multiple : DEFAULT_TEXT.single;
331
+ this._refs.text.textContent = text === "" ? fallback : text;
332
+ }
333
+ /** What it takes, under the text: `PNG, JPG · up to 5 MB`. */
334
+ renderHint() {
335
+ const types = describeAccept(this.accept.get());
336
+ const maxSize = this.maxSize.get();
337
+ const hint = [types, maxSize === NO_LIMIT ? "" : `up to ${formatFileSize(maxSize, pageLocale())}`].filter((part) => part !== "").join(" · ");
338
+ this._refs.hint.textContent = hint;
339
+ this._refs.hint.hidden = hint === "";
340
+ }
341
+ /** Each file is its own entry under the field's name, as from a native file input. */
342
+ renderFormValue() {
343
+ const name = this.getAttribute("name") ?? "";
344
+ if (name === "") {
345
+ this.internals.setFormValue(null);
346
+ return;
347
+ }
348
+ const data = new FormData();
349
+ for (const file of this._state.files) data.append(name, file);
350
+ this.internals.setFormValue(data);
351
+ }
352
+ renderValidity() {
353
+ if (this.required.get() && this._state.files.length === 0) {
354
+ this.internals.setValidity({ valueMissing: true }, VALUE_MISSING, this._refs.browse);
355
+ return;
356
+ }
357
+ this.internals.setValidity({});
358
+ }
359
+ renderDisabled(disabled) {
360
+ this._refs.browse.disabled = disabled;
361
+ this.reflect("disabled", disabled);
362
+ this.renderFiles();
363
+ }
364
+ };
365
+ JlFileDrop.register();
366
+ export { JlFileDrop };
@@ -0,0 +1,3 @@
1
+ // src/molecules/jl-list-item.css?raw
2
+ var jl_list_item_default = ":host {\n display: block;\n --pad-x: 16px;\n --pad-y: 12px;\n}\n\n.item {\n position: relative;\n display: flex;\n align-items: center;\n gap: 12px;\n min-height: 52px;\n padding: var(--pad-y) var(--pad-x);\n background: var(--jl-surface);\n color: var(--jl-text);\n transition: background var(--jl-fast) var(--jl-ease);\n}\n\n/* Rows that follow each other are separated by a line; the last one leaves the edge to its container. */\n:host(:not(:last-child)) .item {\n border-bottom: 1px solid var(--jl-line);\n}\n\n:host([clickable]) .item {\n cursor: pointer;\n}\n\n:host([clickable]) .item:hover {\n background: var(--jl-surface-2);\n}\n\n:host([selected]) .item,\n:host([selected][clickable]) .item:hover {\n background: var(--jl-accent-soft);\n}\n\n/* The link covers the row, so a press anywhere follows it; the trailing controls sit above it. */\n.link {\n position: absolute;\n inset: 0;\n z-index: 1;\n}\n\n.link:focus-visible,\n.item:focus-visible {\n box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--jl-brand) 55%, transparent);\n}\n\n.end {\n position: relative;\n z-index: 2;\n display: flex;\n flex: none;\n align-items: center;\n gap: 8px;\n}\n\n.start {\n display: inline-flex;\n flex: none;\n align-items: center;\n}\n\n.icon-box {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: var(--jl-radius-sm);\n background: var(--jl-surface-3);\n color: var(--jl-text-2);\n}\n\n:host([selected]) .icon-box {\n background: color-mix(in srgb, var(--jl-accent) 14%, transparent);\n color: var(--jl-accent);\n}\n\n.text {\n display: grid;\n flex: 1;\n gap: 1px;\n min-width: 0;\n}\n\n.heading {\n overflow: hidden;\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 1.4;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n:host([selected]) .heading {\n color: var(--jl-accent);\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n line-height: 1.45;\n}\n\n.meta {\n flex: none;\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.chevron {\n margin-right: -4px;\n color: var(--jl-faint);\n}\n";
3
+ export { jl_list_item_default as default };