@supersoniks/concorde 5.0.1 → 5.0.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 (258) hide show
  1. package/build-infos.json +1 -1
  2. package/concorde-core.bundle.js +519 -586
  3. package/concorde-core.es.js +3783 -3689
  4. package/dist/concorde-core.bundle.js +519 -586
  5. package/dist/concorde-core.es.js +3783 -3689
  6. package/docs/altcha-widget.js +2662 -0
  7. package/docs/assets/index--GpD7R7a.css +1 -0
  8. package/docs/assets/index-BnK6OS7w.js +12115 -0
  9. package/docs/crawl/core/components/functional/date/date.html +190 -0
  10. package/docs/crawl/core/components/functional/fetch/fetch.html +109 -0
  11. package/docs/crawl/core/components/functional/if/if.html +27 -0
  12. package/docs/crawl/core/components/functional/jsonata/jsonata.html +79 -0
  13. package/docs/crawl/core/components/functional/list/list.html +70 -0
  14. package/docs/crawl/core/components/functional/mix/mix.html +45 -0
  15. package/docs/crawl/core/components/functional/queue/queue.html +96 -0
  16. package/docs/crawl/core/components/functional/router/router.html +106 -0
  17. package/docs/crawl/core/components/functional/sdui/sdui.html +269 -0
  18. package/docs/crawl/core/components/functional/states/states.html +66 -0
  19. package/docs/crawl/core/components/functional/submit/submit.html +160 -0
  20. package/docs/crawl/core/components/functional/subscriber/subscriber.html +67 -0
  21. package/docs/crawl/core/components/functional/value/value.html +51 -0
  22. package/docs/crawl/core/components/ui/alert/alert.html +140 -0
  23. package/docs/crawl/core/components/ui/alert-messages/alert-messages.html +23 -0
  24. package/docs/crawl/core/components/ui/badge/badge.html +148 -0
  25. package/docs/crawl/core/components/ui/button/button.html +150 -0
  26. package/docs/crawl/core/components/ui/captcha/captcha.html +30 -0
  27. package/docs/crawl/core/components/ui/card/card.html +143 -0
  28. package/docs/crawl/core/components/ui/divider/divider.html +58 -0
  29. package/docs/crawl/core/components/ui/form/checkbox/checkbox.html +73 -0
  30. package/docs/crawl/core/components/ui/form/fieldset/fieldset.html +135 -0
  31. package/docs/crawl/core/components/ui/form/form-actions/form-actions.html +79 -0
  32. package/docs/crawl/core/components/ui/form/form-layout/form-layout.html +62 -0
  33. package/docs/crawl/core/components/ui/form/input/input.html +109 -0
  34. package/docs/crawl/core/components/ui/form/input-autocomplete/input-autocomplete.html +38 -0
  35. package/docs/crawl/core/components/ui/form/radio/radio.html +56 -0
  36. package/docs/crawl/core/components/ui/form/select/select.html +73 -0
  37. package/docs/crawl/core/components/ui/form/switch/switch.html +56 -0
  38. package/docs/crawl/core/components/ui/form/textarea/textarea.html +59 -0
  39. package/docs/crawl/core/components/ui/group/group.html +82 -0
  40. package/docs/crawl/core/components/ui/icon/icon.html +133 -0
  41. package/docs/crawl/core/components/ui/image/image.html +186 -0
  42. package/docs/crawl/core/components/ui/link/link.html +42 -0
  43. package/docs/crawl/core/components/ui/loader/loader.html +50 -0
  44. package/docs/crawl/core/components/ui/menu/menu.html +322 -0
  45. package/docs/crawl/core/components/ui/modal/modal.html +117 -0
  46. package/docs/crawl/core/components/ui/pop/pop.html +178 -0
  47. package/docs/crawl/core/components/ui/progress/progress.html +58 -0
  48. package/docs/crawl/core/components/ui/table/table.html +413 -0
  49. package/docs/crawl/core/components/ui/toast/toast.html +122 -0
  50. package/docs/crawl/core/components/ui/tooltip/tooltip.html +79 -0
  51. package/docs/crawl/docs/_core-concept/dataFlow.html +177 -0
  52. package/docs/crawl/docs/_core-concept/overview.html +66 -0
  53. package/docs/crawl/docs/_core-concept/subscriber.html +75 -0
  54. package/docs/crawl/docs/_decorators/ancestor-attribute.html +116 -0
  55. package/docs/crawl/docs/_decorators/auto-subscribe.html +202 -0
  56. package/docs/crawl/docs/_decorators/bind.html +141 -0
  57. package/docs/crawl/docs/_decorators/get.html +149 -0
  58. package/docs/crawl/docs/_decorators/handle.html +165 -0
  59. package/docs/crawl/docs/_decorators/on-assign.html +355 -0
  60. package/docs/crawl/docs/_decorators/patch.html +78 -0
  61. package/docs/crawl/docs/_decorators/post.html +139 -0
  62. package/docs/crawl/docs/_decorators/publish.html +61 -0
  63. package/docs/crawl/docs/_decorators/put.html +78 -0
  64. package/docs/crawl/docs/_decorators/subscribe.html +109 -0
  65. package/docs/crawl/docs/_decorators/wait-for-ancestors.html +137 -0
  66. package/docs/crawl/docs/_directives/sub.html +93 -0
  67. package/docs/crawl/docs/_getting-started/ai-agents.html +173 -0
  68. package/docs/crawl/docs/_getting-started/concorde-manual-install.html +100 -0
  69. package/docs/crawl/docs/_getting-started/concorde-outside.html +58 -0
  70. package/docs/crawl/docs/_getting-started/create-a-component.html +111 -0
  71. package/docs/crawl/docs/_getting-started/my-first-component.html +189 -0
  72. package/docs/crawl/docs/_getting-started/my-first-subscriber.html +110 -0
  73. package/docs/crawl/docs/_getting-started/pubsub.html +62 -0
  74. package/docs/crawl/docs/_getting-started/start.html +76 -0
  75. package/docs/crawl/docs/_getting-started/theming.html +85 -0
  76. package/docs/crawl/docs/_misc/api-configuration.html +149 -0
  77. package/docs/crawl/docs/_misc/dataProviderKey.html +135 -0
  78. package/docs/crawl/docs/_misc/docs-mock-api.html +158 -0
  79. package/docs/crawl/docs/_misc/dynamic-path.html +180 -0
  80. package/docs/crawl/docs/_misc/endpoint.html +50 -0
  81. package/docs/crawl/docs/_misc/formelement-subscriber-removal.html +138 -0
  82. package/docs/crawl/docs/_misc/html-integration.html +48 -0
  83. package/docs/crawl/hubs/decorators.html +547 -0
  84. package/docs/crawl/hubs/form-components.html +359 -0
  85. package/docs/crawl/hubs/remote-data.html +452 -0
  86. package/docs/crawl/index.html +101 -0
  87. package/docs/css/docs.css +0 -0
  88. package/docs/docs-mock-api-sw.js +724 -0
  89. package/docs/docs-mock-api-sw.js.map +7 -0
  90. package/docs/fonts/ClashGrotesk-Bold.eot +0 -0
  91. package/docs/fonts/ClashGrotesk-Bold.ttf +0 -0
  92. package/docs/fonts/ClashGrotesk-Bold.woff +0 -0
  93. package/docs/fonts/ClashGrotesk-Bold.woff2 +0 -0
  94. package/docs/fonts/ClashGrotesk-Extralight.eot +0 -0
  95. package/docs/fonts/ClashGrotesk-Extralight.ttf +0 -0
  96. package/docs/fonts/ClashGrotesk-Extralight.woff +0 -0
  97. package/docs/fonts/ClashGrotesk-Extralight.woff2 +0 -0
  98. package/docs/fonts/ClashGrotesk-Light.eot +0 -0
  99. package/docs/fonts/ClashGrotesk-Light.ttf +0 -0
  100. package/docs/fonts/ClashGrotesk-Light.woff +0 -0
  101. package/docs/fonts/ClashGrotesk-Light.woff2 +0 -0
  102. package/docs/fonts/ClashGrotesk-Medium.eot +0 -0
  103. package/docs/fonts/ClashGrotesk-Medium.ttf +0 -0
  104. package/docs/fonts/ClashGrotesk-Medium.woff +0 -0
  105. package/docs/fonts/ClashGrotesk-Medium.woff2 +0 -0
  106. package/docs/fonts/ClashGrotesk-Regular.eot +0 -0
  107. package/docs/fonts/ClashGrotesk-Regular.ttf +0 -0
  108. package/docs/fonts/ClashGrotesk-Regular.woff +0 -0
  109. package/docs/fonts/ClashGrotesk-Regular.woff2 +0 -0
  110. package/docs/fonts/ClashGrotesk-Semibold.eot +0 -0
  111. package/docs/fonts/ClashGrotesk-Semibold.ttf +0 -0
  112. package/docs/fonts/ClashGrotesk-Semibold.woff +0 -0
  113. package/docs/fonts/ClashGrotesk-Semibold.woff2 +0 -0
  114. package/docs/fonts/ClashGrotesk-Variable.eot +0 -0
  115. package/docs/fonts/ClashGrotesk-Variable.ttf +0 -0
  116. package/docs/fonts/ClashGrotesk-Variable.woff +0 -0
  117. package/docs/fonts/ClashGrotesk-Variable.woff2 +0 -0
  118. package/docs/img/concorde-icon.svg +5 -0
  119. package/docs/img/concorde-logo.svg +1 -0
  120. package/docs/img/concorde.png +0 -0
  121. package/docs/img/concorde_def.png +0 -0
  122. package/docs/img/concorde_seuil.png.webp +0 -0
  123. package/docs/img/concorde_seuil_invert.png +0 -0
  124. package/docs/img/paul_metrand.jpg +0 -0
  125. package/docs/img/paul_metrand_xs.jpg +0 -0
  126. package/docs/index.html +93 -0
  127. package/docs/llms-full.txt +5945 -0
  128. package/docs/llms.txt +104 -0
  129. package/docs/robots.txt +16 -0
  130. package/docs/sitemap.xml +237 -0
  131. package/docs/src/core/components/functional/date/date.md +290 -0
  132. package/docs/src/core/components/functional/fetch/fetch.md +125 -0
  133. package/docs/src/core/components/functional/if/if.md +9 -0
  134. package/docs/src/core/components/functional/jsonata/jsonata.md +56 -0
  135. package/docs/src/core/components/functional/list/list.md +65 -0
  136. package/docs/src/core/components/functional/mix/mix.md +41 -0
  137. package/docs/src/core/components/functional/queue/queue.md +72 -0
  138. package/docs/src/core/components/functional/router/router.md +94 -0
  139. package/docs/src/core/components/functional/sdui/default-library.json +108 -0
  140. package/docs/src/core/components/functional/sdui/example.json +99 -0
  141. package/docs/src/core/components/functional/sdui/sdui.md +356 -0
  142. package/docs/src/core/components/functional/states/states.md +87 -0
  143. package/docs/src/core/components/functional/submit/submit.md +114 -0
  144. package/docs/src/core/components/functional/subscriber/subscriber.md +91 -0
  145. package/docs/src/core/components/functional/value/value.md +35 -0
  146. package/docs/src/core/components/ui/alert/alert.md +177 -0
  147. package/docs/src/core/components/ui/alert-messages/alert-messages.md +0 -0
  148. package/docs/src/core/components/ui/badge/badge.md +175 -0
  149. package/docs/src/core/components/ui/button/button.md +183 -0
  150. package/docs/src/core/components/ui/captcha/captcha.md +12 -0
  151. package/docs/src/core/components/ui/card/card.md +152 -0
  152. package/docs/src/core/components/ui/divider/divider.md +35 -0
  153. package/docs/src/core/components/ui/form/checkbox/checkbox.md +77 -0
  154. package/docs/src/core/components/ui/form/fieldset/fieldset.md +129 -0
  155. package/docs/src/core/components/ui/form/form-actions/form-actions.md +77 -0
  156. package/docs/src/core/components/ui/form/form-layout/form-layout.md +44 -0
  157. package/docs/src/core/components/ui/form/input/input.md +142 -0
  158. package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +29 -0
  159. package/docs/src/core/components/ui/form/radio/radio.md +57 -0
  160. package/docs/src/core/components/ui/form/select/select.md +71 -0
  161. package/docs/src/core/components/ui/form/switch/switch.md +57 -0
  162. package/docs/src/core/components/ui/form/textarea/textarea.md +65 -0
  163. package/docs/src/core/components/ui/group/group.md +75 -0
  164. package/docs/src/core/components/ui/icon/icon.md +125 -0
  165. package/docs/src/core/components/ui/icon/icons.json +1 -0
  166. package/docs/src/core/components/ui/image/image.md +201 -0
  167. package/docs/src/core/components/ui/link/link.md +43 -0
  168. package/docs/src/core/components/ui/loader/loader.md +55 -0
  169. package/docs/src/core/components/ui/menu/menu.md +329 -0
  170. package/docs/src/core/components/ui/modal/modal.md +119 -0
  171. package/docs/src/core/components/ui/pop/pop.md +200 -0
  172. package/docs/src/core/components/ui/progress/progress.md +63 -0
  173. package/docs/src/core/components/ui/table/table.md +455 -0
  174. package/docs/src/core/components/ui/toast/toast.md +166 -0
  175. package/docs/src/core/components/ui/tooltip/tooltip.md +82 -0
  176. package/docs/src/docs/_core-concept/dataFlow.md +88 -0
  177. package/docs/src/docs/_core-concept/overview.md +57 -0
  178. package/docs/src/docs/_core-concept/subscriber.md +75 -0
  179. package/docs/src/docs/_decorators/ancestor-attribute.md +127 -0
  180. package/docs/src/docs/_decorators/auto-subscribe.md +202 -0
  181. package/docs/src/docs/_decorators/bind.md +167 -0
  182. package/docs/src/docs/_decorators/get.md +162 -0
  183. package/docs/src/docs/_decorators/handle.md +175 -0
  184. package/docs/src/docs/_decorators/on-assign.md +390 -0
  185. package/docs/src/docs/_decorators/patch.md +70 -0
  186. package/docs/src/docs/_decorators/post.md +146 -0
  187. package/docs/src/docs/_decorators/publish.md +55 -0
  188. package/docs/src/docs/_decorators/put.md +70 -0
  189. package/docs/src/docs/_decorators/subscribe.md +100 -0
  190. package/docs/src/docs/_decorators/wait-for-ancestors.md +163 -0
  191. package/docs/src/docs/_directives/sub.md +91 -0
  192. package/docs/src/docs/_getting-started/ai-agents.md +76 -0
  193. package/docs/src/docs/_getting-started/concorde-manual-install.md +133 -0
  194. package/docs/src/docs/_getting-started/concorde-outside.md +33 -0
  195. package/docs/src/docs/_getting-started/create-a-component.md +139 -0
  196. package/docs/src/docs/_getting-started/my-first-component.md +236 -0
  197. package/docs/src/docs/_getting-started/my-first-subscriber.md +120 -0
  198. package/docs/src/docs/_getting-started/pubsub.md +37 -0
  199. package/docs/src/docs/_getting-started/start.md +47 -0
  200. package/docs/src/docs/_getting-started/theming.md +91 -0
  201. package/docs/src/docs/_misc/api-configuration.md +81 -0
  202. package/docs/src/docs/_misc/dataProviderKey.md +168 -0
  203. package/docs/src/docs/_misc/docs-mock-api.md +63 -0
  204. package/docs/src/docs/_misc/dynamic-path.md +71 -0
  205. package/docs/src/docs/_misc/endpoint.md +45 -0
  206. package/docs/src/docs/_misc/formelement-subscriber-removal.md +115 -0
  207. package/docs/src/docs/_misc/html-integration.md +15 -0
  208. package/docs/src/docs/search/docs-search.json +10137 -0
  209. package/docs/src/tag-list.json +1 -0
  210. package/docs/src/tsconfig-model.json +23 -0
  211. package/docs/src/tsconfig.json +1224 -0
  212. package/docs/svg/regular/plane.svg +1 -0
  213. package/docs/svg/solid/plane.svg +1 -0
  214. package/index.html +2 -2
  215. package/package.json +14 -1
  216. package/src/core/components/ui/_css/rounded.ts +36 -0
  217. package/src/core/components/ui/_css/type.ts +49 -0
  218. package/src/core/components/ui/_i18n/ui-aria-labels.ts +104 -0
  219. package/src/core/components/ui/alert/alert.md +128 -72
  220. package/src/core/components/ui/alert/alert.spec.ts +145 -0
  221. package/src/core/components/ui/alert/alert.ts +86 -91
  222. package/src/core/components/ui/alert-messages/alert-messages.ts +1 -1
  223. package/src/core/components/ui/badge/badge.md +126 -78
  224. package/src/core/components/ui/badge/badge.ts +29 -81
  225. package/src/core/components/ui/button/button.md +1 -0
  226. package/src/core/components/ui/button/button.ts +11 -8
  227. package/src/core/components/ui/card/card-header.ts +7 -2
  228. package/src/core/components/ui/card/card.md +120 -65
  229. package/src/core/components/ui/divider/divider.ts +4 -9
  230. package/src/core/components/ui/form/fieldset/legend-description.ts +1 -1
  231. package/src/core/components/ui/form/fieldset/legend.ts +1 -1
  232. package/src/core/components/ui/form/input/input.md +1 -1
  233. package/src/core/components/ui/form/input/input.ts +19 -15
  234. package/src/core/components/ui/form/textarea/textarea.md +1 -1
  235. package/src/core/components/ui/image/image.md +147 -53
  236. package/src/core/components/ui/image/image.ts +10 -44
  237. package/src/core/components/ui/link/link.md +2 -2
  238. package/src/core/components/ui/link/link.ts +42 -37
  239. package/src/core/components/ui/modal/modal-close.ts +8 -12
  240. package/src/core/components/ui/modal/modal.ts +18 -18
  241. package/src/core/components/ui/pop/pop.md +7 -5
  242. package/src/core/components/ui/pop/pop.ts +78 -38
  243. package/src/core/components/ui/table/table.ts +2 -8
  244. package/src/core/components/ui/toast/dismissed.ts +14 -0
  245. package/src/core/components/ui/toast/toast-item.ts +139 -91
  246. package/src/core/components/ui/toast/toast.ts +4 -7
  247. package/src/core/components/ui/tooltip/tooltip.ts +6 -8
  248. package/src/core/utils/LocationHandler.ts +12 -5
  249. package/src/core/utils/dismissed.ts +22 -0
  250. package/src/core/utils/positioning.spec.ts +28 -0
  251. package/src/core/utils/positioning.ts +19 -0
  252. package/src/docs/_getting-started/concorde-outside.md +3 -3
  253. package/src/docs/code.ts +3 -1
  254. package/src/docs/search/docs-search.json +541 -1
  255. package/src/docs/tailwind/css/tailwind.css +6 -0
  256. package/src/tsconfig.json +422 -383
  257. package/src/tsconfig.tsbuildinfo +1 -1
  258. package/tailwind.config.js +50 -3
@@ -30,29 +30,31 @@
30
30
  </template>
31
31
  </sonic-code>
32
32
 
33
- Default placement is `bottom`. Content is aligned on the start of the trigger, add `center` to center it. If there is not enough room, content flips to the opposite side and stays inside the viewport.
33
+ Default placement is `bottom`. Placement is written `side-align`, like `sonic-tooltip`: `side` is `top`, `right`, `bottom` or `left`, `align` is `start`, `center` or `end`. Without `align`, content is aligned on the start of the trigger. If there is not enough room, content flips to the opposite side and stays inside the viewport.
34
+
35
+ The former space syntax (`placement="bottom center"`) still works but is deprecated.
34
36
 
35
37
  <sonic-code>
36
38
  <template>
37
- <sonic-pop class="inline-block" placement="bottom center">
39
+ <sonic-pop class="inline-block" placement="bottom-center">
38
40
  <sonic-button>Bottom center</sonic-button>
39
41
  <sonic-menu slot="content">
40
42
  <sonic-menu-item><sonic-icon library="iconoir" name="bell-notification" slot="prefix"></sonic-icon>Notifications</sonic-menu-item>
41
43
  </sonic-menu>
42
44
  </sonic-pop>
43
- <sonic-pop class="inline-block" placement="top center">
45
+ <sonic-pop class="inline-block" placement="top-center">
44
46
  <sonic-button>Top center</sonic-button>
45
47
  <sonic-menu slot="content">
46
48
  <sonic-menu-item><sonic-icon library="iconoir" name="bell-notification" slot="prefix"></sonic-icon>Notifications</sonic-menu-item>
47
49
  </sonic-menu>
48
50
  </sonic-pop>
49
- <sonic-pop class="inline-block" placement="left center">
51
+ <sonic-pop class="inline-block" placement="left-center">
50
52
  <sonic-button>Left center</sonic-button>
51
53
  <sonic-menu slot="content">
52
54
  <sonic-menu-item><sonic-icon library="iconoir" name="bell-notification" slot="prefix"></sonic-icon>Notifications</sonic-menu-item>
53
55
  </sonic-menu>
54
56
  </sonic-pop>
55
- <sonic-pop class="inline-block" placement="right center">
57
+ <sonic-pop class="inline-block" placement="right-center">
56
58
  <sonic-button>Right center</sonic-button>
57
59
  <sonic-menu slot="content">
58
60
  <sonic-menu-item><sonic-icon library="iconoir" name="bell-notification" slot="prefix"></sonic-icon>Notifications</sonic-menu-item>
@@ -7,20 +7,31 @@ import HTML, {
7
7
  } from "@supersoniks/concorde/core/utils/HTML";
8
8
  import {
9
9
  computeFloatingPosition,
10
+ FloatingPlacement,
10
11
  FloatingSide,
11
12
  getFloatingBounds,
13
+ parsePlacement,
14
+ startFrameLoop,
12
15
  supportsPopover,
13
16
  } from "@supersoniks/concorde/core/utils/positioning";
14
17
  import { Shadow, shadowable } from "../_css/shadow";
15
- type PopPlacement =
16
- | "bottom"
17
- | "top"
18
- | "right"
19
- | "left"
20
- | "bottom center"
21
- | "top center"
22
- | "right center"
23
- | "left center";
18
+
19
+ /** @deprecated écriture avec espace, utiliser `"bottom-center"`. */
20
+ type LegacyPopPlacement = `${FloatingSide} center`;
21
+ type PopPlacement = FloatingPlacement | LegacyPopPlacement;
22
+
23
+ let legacyPlacementWarned = false;
24
+
25
+ function normalizePopPlacement(placement: PopPlacement): FloatingPlacement {
26
+ if (!placement.includes(" ")) return placement as FloatingPlacement;
27
+ if (!legacyPlacementWarned) {
28
+ legacyPlacementWarned = true;
29
+ console.warn(
30
+ `[concorde] sonic-pop placement="${placement}" est déprécié, utiliser placement="${placement.replace(" ", "-")}".`,
31
+ );
32
+ }
33
+ return placement.replace(" ", "-") as FloatingPlacement;
34
+ }
24
35
 
25
36
  const tagName = "sonic-pop";
26
37
 
@@ -108,7 +119,6 @@ export class Pop extends LitElement {
108
119
  @property({ type: String, reflect: true }) shadow: Shadow = "lg";
109
120
 
110
121
  @property({ type: String }) placement: PopPlacement = "bottom";
111
- positioningRuns = false;
112
122
  lastContentX = 0;
113
123
  lastContentY = 0;
114
124
  resizeObserver: ResizeObserver = new ResizeObserver(() =>
@@ -116,12 +126,9 @@ export class Pop extends LitElement {
116
126
  );
117
127
 
118
128
  @state() private triggerElement: HTMLElement | null = null;
119
-
120
- runPositioningLoop() {
121
- if (!this.positioningRuns) return;
122
- this.computePosition(this.placement);
123
- window.requestAnimationFrame(() => this.runPositioningLoop());
124
- }
129
+ private stopPositioning?: () => void;
130
+ private hideDisplayTimeout?: ReturnType<typeof setTimeout>;
131
+ private pendingShow = false;
125
132
 
126
133
  toggle(e: Event) {
127
134
  if (this.open && this.noToggle) return;
@@ -134,12 +141,22 @@ export class Pop extends LitElement {
134
141
  this.triggerElement = e.target as HTMLElement;
135
142
  }
136
143
 
137
- this.open = !this.open;
138
- this.open ? this.show() : this.hide();
144
+ if (this.open) this.hide();
145
+ else this.show();
139
146
  }
140
147
 
141
148
  show() {
149
+ if (!this.popContent) {
150
+ this.pendingShow = true;
151
+ this.updateComplete.then(() => {
152
+ if (this.pendingShow) this.show();
153
+ });
154
+ return;
155
+ }
156
+ this.pendingShow = false;
157
+ if (this.open) return;
142
158
  if (!Pop.supportsPopover) this.setMaxZindex();
159
+ clearTimeout(this.hideDisplayTimeout);
143
160
  this.popContent?.style?.removeProperty("display");
144
161
  if (Pop.supportsPopover && !this.popContent.matches(":popover-open")) {
145
162
  this.popContent.showPopover();
@@ -148,41 +165,50 @@ export class Pop extends LitElement {
148
165
  // Pop.pops suit l'ordre d'ouverture : Escape ferme le dernier ouvert.
149
166
  if (Pop.pops.delete(this)) Pop.pops.add(this);
150
167
  this.popContent.setAttribute("tabindex", "0");
151
- if (this.popBtn && this.popContent && !this.positioningRuns) {
152
- this.positioningRuns = true;
168
+ if (this.popBtn && this.popContent && !this.stopPositioning) {
153
169
  this.lastContentX = 0;
154
170
  this.lastContentY = 0;
155
- this.runPositioningLoop();
171
+ this.stopPositioning = startFrameLoop(() =>
172
+ this.computePosition(this.placement),
173
+ );
156
174
  }
157
175
  this.dispatchEvent(new CustomEvent("show"));
158
176
  }
159
177
 
160
178
  hide() {
179
+ this.pendingShow = false;
180
+ if (!this.popContent) {
181
+ this.dispatchEvent(new CustomEvent("hide"));
182
+ return;
183
+ }
161
184
  if (!Pop.supportsPopover) this.resetZindexes();
162
185
  if (Pop.supportsPopover && this.popContent?.matches(":popover-open")) {
163
186
  this.popContent.hidePopover();
164
187
  }
165
188
  this.open = false;
166
189
  this.popContent.setAttribute("tabindex", "-1");
167
- this.positioningRuns = false;
190
+ this.stopPositionLoop();
191
+ if (!Pop.supportsPopover) this.hideContentAfterTransition();
168
192
  if (this.triggerElement) {
169
193
  this.triggerElement.focus();
170
194
  this.triggerElement = null;
171
195
  }
172
- Object.assign(this.popContent.style, {
173
- left: `${this.lastContentX}px`,
174
- top: `${this.lastContentY}px`,
175
- });
176
196
  this.dispatchEvent(new CustomEvent("hide"));
177
197
  }
178
198
 
179
- protected updated(changedProperties: PropertyValues): void {
180
- super.updated(changedProperties);
181
- // Le template remet style="display:none" à chaque render —
182
- // on le retire après coup quand le pop est ouvert.
183
- if (this.open) {
184
- this.popContent?.style?.removeProperty("display");
185
- }
199
+ private stopPositionLoop() {
200
+ this.stopPositioning?.();
201
+ this.stopPositioning = undefined;
202
+ }
203
+
204
+ /** Sans display:none, le contenu masqué par opacité resterait atteignable au clavier. */
205
+ private hideContentAfterTransition() {
206
+ clearTimeout(this.hideDisplayTimeout);
207
+ const duration =
208
+ parseFloat(getComputedStyle(this.popContent).transitionDuration) * 1000;
209
+ this.hideDisplayTimeout = setTimeout(() => {
210
+ if (!this.open) this.popContent.style.display = "none";
211
+ }, duration || 0);
186
212
  }
187
213
 
188
214
  /**
@@ -226,6 +252,7 @@ export class Pop extends LitElement {
226
252
  const target = path[0] as HTMLElement;
227
253
 
228
254
  Pop.pops.forEach((pop: Pop) => {
255
+ if (!pop.open) return;
229
256
  const popContainsTarget = path.includes(pop);
230
257
  const popContentContainsTarget = path.includes(
231
258
  pop.querySelector('[slot="content"]') as EventTarget,
@@ -247,7 +274,8 @@ export class Pop extends LitElement {
247
274
  if (Pop.pops.size == 0) {
248
275
  document.addEventListener("pointerdown", this._handleClosePop);
249
276
  document.addEventListener("click", this._handleClosePop);
250
- document.addEventListener("keydown", Pop._handleKeyDown);
277
+ // Capture : passer avant les écouteurs Escape des ancêtres (ex. sonic-modal).
278
+ document.addEventListener("keydown", Pop._handleKeyDown, true);
251
279
  }
252
280
  Pop.pops.add(this);
253
281
  }
@@ -264,24 +292,36 @@ export class Pop extends LitElement {
264
292
  if (this.popContent) {
265
293
  this.resizeObserver.unobserve(this.popContent);
266
294
  }
295
+ this.stopPositionLoop();
296
+ clearTimeout(this.hideDisplayTimeout);
297
+ if (this.open) {
298
+ if (!Pop.supportsPopover) this.resetZindexes();
299
+ this.open = false;
300
+ this.triggerElement = null;
301
+ this.popContent?.style.setProperty("display", "none");
302
+ }
267
303
  super.disconnectedCallback();
268
304
  Pop.pops.delete(this);
269
305
  if (Pop.pops.size == 0) {
270
306
  document.removeEventListener("pointerdown", this._handleClosePop);
271
307
  document.removeEventListener("click", this._handleClosePop);
272
- document.removeEventListener("keydown", Pop._handleKeyDown);
308
+ document.removeEventListener("keydown", Pop._handleKeyDown, true);
273
309
  }
274
310
  }
275
311
 
276
312
  computePosition(placement: PopPlacement) {
277
- const [side, secondary] = placement.split(" ");
313
+ const { side, align } = parsePlacement(
314
+ normalizePopPlacement(placement),
315
+ "bottom",
316
+ "start",
317
+ );
278
318
  const contentRect = this.popContent.getBoundingClientRect();
279
319
 
280
320
  const { x, y } = computeFloatingPosition({
281
321
  anchor: this.getBoundingClientRect(),
282
322
  floating: contentRect,
283
- side: side as FloatingSide,
284
- align: secondary == "center" ? "center" : "start",
323
+ side,
324
+ align,
285
325
  bounds: getFloatingBounds(this.popContent, 5),
286
326
  });
287
327
 
@@ -11,6 +11,7 @@ import "@supersoniks/concorde/core/components/ui/table/table-tbody";
11
11
  import "@supersoniks/concorde/core/components/ui/table/table-tfoot";
12
12
  import "@supersoniks/concorde/core/components/ui/table/table-caption";
13
13
  import { fontSize } from "@supersoniks/concorde/core/components/ui/_css/size";
14
+ import { Size } from "@supersoniks/concorde/core/components/ui/_css/size";
14
15
 
15
16
  const tagName = "sonic-table";
16
17
  @customElement(tagName)
@@ -66,14 +67,7 @@ export class Table extends LitElement {
66
67
  `,
67
68
  ];
68
69
 
69
- @property({ type: String, reflect: true }) size?:
70
- | "2xs"
71
- | "xs"
72
- | "sm"
73
- | "md"
74
- | "lg"
75
- | "xl"
76
- | "2xl";
70
+ @property({ type: String, reflect: true }) size?: Size;
77
71
  @property({ type: Boolean, reflect: true }) bordered?: boolean;
78
72
  @property({ type: Boolean, reflect: true }) rounded?: boolean;
79
73
  @property({ type: Boolean, reflect: true }) noCustomScroll?: boolean;
@@ -0,0 +1,14 @@
1
+ import {
2
+ isDismissed,
3
+ markDismissed,
4
+ } from "@supersoniks/concorde/core/utils/dismissed";
5
+
6
+ const storageKey = "sonic-toast-dismissed";
7
+
8
+ export function isToastDismissed(id: string | number): boolean {
9
+ return isDismissed(storageKey, id);
10
+ }
11
+
12
+ export function markToastDismissed(id: string | number): void {
13
+ markDismissed(storageKey, id);
14
+ }
@@ -10,19 +10,28 @@ import { renderToastContent } from "@supersoniks/concorde/core/components/ui/toa
10
10
  import { normalizeStyleSheets } from "@supersoniks/concorde/core/components/ui/modal/modal-utils";
11
11
  import { customScroll } from "@supersoniks/concorde/core/components/ui/_css/scroll";
12
12
  import { HTML } from "@supersoniks/concorde/utils";
13
+ import {
14
+ isToastDismissed,
15
+ markToastDismissed,
16
+ } from "@supersoniks/concorde/core/components/ui/toast/dismissed";
17
+ import { resolveToastCloseLabel } from "@supersoniks/concorde/core/components/ui/_i18n/ui-aria-labels";
13
18
 
14
- const icon: Record<string, string> = {
15
- warning: "warning-circled-outline",
16
- success: "check-circled-outline",
17
- error: "warning-circled-outline",
18
- info: "info-empty",
19
- };
19
+ import {
20
+ statusAriaLive,
21
+ statusAriaRole,
22
+ statusColor,
23
+ statusIcon,
24
+ toStatus,
25
+ } from "@supersoniks/concorde/core/components/ui/_css/type";
26
+
27
+ const AUTO_HIDE_DELAY = 6000;
20
28
  const tagName = "sonic-toast-item";
21
29
 
22
30
  @customElement(tagName)
23
31
  export class SonicToastItem extends LitElement {
24
32
  static styles = [
25
33
  customScroll,
34
+ statusColor,
26
35
  css`
27
36
  * {
28
37
  box-sizing: border-box;
@@ -31,7 +40,6 @@ export class SonicToastItem extends LitElement {
31
40
  display: block;
32
41
  pointer-events: auto;
33
42
  position: relative;
34
- --sc-toast-status-color: transparent;
35
43
  --sc-toast-color: var(--sc-base-content, #000);
36
44
  --sc-toast-bg: var(--sc-base, #fff);
37
45
  --sc-toast-rounded: var(--sc-rounded-md);
@@ -52,14 +60,13 @@ export class SonicToastItem extends LitElement {
52
60
  pointer-events: auto;
53
61
  overflow: hidden;
54
62
  line-height: 1.25;
55
- color: var(--sc-toast-color);
56
63
  box-shadow: var(--sc-toast-shadow);
57
64
  border-radius: var(--sc-toast-rounded);
58
65
  background: var(--sc-toast-bg);
59
66
  }
60
67
 
61
68
  .sonic-toast-content {
62
- padding: 1em 2.5rem 1em 1em;
69
+ padding: 0.8em 2.25rem 0.8em 0.8em;
63
70
  display: flex;
64
71
  gap: 0.5rem;
65
72
  overflow: auto;
@@ -67,6 +74,7 @@ export class SonicToastItem extends LitElement {
67
74
  }
68
75
 
69
76
  .sonic-toast-text {
77
+ color: var(--sc-toast-color);
70
78
  align-self: center;
71
79
  margin-top: auto;
72
80
  margin-bottom: auto;
@@ -114,6 +122,7 @@ export class SonicToastItem extends LitElement {
114
122
  border-radius: 50%;
115
123
  text-align: center;
116
124
  opacity: 0.5;
125
+ color: var(--sc-toast-color);
117
126
  background: rgba(0, 0, 0, 0);
118
127
  }
119
128
 
@@ -133,39 +142,21 @@ export class SonicToastItem extends LitElement {
133
142
  /*Title*/
134
143
  .sonic-toast-title {
135
144
  font-weight: bold;
136
- font-size: 1.15rem;
137
- margin: 0.15em 0 0.25em;
145
+ font-size: 1.125rem;
146
+ margin: 0.15em 0 0.15em;
138
147
  line-height: 1.2;
139
148
  }
140
149
 
141
- /*STATUS*/
142
- .success {
143
- --sc-toast-status-color: var(
144
- --sc-success,
145
- var(--sc-base-content, #000)
146
- );
150
+ /*STATUS — accent via statusColor (--sc-_color) */
151
+ :host([status]) .sonic-toast {
152
+ border-top: 3px solid var(--sc-_color);
147
153
  }
148
154
 
149
- .error {
150
- --sc-toast-status-color: var(--sc-danger, var(--sc-base-content, #000));
151
- }
152
-
153
- .warning {
154
- --sc-toast-status-color: var(
155
- --sc-warning,
156
- var(--sc-base-content, #000)
157
- );
158
- }
159
-
160
- .info {
161
- --sc-toast-status-color: var(--sc-info, var(--sc-base-content, #000));
162
- }
163
-
164
- .success,
165
- .error,
166
- .info,
167
- .warning {
168
- border-top: 3px solid var(--sc-toast-status-color, currentColor);
155
+ :host([status="success"]) .sonic-toast:before,
156
+ :host([status="error"]) .sonic-toast:before,
157
+ :host([status="info"]) .sonic-toast:before,
158
+ :host([status="warning"]) .sonic-toast:before {
159
+ background-color: var(--sc-_color);
169
160
  }
170
161
 
171
162
  .sonic-toast:before {
@@ -180,20 +171,12 @@ export class SonicToastItem extends LitElement {
180
171
  pointer-events: none;
181
172
  transition: 0.2s;
182
173
  border-radius: var(--sc-toast-rounded);
183
- background-color: var(--sc-toast-status-color);
184
174
  }
185
175
 
186
176
  .sonic-toast:hover:before {
187
177
  opacity: 0.025;
188
178
  }
189
179
 
190
- .info .sonic-toast-icon,
191
- .error .sonic-toast-icon,
192
- .success .sonic-toast-icon,
193
- .warning .sonic-toast-icon {
194
- color: var(--sc-toast-status-color, currentColor);
195
- }
196
-
197
180
  .sonic-toast-icon {
198
181
  position: sticky;
199
182
  top: 0;
@@ -209,7 +192,7 @@ export class SonicToastItem extends LitElement {
209
192
  @property() label: ToastContent = "";
210
193
  @property({ type: String }) id = "";
211
194
  @property() text: ToastContent = "";
212
- @property({ type: String }) status: ToastStatus = "";
195
+ @property({ type: String, reflect: true }) status: ToastStatus = "";
213
196
  @property({ type: Boolean }) ghost = false;
214
197
  @property({ type: Boolean }) preserve = false;
215
198
  @property({ type: Boolean }) dismissForever = false;
@@ -218,7 +201,42 @@ export class SonicToastItem extends LitElement {
218
201
  @property({ attribute: false }) styleSheet?: ToastStyleSheetInput;
219
202
  @state() visible = true;
220
203
  @state() private _titleId: string = "";
204
+ @state() private _dismissed = false;
221
205
  private _adoptedStyleSheets: Array<CSSStyleSheet> = [];
206
+ private _hideTimer?: ReturnType<typeof setTimeout>;
207
+ private _hideRemaining = AUTO_HIDE_DELAY;
208
+ private _hideStartedAt = 0;
209
+ private _hovered = false;
210
+ private _focused = false;
211
+ private _hidden = false;
212
+
213
+ connectedCallback(): void {
214
+ super.connectedCallback();
215
+ this.addEventListener("mouseenter", this._onMouseEnter);
216
+ this.addEventListener("mouseleave", this._onMouseLeave);
217
+ this.addEventListener("focusin", this._onFocusIn);
218
+ this.addEventListener("focusout", this._onFocusOut);
219
+ }
220
+
221
+ disconnectedCallback(): void {
222
+ this._clearHideTimer();
223
+ this.removeEventListener("mouseenter", this._onMouseEnter);
224
+ this.removeEventListener("mouseleave", this._onMouseLeave);
225
+ this.removeEventListener("focusin", this._onFocusIn);
226
+ this.removeEventListener("focusout", this._onFocusOut);
227
+ super.disconnectedCallback();
228
+ }
229
+
230
+ protected willUpdate(changedProperties: PropertyValues): void {
231
+ if (
232
+ changedProperties.has("dismissForever") ||
233
+ changedProperties.has("id")
234
+ ) {
235
+ this._dismissed =
236
+ this.dismissForever && !!this.id && isToastDismissed(this.id);
237
+ }
238
+ super.willUpdate(changedProperties);
239
+ }
222
240
 
223
241
  // @queryAssignedElements({flatten: true, slot: main"})
224
242
  // textSlot!: HTMLElement[];
@@ -229,7 +247,7 @@ export class SonicToastItem extends LitElement {
229
247
  * - "status" pour les autres (messages informatifs)
230
248
  */
231
249
  getAriaRole(): "alert" | "status" {
232
- return this.status === "error" ? "alert" : "status";
250
+ return statusAriaRole(toStatus(this.status) ?? "default");
233
251
  }
234
252
 
235
253
  /**
@@ -238,7 +256,7 @@ export class SonicToastItem extends LitElement {
238
256
  * - "polite" pour les autres (peut attendre)
239
257
  */
240
258
  getAriaLive(): "polite" | null {
241
- return this.status === "error" ? null : "polite";
259
+ return statusAriaLive(toStatus(this.status) ?? "default");
242
260
  }
243
261
 
244
262
  /**
@@ -254,33 +272,8 @@ export class SonicToastItem extends LitElement {
254
272
  return this._titleId;
255
273
  }
256
274
 
257
- /**
258
- * Génère un label ARIA descriptif pour le bouton de fermeture
259
- */
260
- getCloseButtonLabel(): string {
261
- const statusLabels: Record<string, string> = {
262
- error: "Fermer le message d'erreur",
263
- warning: "Fermer l'avertissement",
264
- success: "Fermer le message de succès",
265
- info: "Fermer l'information",
266
- };
267
- const statusLabel = this.status
268
- ? statusLabels[this.status] || "Fermer la notification"
269
- : "Fermer la notification";
270
- return statusLabel;
271
- }
272
-
273
275
  render() {
274
- // check if the toast is dismissed
275
- if (this.dismissForever) {
276
- const dismissed = localStorage.getItem("sonic-toast-dismissed") || "{}";
277
- const dismissedObj = JSON.parse(dismissed);
278
- if (dismissedObj[this.id]) {
279
- return nothing;
280
- }
281
- }
282
-
283
- if (!this.visible) {
276
+ if (this._dismissed || !this.visible) {
284
277
  return nothing;
285
278
  }
286
279
 
@@ -295,14 +288,14 @@ export class SonicToastItem extends LitElement {
295
288
 
296
289
  const ariaLive = this.getAriaLive();
297
290
  return html`<div
298
- class="sonic-toast ${this.status} ${this.ghost ? "ghost" : ""}"
291
+ class="sonic-toast ${this.ghost ? "ghost" : ""}"
299
292
  role=${this.getAriaRole()}
300
293
  aria-live=${ariaLive || nothing}
301
294
  aria-atomic="true"
302
295
  aria-label=${ariaLabel || nothing}
303
296
  >
304
297
  <button
305
- aria-label=${this.getCloseButtonLabel()}
298
+ aria-label=${resolveToastCloseLabel(this.status)}
306
299
  class="sonic-toast-close"
307
300
  @click=${() => this.hide()}
308
301
  >
@@ -314,9 +307,9 @@ export class SonicToastItem extends LitElement {
314
307
  >
315
308
  ${this.status &&
316
309
  html`<sonic-icon
317
- name=${icon[this.status]}
310
+ name=${statusIcon[toStatus(this.status) ?? "default"]}
318
311
  class="sonic-toast-icon"
319
- size="2xl"
312
+ size="xl"
320
313
  aria-hidden="true"
321
314
  ></sonic-icon>`}
322
315
 
@@ -329,8 +322,6 @@ export class SonicToastItem extends LitElement {
329
322
  ${this.useSlot ? nothing : renderToastContent(this.text)}
330
323
  <slot></slot>
331
324
  </div>
332
-
333
- ${!this.preserve ? this.autoHide() : ""}
334
325
  </div>
335
326
  </div>`;
336
327
  }
@@ -339,6 +330,13 @@ export class SonicToastItem extends LitElement {
339
330
  if (_changedProperties.has("styleSheet")) {
340
331
  this.syncAdoptedStyleSheets();
341
332
  }
333
+ if (
334
+ _changedProperties.has("preserve") ||
335
+ _changedProperties.has("visible") ||
336
+ _changedProperties.has("_dismissed")
337
+ ) {
338
+ this._syncHideTimer();
339
+ }
342
340
  }
343
341
 
344
342
  syncAdoptedStyleSheets() {
@@ -357,30 +355,80 @@ export class SonicToastItem extends LitElement {
357
355
  }
358
356
 
359
357
  hide() {
358
+ this._clearHideTimer();
359
+ this._hidden = true;
360
360
  if (!HTML.getClosestElement(this, "sonic-toast")) {
361
361
  this.visible = false;
362
362
  }
363
363
 
364
364
  if (this.dismissForever) {
365
- // set in local Storage an Object with the id as key and if it's dismissed as value
366
- const dismissed = localStorage.getItem("sonic-toast-dismissed") || "{}";
367
- const dismissedObj = JSON.parse(dismissed);
368
- dismissedObj[this.id] = true;
369
- localStorage.setItem(
370
- "sonic-toast-dismissed",
371
- JSON.stringify(dismissedObj),
372
- );
365
+ markToastDismissed(this.id);
373
366
  }
374
367
  this.dispatchEvent(new CustomEvent("hide", { bubbles: true }));
375
368
  }
376
369
 
377
370
  show() {
371
+ this._hidden = false;
372
+ this._hideRemaining = AUTO_HIDE_DELAY;
378
373
  this.visible = true;
374
+ this._syncHideTimer();
379
375
  }
380
376
 
381
- autoHide() {
382
- setTimeout(() => {
377
+ private _syncHideTimer() {
378
+ const shouldRun =
379
+ !this.preserve &&
380
+ this.visible &&
381
+ !this._hidden &&
382
+ !this._dismissed &&
383
+ !this._hovered &&
384
+ !this._focused;
385
+
386
+ if (!shouldRun) {
387
+ this._pauseHideTimer();
388
+ return;
389
+ }
390
+ if (this._hideTimer) return;
391
+
392
+ this._hideStartedAt = Date.now();
393
+ this._hideTimer = setTimeout(() => {
394
+ this._hideTimer = undefined;
383
395
  this.hide();
384
- }, 6000);
396
+ }, this._hideRemaining);
385
397
  }
398
+
399
+ private _pauseHideTimer() {
400
+ if (!this._hideTimer) return;
401
+ clearTimeout(this._hideTimer);
402
+ this._hideTimer = undefined;
403
+ this._hideRemaining = Math.max(
404
+ 0,
405
+ this._hideRemaining - (Date.now() - this._hideStartedAt),
406
+ );
407
+ }
408
+
409
+ private _clearHideTimer() {
410
+ clearTimeout(this._hideTimer);
411
+ this._hideTimer = undefined;
412
+ }
413
+
414
+ private _onMouseEnter = () => {
415
+ this._hovered = true;
416
+ this._syncHideTimer();
417
+ };
418
+
419
+ private _onMouseLeave = () => {
420
+ this._hovered = false;
421
+ this._syncHideTimer();
422
+ };
423
+
424
+ private _onFocusIn = () => {
425
+ this._focused = true;
426
+ this._syncHideTimer();
427
+ };
428
+
429
+ private _onFocusOut = (e: FocusEvent) => {
430
+ if (this.contains(e.relatedTarget as Node | null)) return;
431
+ this._focused = false;
432
+ this._syncHideTimer();
433
+ };
386
434
  }