@brandsync/wc 1.6.0 → 1.7.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 (267) hide show
  1. package/custom-elements.json +1122 -98
  2. package/dist/brandsync-wc/brandsync-wc.esm.js +1 -1
  3. package/dist/brandsync-wc/{p-44bfdd4b.entry.js → p-06e9b45d.entry.js} +1 -1
  4. package/dist/brandsync-wc/p-0d86c22d.entry.js +1 -0
  5. package/dist/brandsync-wc/{p-e2ac61c4.entry.js → p-13e4966c.entry.js} +1 -1
  6. package/dist/brandsync-wc/{p-18c59979.entry.js → p-18021c05.entry.js} +1 -1
  7. package/dist/brandsync-wc/{p-cfb49342.entry.js → p-1c8160c2.entry.js} +1 -1
  8. package/dist/brandsync-wc/{p-00f733ef.entry.js → p-21c14465.entry.js} +1 -1
  9. package/dist/brandsync-wc/{p-6d0c054b.entry.js → p-27100489.entry.js} +1 -1
  10. package/dist/brandsync-wc/{p-639946e7.entry.js → p-2d6955d6.entry.js} +1 -1
  11. package/dist/brandsync-wc/{p-3ed5b215.entry.js → p-309f920e.entry.js} +1 -1
  12. package/dist/brandsync-wc/{p-fcd4ec60.entry.js → p-30d6a6d0.entry.js} +1 -1
  13. package/dist/brandsync-wc/{p-57e1dc35.entry.js → p-3eab22a5.entry.js} +1 -1
  14. package/dist/brandsync-wc/{p-57609703.entry.js → p-4b0938a5.entry.js} +1 -1
  15. package/dist/brandsync-wc/{p-512dafc6.entry.js → p-5312b6a5.entry.js} +1 -1
  16. package/dist/brandsync-wc/{p-9fb8848c.entry.js → p-538cc08e.entry.js} +1 -1
  17. package/dist/brandsync-wc/p-55e6438c.entry.js +1 -0
  18. package/dist/brandsync-wc/{p-a8cdf449.entry.js → p-600b5434.entry.js} +1 -1
  19. package/dist/brandsync-wc/p-61db6ce2.entry.js +1 -0
  20. package/dist/brandsync-wc/{p-ef68f29b.entry.js → p-70e035ed.entry.js} +1 -1
  21. package/dist/brandsync-wc/{p-0aca5f0e.entry.js → p-77433277.entry.js} +1 -1
  22. package/dist/brandsync-wc/{p-f714683d.entry.js → p-777a9dbf.entry.js} +1 -1
  23. package/dist/brandsync-wc/p-7ed5670e.entry.js +1 -0
  24. package/dist/brandsync-wc/{p-e35249c4.entry.js → p-819a3dae.entry.js} +1 -1
  25. package/dist/brandsync-wc/{p-11224cab.entry.js → p-85c70f6d.entry.js} +1 -1
  26. package/dist/brandsync-wc/{p-525504c2.entry.js → p-8b2ab522.entry.js} +1 -1
  27. package/dist/brandsync-wc/{p-abda1d69.entry.js → p-8b57f606.entry.js} +1 -1
  28. package/dist/brandsync-wc/{p-a603a13a.entry.js → p-9c1be2a0.entry.js} +1 -1
  29. package/dist/brandsync-wc/{p-604553a0.entry.js → p-a36a0e6a.entry.js} +1 -1
  30. package/dist/brandsync-wc/{p-3cb58df8.entry.js → p-b0634051.entry.js} +1 -1
  31. package/dist/brandsync-wc/{p-7b9598d9.entry.js → p-b2dc9886.entry.js} +1 -1
  32. package/dist/brandsync-wc/{p-413a1ee0.entry.js → p-bcee3d60.entry.js} +1 -1
  33. package/dist/brandsync-wc/{p-a3a4d541.entry.js → p-be14f99f.entry.js} +1 -1
  34. package/dist/brandsync-wc/{p-52d5f6a9.entry.js → p-c197cc95.entry.js} +1 -1
  35. package/dist/brandsync-wc/{p-9b72cc12.entry.js → p-c3a28a89.entry.js} +1 -1
  36. package/dist/brandsync-wc/{p-43617f2e.entry.js → p-d06543a5.entry.js} +1 -1
  37. package/dist/brandsync-wc/{p-c5011746.entry.js → p-d25b7b74.entry.js} +1 -1
  38. package/dist/brandsync-wc/{p-bb77098b.entry.js → p-e1069ba9.entry.js} +1 -1
  39. package/dist/brandsync-wc/p-e427bbda.entry.js +1 -0
  40. package/dist/brandsync-wc/{p-bb68e236.entry.js → p-e61efe4d.entry.js} +1 -1
  41. package/dist/brandsync-wc/{p-6c769a1d.entry.js → p-f14bda01.entry.js} +1 -1
  42. package/dist/brandsync-wc/{p-c054f493.entry.js → p-f243683f.entry.js} +1 -1
  43. package/dist/brandsync-wc/{p-7609221f.entry.js → p-f518f486.entry.js} +1 -1
  44. package/dist/brandsync-wc/{p-98021881.entry.js → p-f8f03334.entry.js} +1 -1
  45. package/dist/brandsync-wc/{p-a8e2a2b2.entry.js → p-fba01c96.entry.js} +1 -1
  46. package/dist/brandsync-wc/{p-db46376b.entry.js → p-fde5e9f1.entry.js} +1 -1
  47. package/dist/cjs/brandsync-wc.cjs.js +1 -1
  48. package/dist/cjs/bs-ai-disclaimer.cjs.entry.js +1 -1
  49. package/dist/cjs/bs-ai-greeting.cjs.entry.js +1 -1
  50. package/dist/cjs/bs-ai-thinking.cjs.entry.js +1 -1
  51. package/dist/cjs/bs-attachment-list.cjs.entry.js +1 -1
  52. package/dist/cjs/bs-breadcrumbs.cjs.entry.js +1 -1
  53. package/dist/cjs/bs-button-skeleton.cjs.entry.js +1 -1
  54. package/dist/cjs/bs-carousel.cjs.entry.js +196 -0
  55. package/dist/cjs/bs-chatbot-feedback.cjs.entry.js +1 -1
  56. package/dist/cjs/bs-chatbot-header.cjs.entry.js +1 -1
  57. package/dist/cjs/bs-chatbot-response-action.cjs.entry.js +1 -1
  58. package/dist/cjs/bs-chatbot-sources-drawer.cjs.entry.js +1 -1
  59. package/dist/cjs/bs-chatbot-suggestion-button.cjs.entry.js +1 -1
  60. package/dist/cjs/bs-checkbox-skeleton.cjs.entry.js +1 -1
  61. package/dist/cjs/bs-checkbox.cjs.entry.js +1 -1
  62. package/dist/cjs/bs-chip-filter.cjs.entry.js +1 -1
  63. package/dist/cjs/bs-chip-informative.cjs.entry.js +1 -1
  64. package/dist/cjs/bs-chip-input.cjs.entry.js +1 -1
  65. package/dist/cjs/bs-composer-status-banner.cjs.entry.js +1 -1
  66. package/dist/cjs/bs-composer.cjs.entry.js +1 -1
  67. package/dist/cjs/bs-data-table.cjs.entry.js +2 -2
  68. package/dist/cjs/bs-file-upload-item.cjs.entry.js +85 -0
  69. package/dist/cjs/bs-file-upload.cjs.entry.js +110 -0
  70. package/dist/cjs/bs-icon-button.cjs.entry.js +1 -1
  71. package/dist/cjs/bs-inline-tab.cjs.entry.js +1 -1
  72. package/dist/cjs/bs-input.cjs.entry.js +2 -2
  73. package/dist/cjs/bs-menu-item.cjs.entry.js +1 -1
  74. package/dist/cjs/bs-menu.cjs.entry.js +1 -1
  75. package/dist/cjs/bs-navigation-drawer-item.cjs.entry.js +1 -1
  76. package/dist/cjs/bs-navigation-drawer.cjs.entry.js +1 -1
  77. package/dist/cjs/bs-navigation-header.cjs.entry.js +1 -1
  78. package/dist/cjs/bs-pagination.cjs.entry.js +1 -1
  79. package/dist/cjs/bs-progress-linear.cjs.entry.js +7 -2
  80. package/dist/cjs/bs-progress.cjs.entry.js +2 -2
  81. package/dist/cjs/bs-radio.cjs.entry.js +1 -1
  82. package/dist/cjs/bs-slider.cjs.entry.js +1 -1
  83. package/dist/cjs/bs-snackbar.cjs.entry.js +1 -1
  84. package/dist/cjs/bs-source-link.cjs.entry.js +2 -2
  85. package/dist/cjs/bs-stepper-step.cjs.entry.js +1 -1
  86. package/dist/cjs/bs-stepper.cjs.entry.js +1 -1
  87. package/dist/cjs/bs-switch.cjs.entry.js +1 -1
  88. package/dist/cjs/bs-tab.cjs.entry.js +1 -1
  89. package/dist/cjs/bs-tabs.cjs.entry.js +1 -1
  90. package/dist/cjs/bs-toast.cjs.entry.js +1 -1
  91. package/dist/cjs/bs-tooltip.cjs.entry.js +1 -1
  92. package/dist/cjs/loader.cjs.js +1 -1
  93. package/dist/collection/collection-manifest.json +3 -0
  94. package/dist/collection/components/bs-accordion/bs-accordion.cmp.test.js +5 -0
  95. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.js +1 -1
  96. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.js +1 -1
  97. package/dist/collection/components/bs-ai-thinking/bs-ai-thinking.js +1 -1
  98. package/dist/collection/components/bs-attachment-list/bs-attachment-list.js +1 -1
  99. package/dist/collection/components/bs-breadcrumb/bs-breadcrumbs/bs-breadcrumbs.js +1 -1
  100. package/dist/collection/components/bs-button/bs-icon-button/bs-icon-button.js +1 -1
  101. package/dist/collection/components/bs-button-skeleton/bs-button-skeleton.js +1 -1
  102. package/dist/collection/components/bs-carousel/bs-carousel.cmp.test.js +198 -0
  103. package/dist/collection/components/bs-carousel/bs-carousel.css +107 -0
  104. package/dist/collection/components/bs-carousel/bs-carousel.js +531 -0
  105. package/dist/collection/components/bs-carousel/bs-carousel.stories.js +68 -0
  106. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.js +1 -1
  107. package/dist/collection/components/bs-chatbot-header/bs-chatbot-header.js +1 -1
  108. package/dist/collection/components/bs-chatbot-response-action/bs-chatbot-response-action.js +1 -1
  109. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.js +1 -1
  110. package/dist/collection/components/bs-chatbot-suggestion-button/bs-chatbot-suggestion-button.js +1 -1
  111. package/dist/collection/components/bs-checkbox/bs-checkbox-skeleton/bs-checkbox-skeleton.js +1 -1
  112. package/dist/collection/components/bs-checkbox/bs-checkbox.js +1 -1
  113. package/dist/collection/components/bs-chip/bs-chip-filter/bs-chip-filter.js +1 -1
  114. package/dist/collection/components/bs-chip/bs-chip-informative/bs-chip-informative.js +1 -1
  115. package/dist/collection/components/bs-chip/bs-chip-input/bs-chip-input.js +1 -1
  116. package/dist/collection/components/bs-composer/bs-composer.js +1 -1
  117. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.js +1 -1
  118. package/dist/collection/components/bs-data-table/bs-data-table.js +2 -2
  119. package/dist/collection/components/bs-file-upload/bs-file-upload.cmp.test.js +119 -0
  120. package/dist/collection/components/bs-file-upload/bs-file-upload.css +139 -0
  121. package/dist/collection/components/bs-file-upload/bs-file-upload.js +342 -0
  122. package/dist/collection/components/bs-file-upload/bs-file-upload.stories.js +116 -0
  123. package/dist/collection/components/bs-file-upload-item/bs-file-upload-item.cmp.test.js +115 -0
  124. package/dist/collection/components/bs-file-upload-item/bs-file-upload-item.css +154 -0
  125. package/dist/collection/components/bs-file-upload-item/bs-file-upload-item.js +329 -0
  126. package/dist/collection/components/bs-file-upload-item/bs-file-upload-item.stories.js +54 -0
  127. package/dist/collection/components/bs-input/bs-input.js +2 -2
  128. package/dist/collection/components/bs-menu/bs-menu.js +1 -1
  129. package/dist/collection/components/bs-menu-item/bs-menu-item.js +1 -1
  130. package/dist/collection/components/bs-pagination/bs-pagination.js +1 -1
  131. package/dist/collection/components/bs-progress/bs-progress.js +2 -2
  132. package/dist/collection/components/bs-progress-linear/bs-progress-linear.js +27 -2
  133. package/dist/collection/components/bs-radio/bs-radio.js +1 -1
  134. package/dist/collection/components/bs-slider/bs-slider.js +1 -1
  135. package/dist/collection/components/bs-snackbar/bs-snackbar.js +1 -1
  136. package/dist/collection/components/bs-source-link/bs-source-link.js +2 -2
  137. package/dist/collection/components/bs-stepper/bs-stepper/bs-stepper.js +1 -1
  138. package/dist/collection/components/bs-stepper/bs-stepper-step/bs-stepper-step.js +1 -1
  139. package/dist/collection/components/bs-switch/bs-switch.js +1 -1
  140. package/dist/collection/components/bs-tab/bs-inline-tab/bs-inline-tab.js +1 -1
  141. package/dist/collection/components/bs-tab/bs-tab/bs-tab.js +1 -1
  142. package/dist/collection/components/bs-tab/bs-tabs/bs-tabs.js +1 -1
  143. package/dist/collection/components/bs-toast/bs-toast.js +1 -1
  144. package/dist/collection/components/bs-tooltip/bs-tooltip.js +1 -1
  145. package/dist/collection/components/ui-shell/bs-navigation-drawer/bs-navigation-drawer/bs-navigation-drawer.js +1 -1
  146. package/dist/collection/components/ui-shell/bs-navigation-drawer/bs-navigation-drawer-item/bs-navigation-drawer-item.js +1 -1
  147. package/dist/collection/components/ui-shell/bs-navigation-header/bs-navigation-header/bs-navigation-header.js +1 -1
  148. package/dist/collection/test/helpers/conventions.js +46 -0
  149. package/dist/collection/test/helpers/conventions.unit.test.js +38 -0
  150. package/dist/components/bs-ai-disclaimer.js +1 -1
  151. package/dist/components/bs-ai-greeting.js +1 -1
  152. package/dist/components/bs-ai-thinking.js +1 -1
  153. package/dist/components/bs-attachment-list.js +1 -1
  154. package/dist/components/bs-attachment.js +1 -1
  155. package/dist/components/bs-breadcrumb-overflow.js +1 -1
  156. package/dist/components/bs-breadcrumbs.js +1 -1
  157. package/dist/components/bs-button-skeleton.js +1 -1
  158. package/dist/components/bs-carousel.d.ts +11 -0
  159. package/dist/components/bs-carousel.js +1 -0
  160. package/dist/components/bs-chatbot-feedback.js +1 -1
  161. package/dist/components/bs-chatbot-header.js +1 -1
  162. package/dist/components/bs-chatbot-response-action.js +1 -1
  163. package/dist/components/bs-chatbot-sources-drawer.js +1 -1
  164. package/dist/components/bs-chatbot-suggestion-button.js +1 -1
  165. package/dist/components/bs-checkbox-skeleton.js +1 -1
  166. package/dist/components/bs-checkbox.js +1 -1
  167. package/dist/components/bs-chip-filter.js +1 -1
  168. package/dist/components/bs-chip-informative.js +1 -1
  169. package/dist/components/bs-chip-input.js +1 -1
  170. package/dist/components/bs-composer-status-banner.js +1 -1
  171. package/dist/components/bs-composer.js +1 -1
  172. package/dist/components/bs-data-table.js +1 -1
  173. package/dist/components/bs-dialog.js +1 -1
  174. package/dist/components/bs-file-upload-item.d.ts +11 -0
  175. package/dist/components/bs-file-upload-item.js +1 -0
  176. package/dist/components/bs-file-upload.d.ts +11 -0
  177. package/dist/components/bs-file-upload.js +1 -0
  178. package/dist/components/bs-icon-button.js +1 -1
  179. package/dist/components/bs-inline-tab.js +1 -1
  180. package/dist/components/bs-input.js +1 -1
  181. package/dist/components/bs-menu-item.js +1 -1
  182. package/dist/components/bs-menu.js +1 -1
  183. package/dist/components/bs-navigation-drawer-item.js +1 -1
  184. package/dist/components/bs-navigation-drawer.js +1 -1
  185. package/dist/components/bs-navigation-header.js +1 -1
  186. package/dist/components/bs-pagination.js +1 -1
  187. package/dist/components/bs-progress-linear.js +1 -1
  188. package/dist/components/bs-progress.js +1 -1
  189. package/dist/components/bs-radio.js +1 -1
  190. package/dist/components/bs-slider.js +1 -1
  191. package/dist/components/bs-snackbar.js +1 -1
  192. package/dist/components/bs-source-link.js +1 -1
  193. package/dist/components/bs-stepper-step.js +1 -1
  194. package/dist/components/bs-stepper.js +1 -1
  195. package/dist/components/bs-switch.js +1 -1
  196. package/dist/components/bs-tab.js +1 -1
  197. package/dist/components/bs-tabs.js +1 -1
  198. package/dist/components/bs-toast.js +1 -1
  199. package/dist/components/bs-tooltip.js +1 -1
  200. package/dist/components/p-BxFJ74fI.js +1 -0
  201. package/dist/components/{p-DKMMCLxx.js → p-DI5GgSqL.js} +1 -1
  202. package/dist/components/{p-DIezWdNu.js → p-Dqt-Qp4W.js} +1 -1
  203. package/dist/components/p-Dx8gAOVC.js +1 -0
  204. package/dist/components/{p-B8eut73c.js → p-LYvoWZt4.js} +1 -1
  205. package/dist/components/{p-3gs695M6.js → p-v4T0qPVn.js} +1 -1
  206. package/dist/esm/brandsync-wc.js +1 -1
  207. package/dist/esm/bs-ai-disclaimer.entry.js +1 -1
  208. package/dist/esm/bs-ai-greeting.entry.js +1 -1
  209. package/dist/esm/bs-ai-thinking.entry.js +1 -1
  210. package/dist/esm/bs-attachment-list.entry.js +1 -1
  211. package/dist/esm/bs-breadcrumbs.entry.js +1 -1
  212. package/dist/esm/bs-button-skeleton.entry.js +1 -1
  213. package/dist/esm/bs-carousel.entry.js +194 -0
  214. package/dist/esm/bs-chatbot-feedback.entry.js +1 -1
  215. package/dist/esm/bs-chatbot-header.entry.js +1 -1
  216. package/dist/esm/bs-chatbot-response-action.entry.js +1 -1
  217. package/dist/esm/bs-chatbot-sources-drawer.entry.js +1 -1
  218. package/dist/esm/bs-chatbot-suggestion-button.entry.js +1 -1
  219. package/dist/esm/bs-checkbox-skeleton.entry.js +1 -1
  220. package/dist/esm/bs-checkbox.entry.js +1 -1
  221. package/dist/esm/bs-chip-filter.entry.js +1 -1
  222. package/dist/esm/bs-chip-informative.entry.js +1 -1
  223. package/dist/esm/bs-chip-input.entry.js +1 -1
  224. package/dist/esm/bs-composer-status-banner.entry.js +1 -1
  225. package/dist/esm/bs-composer.entry.js +1 -1
  226. package/dist/esm/bs-data-table.entry.js +2 -2
  227. package/dist/esm/bs-file-upload-item.entry.js +83 -0
  228. package/dist/esm/bs-file-upload.entry.js +108 -0
  229. package/dist/esm/bs-icon-button.entry.js +1 -1
  230. package/dist/esm/bs-inline-tab.entry.js +1 -1
  231. package/dist/esm/bs-input.entry.js +2 -2
  232. package/dist/esm/bs-menu-item.entry.js +1 -1
  233. package/dist/esm/bs-menu.entry.js +1 -1
  234. package/dist/esm/bs-navigation-drawer-item.entry.js +1 -1
  235. package/dist/esm/bs-navigation-drawer.entry.js +1 -1
  236. package/dist/esm/bs-navigation-header.entry.js +1 -1
  237. package/dist/esm/bs-pagination.entry.js +1 -1
  238. package/dist/esm/bs-progress-linear.entry.js +7 -2
  239. package/dist/esm/bs-progress.entry.js +2 -2
  240. package/dist/esm/bs-radio.entry.js +1 -1
  241. package/dist/esm/bs-slider.entry.js +1 -1
  242. package/dist/esm/bs-snackbar.entry.js +1 -1
  243. package/dist/esm/bs-source-link.entry.js +2 -2
  244. package/dist/esm/bs-stepper-step.entry.js +1 -1
  245. package/dist/esm/bs-stepper.entry.js +1 -1
  246. package/dist/esm/bs-switch.entry.js +1 -1
  247. package/dist/esm/bs-tab.entry.js +1 -1
  248. package/dist/esm/bs-tabs.entry.js +1 -1
  249. package/dist/esm/bs-toast.entry.js +1 -1
  250. package/dist/esm/bs-tooltip.entry.js +1 -1
  251. package/dist/esm/loader.js +1 -1
  252. package/dist/types/components/bs-carousel/bs-carousel.cmp.test.d.ts +1 -0
  253. package/dist/types/components/bs-carousel/bs-carousel.d.ts +101 -0
  254. package/dist/types/components/bs-carousel/bs-carousel.stories.d.ts +17 -0
  255. package/dist/types/components/bs-file-upload/bs-file-upload.cmp.test.d.ts +1 -0
  256. package/dist/types/components/bs-file-upload/bs-file-upload.d.ts +59 -0
  257. package/dist/types/components/bs-file-upload/bs-file-upload.stories.d.ts +21 -0
  258. package/dist/types/components/bs-file-upload-item/bs-file-upload-item.cmp.test.d.ts +1 -0
  259. package/dist/types/components/bs-file-upload-item/bs-file-upload-item.d.ts +58 -0
  260. package/dist/types/components/bs-file-upload-item/bs-file-upload-item.stories.d.ts +18 -0
  261. package/dist/types/components/bs-progress-linear/bs-progress-linear.d.ts +5 -0
  262. package/dist/types/components.d.ts +594 -0
  263. package/dist/types/test/helpers/conventions.d.ts +21 -0
  264. package/dist/types/test/helpers/conventions.unit.test.d.ts +1 -0
  265. package/package.json +2 -1
  266. package/dist/brandsync-wc/p-6bf8a5bd.entry.js +0 -1
  267. package/dist/brandsync-wc/p-fe778b6b.entry.js +0 -1
@@ -0,0 +1,342 @@
1
+ import { h } from "@stencil/core";
2
+ /**
3
+ * A file dropzone: drag files onto it, or press "Browse file" to open the file picker. It reports
4
+ * the chosen files and does nothing else -- the consuming app owns the upload itself and shows its
5
+ * progress with `bs-file-upload-item`.
6
+ *
7
+ * ## When to use
8
+ * - Letting users add one or more files (documents, images) to upload.
9
+ *
10
+ * ## When not to use
11
+ * - Attaching files to a chat message -- see `bs-composer` / `bs-attachment`.
12
+ * - A file input that must submit with a native `<form>`: this component is **not**
13
+ * form-associated. Read the files from `bsFilesSelect` and submit them yourself.
14
+ *
15
+ * @slot icon - Replaces the default upload icon inside the round icon area.
16
+ * @part dropzone - The dashed drop area.
17
+ * @part icon - The round icon area.
18
+ * @part heading - The heading text.
19
+ * @part description - The guidelines text under the heading.
20
+ * @part browse - The "Browse file" button.
21
+ */
22
+ // Note: this component's CSS Custom Properties (--bs-file-upload-*) are documented via @prop
23
+ // comments in bs-file-upload.css, not here (see bs-input.tsx's equivalent note).
24
+ export class BsFileUpload {
25
+ el;
26
+ inputEl;
27
+ /** Layout: icon beside the text (`horizontal`) or stacked above it, centered (`vertical`). */
28
+ direction = 'horizontal';
29
+ /** Heading text (e.g. "Upload your documents"). */
30
+ heading;
31
+ /** Guidelines under the heading (e.g. "PDF or JPG, up to 8MB"). */
32
+ description;
33
+ /** Label of the browse button. Override for localisation. */
34
+ buttonLabel = 'Browse file';
35
+ /** Which files are accepted, same syntax as the native `accept` attribute: comma-separated
36
+ * extensions (`.pdf`), MIME types (`image/png`) or wildcards (`image/*`). Applied to the picker
37
+ * and also to dropped files, which the browser does not filter. */
38
+ accept;
39
+ /** Allows selecting/dropping more than one file. Without it only the first file is taken. */
40
+ multiple = false;
41
+ /** Disables the browse button and ignores drops. */
42
+ disabled = false;
43
+ /** Whether a file is currently being dragged over the dropzone (the Figma "Drop" state). Set by
44
+ * the component during a drag; mutable and reflected so it can also be set to show the state
45
+ * statically. */
46
+ dragging = false;
47
+ /** Fires with the accepted files, after the picker closes or files are dropped. */
48
+ bsFilesSelect;
49
+ /** Fires with files that were not taken: they didn't match `accept`, or were extra files
50
+ * dropped while `multiple` is off. */
51
+ bsFilesReject;
52
+ matchesAccept(file) {
53
+ const tokens = (this.accept ?? '')
54
+ .split(',')
55
+ .map(t => t.trim().toLowerCase())
56
+ .filter(Boolean);
57
+ if (tokens.length === 0)
58
+ return true;
59
+ const name = file.name.toLowerCase();
60
+ const type = (file.type || '').toLowerCase();
61
+ return tokens.some(token => {
62
+ if (token.startsWith('.'))
63
+ return name.endsWith(token);
64
+ if (token.endsWith('/*'))
65
+ return type.startsWith(token.slice(0, -1));
66
+ return type === token;
67
+ });
68
+ }
69
+ handleFiles(files) {
70
+ const matching = files.filter(f => this.matchesAccept(f));
71
+ const rejected = files.filter(f => !this.matchesAccept(f));
72
+ const accepted = this.multiple ? matching : matching.slice(0, 1);
73
+ if (!this.multiple)
74
+ rejected.push(...matching.slice(1));
75
+ if (accepted.length)
76
+ this.bsFilesSelect.emit(accepted);
77
+ if (rejected.length)
78
+ this.bsFilesReject.emit(rejected);
79
+ }
80
+ onBrowse = () => {
81
+ if (!this.disabled)
82
+ this.inputEl?.click();
83
+ };
84
+ onInputChange = () => {
85
+ const files = Array.from(this.inputEl?.files ?? []);
86
+ // Reset so choosing the same file again still fires `change`.
87
+ if (this.inputEl)
88
+ this.inputEl.value = '';
89
+ if (files.length)
90
+ this.handleFiles(files);
91
+ };
92
+ onDragOver = (ev) => {
93
+ if (this.disabled)
94
+ return;
95
+ ev.preventDefault();
96
+ this.dragging = true;
97
+ };
98
+ onDragLeave = (ev) => {
99
+ // dragleave also fires when crossing between child elements; only clear when leaving the zone.
100
+ const next = ev.relatedTarget;
101
+ if (next && ev.currentTarget.contains(next))
102
+ return;
103
+ this.dragging = false;
104
+ };
105
+ onDrop = (ev) => {
106
+ if (this.disabled)
107
+ return;
108
+ ev.preventDefault();
109
+ this.dragging = false;
110
+ const files = Array.from(ev.dataTransfer?.files ?? []);
111
+ if (files.length)
112
+ this.handleFiles(files);
113
+ };
114
+ render() {
115
+ return (h("div", { key: 'baa70a704a4c3508cd3186f567d0887218a9c04d', part: "dropzone", class: `bs-file-upload bs-file-upload--${this.direction} ${this.dragging ? 'bs-file-upload--dragging' : ''} ${this.disabled ? 'bs-file-upload--disabled' : ''}`, onDragEnter: this.onDragOver, onDragOver: this.onDragOver, onDragLeave: this.onDragLeave, onDrop: this.onDrop }, h("div", { key: 'c5fe3207ba56506a272c42097ec23881bcf863d4', part: "icon", class: "bs-file-upload__icon" }, h("slot", { key: 'd33d8f0f7b17bab679124823c61a1b7c6d031345', name: "icon" }, h(UploadIcon, { key: 'd0356e97a031867f323de21905a2bbca57963962' }))), h("div", { key: 'a5f4fb403556b2b596291bfddad2b73b1765f91f', class: "bs-file-upload__content" }, h("div", { key: 'b99afac250a8d40e54c97f74afde42620da2ff07', class: "bs-file-upload__text" }, this.heading && (h("span", { key: '33a2ce5aac1d8bf15c55dcf8ccfe4fbce6892fd1', part: "heading", class: "bs-file-upload__heading" }, this.heading)), this.description && (h("span", { key: 'eec7f2fcc598412107e7b9c78efae30a3714c674', part: "description", id: "description", class: "bs-file-upload__description" }, this.description))), h("bs-button", { key: 'f44a5747bb0e9452aefa1fe2fc1d51b1af7da9b1', part: "browse", variant: "neutral", size: "sm", disabled: this.disabled, onClick: this.onBrowse }, this.buttonLabel)), h("input", { key: 'c8e19f95978d103f535bf26fb64c34ff69c4c7ee', ref: el => (this.inputEl = el), class: "bs-file-upload__input", type: "file", hidden: true, tabindex: -1, accept: this.accept, multiple: this.multiple, disabled: this.disabled, "aria-describedby": this.description ? 'description' : undefined, onChange: this.onInputChange })));
116
+ }
117
+ static get is() { return "bs-file-upload"; }
118
+ static get encapsulation() { return "shadow"; }
119
+ static get originalStyleUrls() {
120
+ return {
121
+ "$": ["bs-file-upload.css"]
122
+ };
123
+ }
124
+ static get styleUrls() {
125
+ return {
126
+ "$": ["bs-file-upload.css"]
127
+ };
128
+ }
129
+ static get properties() {
130
+ return {
131
+ "direction": {
132
+ "type": "string",
133
+ "mutable": false,
134
+ "complexType": {
135
+ "original": "BsFileUploadDirection",
136
+ "resolved": "\"horizontal\" | \"vertical\"",
137
+ "references": {
138
+ "BsFileUploadDirection": {
139
+ "location": "local",
140
+ "path": "/home/runner/work/eg-brandsync-storybook/eg-brandsync-storybook/packages/wc/src/components/bs-file-upload/bs-file-upload.tsx",
141
+ "id": "src/components/bs-file-upload/bs-file-upload.tsx::BsFileUploadDirection"
142
+ }
143
+ }
144
+ },
145
+ "required": false,
146
+ "optional": false,
147
+ "docs": {
148
+ "tags": [],
149
+ "text": "Layout: icon beside the text (`horizontal`) or stacked above it, centered (`vertical`)."
150
+ },
151
+ "getter": false,
152
+ "setter": false,
153
+ "reflect": true,
154
+ "attribute": "direction",
155
+ "defaultValue": "'horizontal'"
156
+ },
157
+ "heading": {
158
+ "type": "string",
159
+ "mutable": false,
160
+ "complexType": {
161
+ "original": "string",
162
+ "resolved": "string",
163
+ "references": {}
164
+ },
165
+ "required": false,
166
+ "optional": true,
167
+ "docs": {
168
+ "tags": [],
169
+ "text": "Heading text (e.g. \"Upload your documents\")."
170
+ },
171
+ "getter": false,
172
+ "setter": false,
173
+ "reflect": false,
174
+ "attribute": "heading"
175
+ },
176
+ "description": {
177
+ "type": "string",
178
+ "mutable": false,
179
+ "complexType": {
180
+ "original": "string",
181
+ "resolved": "string",
182
+ "references": {}
183
+ },
184
+ "required": false,
185
+ "optional": true,
186
+ "docs": {
187
+ "tags": [],
188
+ "text": "Guidelines under the heading (e.g. \"PDF or JPG, up to 8MB\")."
189
+ },
190
+ "getter": false,
191
+ "setter": false,
192
+ "reflect": false,
193
+ "attribute": "description"
194
+ },
195
+ "buttonLabel": {
196
+ "type": "string",
197
+ "mutable": false,
198
+ "complexType": {
199
+ "original": "string",
200
+ "resolved": "string",
201
+ "references": {}
202
+ },
203
+ "required": false,
204
+ "optional": false,
205
+ "docs": {
206
+ "tags": [],
207
+ "text": "Label of the browse button. Override for localisation."
208
+ },
209
+ "getter": false,
210
+ "setter": false,
211
+ "reflect": false,
212
+ "attribute": "button-label",
213
+ "defaultValue": "'Browse file'"
214
+ },
215
+ "accept": {
216
+ "type": "string",
217
+ "mutable": false,
218
+ "complexType": {
219
+ "original": "string",
220
+ "resolved": "string",
221
+ "references": {}
222
+ },
223
+ "required": false,
224
+ "optional": true,
225
+ "docs": {
226
+ "tags": [],
227
+ "text": "Which files are accepted, same syntax as the native `accept` attribute: comma-separated\nextensions (`.pdf`), MIME types (`image/png`) or wildcards (`image/*`). Applied to the picker\nand also to dropped files, which the browser does not filter."
228
+ },
229
+ "getter": false,
230
+ "setter": false,
231
+ "reflect": false,
232
+ "attribute": "accept"
233
+ },
234
+ "multiple": {
235
+ "type": "boolean",
236
+ "mutable": false,
237
+ "complexType": {
238
+ "original": "boolean",
239
+ "resolved": "boolean",
240
+ "references": {}
241
+ },
242
+ "required": false,
243
+ "optional": false,
244
+ "docs": {
245
+ "tags": [],
246
+ "text": "Allows selecting/dropping more than one file. Without it only the first file is taken."
247
+ },
248
+ "getter": false,
249
+ "setter": false,
250
+ "reflect": false,
251
+ "attribute": "multiple",
252
+ "defaultValue": "false"
253
+ },
254
+ "disabled": {
255
+ "type": "boolean",
256
+ "mutable": false,
257
+ "complexType": {
258
+ "original": "boolean",
259
+ "resolved": "boolean",
260
+ "references": {}
261
+ },
262
+ "required": false,
263
+ "optional": false,
264
+ "docs": {
265
+ "tags": [],
266
+ "text": "Disables the browse button and ignores drops."
267
+ },
268
+ "getter": false,
269
+ "setter": false,
270
+ "reflect": true,
271
+ "attribute": "disabled",
272
+ "defaultValue": "false"
273
+ },
274
+ "dragging": {
275
+ "type": "boolean",
276
+ "mutable": true,
277
+ "complexType": {
278
+ "original": "boolean",
279
+ "resolved": "boolean",
280
+ "references": {}
281
+ },
282
+ "required": false,
283
+ "optional": false,
284
+ "docs": {
285
+ "tags": [],
286
+ "text": "Whether a file is currently being dragged over the dropzone (the Figma \"Drop\" state). Set by\nthe component during a drag; mutable and reflected so it can also be set to show the state\nstatically."
287
+ },
288
+ "getter": false,
289
+ "setter": false,
290
+ "reflect": true,
291
+ "attribute": "dragging",
292
+ "defaultValue": "false"
293
+ }
294
+ };
295
+ }
296
+ static get events() {
297
+ return [{
298
+ "method": "bsFilesSelect",
299
+ "name": "bsFilesSelect",
300
+ "bubbles": true,
301
+ "cancelable": true,
302
+ "composed": true,
303
+ "docs": {
304
+ "tags": [],
305
+ "text": "Fires with the accepted files, after the picker closes or files are dropped."
306
+ },
307
+ "complexType": {
308
+ "original": "File[]",
309
+ "resolved": "File[]",
310
+ "references": {
311
+ "File": {
312
+ "location": "global",
313
+ "id": "global::File"
314
+ }
315
+ }
316
+ }
317
+ }, {
318
+ "method": "bsFilesReject",
319
+ "name": "bsFilesReject",
320
+ "bubbles": true,
321
+ "cancelable": true,
322
+ "composed": true,
323
+ "docs": {
324
+ "tags": [],
325
+ "text": "Fires with files that were not taken: they didn't match `accept`, or were extra files\ndropped while `multiple` is off."
326
+ },
327
+ "complexType": {
328
+ "original": "File[]",
329
+ "resolved": "File[]",
330
+ "references": {
331
+ "File": {
332
+ "location": "global",
333
+ "id": "global::File"
334
+ }
335
+ }
336
+ }
337
+ }];
338
+ }
339
+ static get elementRef() { return "el"; }
340
+ }
341
+ // Figma's "UploadSimple" icon (3272:50330), exported SVG, recolored via currentColor.
342
+ const UploadIcon = () => (h("svg", { class: "bs-file-upload__icon-svg", viewBox: "0 0 32 32", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" }, h("path", { d: "M16 18V4", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { d: "M27 18V26H5V18", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { d: "M11 9L16 4L21 9", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" })));
@@ -0,0 +1,116 @@
1
+ import { html, render } from "lit";
2
+ import { componentDescription, propDescription } from "../../stories-utils";
3
+ const directions = ['horizontal', 'vertical'];
4
+ const meta = {
5
+ title: 'Components/bs-file-upload',
6
+ parameters: { docs: { description: { component: componentDescription('bs-file-upload') } } },
7
+ render: args => html `
8
+ <bs-file-upload
9
+ direction=${args.direction}
10
+ heading=${args.heading}
11
+ description=${args.description}
12
+ button-label=${args.buttonLabel}
13
+ accept=${args.accept}
14
+ ?multiple=${args.multiple}
15
+ ?disabled=${args.disabled}
16
+ ?dragging=${args.dragging}
17
+ ></bs-file-upload>
18
+ `,
19
+ argTypes: {
20
+ direction: { control: { type: 'select' }, options: directions, description: propDescription('bs-file-upload', 'direction') },
21
+ heading: { control: 'text', description: propDescription('bs-file-upload', 'heading') },
22
+ description: { control: 'text', description: propDescription('bs-file-upload', 'description') },
23
+ buttonLabel: { control: 'text', description: propDescription('bs-file-upload', 'buttonLabel') },
24
+ accept: { control: 'text', description: propDescription('bs-file-upload', 'accept') },
25
+ multiple: { control: 'boolean', description: propDescription('bs-file-upload', 'multiple') },
26
+ disabled: { control: 'boolean', description: propDescription('bs-file-upload', 'disabled') },
27
+ dragging: { control: 'boolean', description: propDescription('bs-file-upload', 'dragging') },
28
+ },
29
+ args: {
30
+ direction: 'horizontal',
31
+ heading: 'Heading',
32
+ description: 'Upload Guidelines',
33
+ buttonLabel: 'Browse file',
34
+ accept: '',
35
+ multiple: true,
36
+ disabled: false,
37
+ dragging: false,
38
+ },
39
+ };
40
+ export default meta;
41
+ // Figma "Direction=Horizontal, States=Enabled".
42
+ export const Default = {};
43
+ // Figma "Direction=Vertical, States=Enabled".
44
+ export const Vertical = { args: { direction: 'vertical' } };
45
+ // Figma "States=Drop" -- what the zone looks like while a file is dragged over it (try it: drag a
46
+ // file from your desktop over the Default story).
47
+ export const Dragging = {
48
+ name: 'Drop state',
49
+ args: { dragging: true },
50
+ };
51
+ export const Disabled = { args: { disabled: true } };
52
+ // Every Figma variant side by side.
53
+ export const AllVariants = {
54
+ name: 'All variants',
55
+ render: () => html `
56
+ <div style="display: flex; flex-direction: column; gap: 20px;">
57
+ ${['horizontal', 'vertical'].map(direction => html `
58
+ <bs-file-upload direction=${direction} heading="Heading" description="Upload Guidelines"></bs-file-upload>
59
+ <bs-file-upload direction=${direction} dragging heading="Heading" description="Upload Guidelines"></bs-file-upload>
60
+ `)}
61
+ </div>
62
+ `,
63
+ };
64
+ // The intended pairing: the dropzone reports files, the app uploads them and shows each one with
65
+ // bs-file-upload-item. Uploads here are simulated (a timer drives the progress) -- a real app drives
66
+ // the items from its own upload.
67
+ const formatSize = (bytes) => (bytes >= 1048576 ? `${(bytes / 1048576).toFixed(1)}MB` : `${Math.max(1, Math.round(bytes / 1024))}KB`);
68
+ function simulateUpload(item, file) {
69
+ item.state = 'loading';
70
+ let progress = 0;
71
+ const timer = window.setInterval(() => {
72
+ progress += 10 + Math.round(Math.random() * 15);
73
+ if (progress >= 100) {
74
+ window.clearInterval(timer);
75
+ const failed = /\.exe$/i.test(file.name);
76
+ item.state = failed ? 'error' : 'success';
77
+ if (failed)
78
+ item.message = 'Executable files are not allowed';
79
+ return;
80
+ }
81
+ item.state = 'in-progress';
82
+ item.progress = progress;
83
+ }, 400);
84
+ item.addEventListener('bsCancel', () => window.clearInterval(timer));
85
+ }
86
+ export const WithUploadList = {
87
+ name: 'With upload list',
88
+ render: () => {
89
+ const onSelect = (ev) => {
90
+ const list = ev.currentTarget.parentElement.querySelector('.list');
91
+ ev.detail.forEach(file => {
92
+ const holder = document.createElement('div');
93
+ render(html `<bs-file-upload-item
94
+ file-name=${file.name}
95
+ file-size=${formatSize(file.size)}
96
+ @bsCancel=${() => holder.remove()}
97
+ @bsRemove=${() => holder.remove()}
98
+ ></bs-file-upload-item>`, holder);
99
+ list.appendChild(holder);
100
+ simulateUpload(holder.firstElementChild, file);
101
+ });
102
+ };
103
+ return html `
104
+ <div style="display: flex; flex-direction: column; gap: 16px; max-width: 640px;">
105
+ <bs-file-upload
106
+ direction="vertical"
107
+ multiple
108
+ heading="Upload your documents"
109
+ description="Drag files here, or browse. Uploads are simulated."
110
+ @bsFilesSelect=${onSelect}
111
+ ></bs-file-upload>
112
+ <div class="list" style="display: flex; flex-direction: column; gap: 12px;"></div>
113
+ </div>
114
+ `;
115
+ },
116
+ };
@@ -0,0 +1,115 @@
1
+ import { render, h, describe, it, expect } from "@stencil/vitest";
2
+ import { assertShadowParts, runAxeCheck } from "../../test/helpers/conventions";
3
+ const btn = (el) => el?.shadowRoot?.querySelector('button');
4
+ describe('bs-file-upload-item', () => {
5
+ it('has no axe-core violations in every state', async () => {
6
+ for (const state of ['loading', 'in-progress', 'error', 'success']) {
7
+ const { root } = await render(h("bs-file-upload-item", { state: state, fileName: "report.pdf", fileSize: "8MB", progress: 40 }));
8
+ await runAxeCheck(root);
9
+ }
10
+ });
11
+ it('exposes its documented shadow parts', async () => {
12
+ const { root } = await render(h("bs-file-upload-item", { state: "success", fileName: "a.pdf", fileSize: "1MB" }));
13
+ assertShadowParts(root.shadowRoot, ['item', 'thumbnail', 'name', 'size', 'status', 'action']);
14
+ const { root: r2 } = await render(h("bs-file-upload-item", { state: "in-progress", fileName: "a.pdf" }));
15
+ assertShadowParts(r2.shadowRoot, ['progress']);
16
+ });
17
+ it('shows the file name and size', async () => {
18
+ const { root } = await render(h("bs-file-upload-item", { fileName: "Image Name", fileSize: "8MB" }));
19
+ expect(root.shadowRoot.querySelector('[part="name"]').textContent).toBe('Image Name');
20
+ expect(root.shadowRoot.querySelector('[part="size"]').textContent).toBe('8MB');
21
+ });
22
+ it('omits the size line when there is no fileSize', async () => {
23
+ const { root } = await render(h("bs-file-upload-item", { fileName: "x" }));
24
+ expect(root.shadowRoot.querySelector('[part="size"]')).toBeNull();
25
+ });
26
+ describe('loading', () => {
27
+ it('renders an indeterminate progress bar labelled "Loading..."', async () => {
28
+ const { root } = await render(h("bs-file-upload-item", { state: "loading", fileName: "a" }));
29
+ const bar = root.shadowRoot.querySelector('bs-progress-linear');
30
+ expect(bar.type).toBe('indeterminate');
31
+ expect(bar.size).toBe('small');
32
+ expect(bar.textContent).toBe('Loading...');
33
+ });
34
+ });
35
+ describe('in-progress', () => {
36
+ it('renders a determinate bar with the percentage label', async () => {
37
+ const { root } = await render(h("bs-file-upload-item", { state: "in-progress", fileName: "a", progress: 40 }));
38
+ const bar = root.shadowRoot.querySelector('bs-progress-linear');
39
+ expect(bar.type).toBe('determinate');
40
+ expect(bar.value).toBe(40);
41
+ expect(bar.textContent).toBe('40% uploaded');
42
+ });
43
+ it('clamps and rounds progress', async () => {
44
+ const { root } = await render(h("bs-file-upload-item", { state: "in-progress", fileName: "a", progress: 140.4 }));
45
+ expect(root.shadowRoot.querySelector('bs-progress-linear').textContent).toBe('100% uploaded');
46
+ });
47
+ it('names the progress bar after the file', async () => {
48
+ const { root } = await render(h("bs-file-upload-item", { state: "in-progress", fileName: "report.pdf", progress: 10 }));
49
+ const bar = root.shadowRoot.querySelector('bs-progress-linear');
50
+ expect(bar.shadowRoot.querySelector('[role="progressbar"]')).toEqualAttribute('aria-label', 'report.pdf');
51
+ });
52
+ });
53
+ describe('error and success', () => {
54
+ it('error: red status with the default or a custom message, announced via role="status"', async () => {
55
+ const { root } = await render(h("bs-file-upload-item", { state: "error", fileName: "a" }));
56
+ const status = root.shadowRoot.querySelector('[part="status"]');
57
+ expect(status).toEqualAttribute('role', 'status');
58
+ expect(status.textContent).toContain('Upload failed');
59
+ expect(status).toHaveClass('bs-file-upload-item__status--error');
60
+ const { root: r2 } = await render(h("bs-file-upload-item", { state: "error", fileName: "a", message: "File too large" }));
61
+ expect(r2.shadowRoot.querySelector('[part="status"]').textContent).toContain('File too large');
62
+ });
63
+ it('success: green status with the default message', async () => {
64
+ const { root } = await render(h("bs-file-upload-item", { state: "success", fileName: "a" }));
65
+ const status = root.shadowRoot.querySelector('[part="status"]');
66
+ expect(status.textContent).toContain('File uploaded successfully');
67
+ expect(status).toHaveClass('bs-file-upload-item__status--success');
68
+ });
69
+ it('the final states render no progress bar', async () => {
70
+ const { root } = await render(h("bs-file-upload-item", { state: "success", fileName: "a" }));
71
+ expect(root.shadowRoot.querySelector('bs-progress-linear')).toBeNull();
72
+ });
73
+ });
74
+ describe('action button', () => {
75
+ it('loading / in-progress / error: a cancel button that emits bsCancel', async () => {
76
+ for (const state of ['loading', 'in-progress', 'error']) {
77
+ const { root, spyOnEvent } = await render(h("bs-file-upload-item", { state: state, fileName: "a" }));
78
+ const cancel = spyOnEvent('bsCancel');
79
+ const remove = spyOnEvent('bsRemove');
80
+ const action = root.shadowRoot.querySelector('[part="action"]');
81
+ expect(btn(action).getAttribute('aria-label')).toBe('Cancel upload');
82
+ btn(action).click();
83
+ expect(cancel).toHaveReceivedEventTimes(1);
84
+ expect(remove).not.toHaveReceivedEvent();
85
+ }
86
+ });
87
+ it('success: a remove button that emits bsRemove', async () => {
88
+ const { root, spyOnEvent } = await render(h("bs-file-upload-item", { state: "success", fileName: "a" }));
89
+ const cancel = spyOnEvent('bsCancel');
90
+ const remove = spyOnEvent('bsRemove');
91
+ const action = root.shadowRoot.querySelector('[part="action"]');
92
+ expect(btn(action).getAttribute('aria-label')).toBe('Remove file');
93
+ btn(action).click();
94
+ expect(remove).toHaveReceivedEventTimes(1);
95
+ expect(cancel).not.toHaveReceivedEvent();
96
+ });
97
+ it('cancelLabel / removeLabel override the accessible names', async () => {
98
+ const { root } = await render(h("bs-file-upload-item", { state: "loading", fileName: "a", cancelLabel: "Annuller" }));
99
+ expect(btn(root.shadowRoot.querySelector('[part="action"]')).getAttribute('aria-label')).toBe('Annuller');
100
+ const { root: r2 } = await render(h("bs-file-upload-item", { state: "success", fileName: "a", removeLabel: "Fjern" }));
101
+ expect(btn(r2.shadowRoot.querySelector('[part="action"]')).getAttribute('aria-label')).toBe('Fjern');
102
+ });
103
+ });
104
+ describe('thumbnail', () => {
105
+ it('shows the placeholder icon without thumbnailSrc, and the image with it', async () => {
106
+ const { root } = await render(h("bs-file-upload-item", { fileName: "a" }));
107
+ expect(root.shadowRoot.querySelector('.bs-file-upload-item__placeholder')).not.toBeNull();
108
+ expect(root.shadowRoot.querySelector('img')).toBeNull();
109
+ const { root: r2 } = await render(h("bs-file-upload-item", { fileName: "a", thumbnailSrc: "data:image/gif;base64,R0lGODlhAQABAAAAACw=", thumbnailAlt: "Preview" }));
110
+ const img = r2.shadowRoot.querySelector('img');
111
+ expect(img).toEqualAttribute('alt', 'Preview');
112
+ expect(r2.shadowRoot.querySelector('.bs-file-upload-item__placeholder')).toBeNull();
113
+ });
114
+ });
115
+ });