@supersoniks/concorde 5.0.0 → 5.0.1

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 (287) hide show
  1. package/.gitlab-ci.yml +0 -13
  2. package/build-infos.json +1 -1
  3. package/concorde-core.bundle.js +257 -273
  4. package/concorde-core.es.js +4127 -4039
  5. package/dist/concorde-core.bundle.js +257 -273
  6. package/dist/concorde-core.es.js +4127 -4039
  7. package/gitlab/job_tests.sh +25 -12
  8. package/index.html +1 -1
  9. package/package.json +3 -1
  10. package/scripts/post-build-docs.js +0 -0
  11. package/scripts/post-build.mjs +0 -0
  12. package/src/core/components/functional/fetch/fetch.md +0 -0
  13. package/src/core/components/functional/if/if.md +0 -0
  14. package/src/core/components/functional/list/list.demo.ts +0 -0
  15. package/src/core/components/functional/list/list.md +0 -0
  16. package/src/core/components/functional/list/list.ts +0 -0
  17. package/src/core/components/functional/router/router.md +0 -0
  18. package/src/core/components/functional/router/router.ts +0 -0
  19. package/src/core/components/functional/sdui/sdui.md +0 -0
  20. package/src/core/components/functional/states/states.md +0 -0
  21. package/src/core/components/functional/submit/submit.md +0 -0
  22. package/src/core/components/ui/_css/size.ts +0 -0
  23. package/src/core/components/ui/alert/alert.ts +0 -0
  24. package/src/core/components/ui/button/button.md +0 -0
  25. package/src/core/components/ui/captcha/altchaStyles.ts +0 -0
  26. package/src/core/components/ui/captcha/captcha.md +0 -0
  27. package/src/core/components/ui/card/card.md +0 -0
  28. package/src/core/components/ui/divider/divider.ts +0 -0
  29. package/src/core/components/ui/form/input/input.md +0 -0
  30. package/src/core/components/ui/form/radio/radio.md +0 -0
  31. package/src/core/components/ui/form/select/select.md +0 -0
  32. package/src/core/components/ui/form/switch/switch.md +0 -0
  33. package/src/core/components/ui/loader/loader.md +0 -0
  34. package/src/core/components/ui/menu/menu.md +0 -0
  35. package/src/core/components/ui/modal/modal-close.ts +0 -0
  36. package/src/core/components/ui/modal/modal.md +0 -0
  37. package/src/core/components/ui/pop/pop.md +103 -1
  38. package/src/core/components/ui/pop/pop.ts +70 -115
  39. package/src/core/components/ui/table/table-caption.ts +0 -0
  40. package/src/core/components/ui/table/table.md +0 -0
  41. package/src/core/components/ui/theme/theme.ts +1 -1
  42. package/src/core/components/ui/tooltip/tooltip.ts +178 -154
  43. package/src/core/utils/positioning.spec.ts +180 -0
  44. package/src/core/utils/positioning.ts +164 -0
  45. package/src/core/utils/route.ts +0 -0
  46. package/src/directives.ts +2 -3
  47. package/src/docs/_core-concept/subscriber.md +0 -0
  48. package/src/docs/_getting-started/concorde-outside.md +0 -0
  49. package/src/docs/_getting-started/my-first-subscriber.md +0 -0
  50. package/src/docs/_getting-started/pubsub.md +0 -0
  51. package/src/docs/_getting-started/start.md +0 -0
  52. package/src/docs/example/users.ts +0 -0
  53. package/src/docs/search/docs-search.json +90 -0
  54. package/src/docs/search/page.ts +0 -0
  55. package/src/docs/tailwind/css/tailwind.css +7 -6
  56. package/src/tsconfig.json +387 -381
  57. package/src/tsconfig.tsbuildinfo +1 -1
  58. package/vitest.setup.ts +49 -13
  59. package/.github/workflows/ci.yml +0 -21
  60. package/.github-export-ignore +0 -30
  61. package/CONTRIBUTING.md +0 -59
  62. package/DEV.md +0 -54
  63. package/LICENSE +0 -21
  64. package/OPEN_SOURCE_CHECKPOINT.md +0 -133
  65. package/concorde-3d.bundle.js +0 -3960
  66. package/concorde-3d.es.js +0 -33433
  67. package/concorde-hugging-face-infer.bundle.js +0 -32
  68. package/concorde-hugging-face-infer.es.js +0 -2952
  69. package/concorde-shader.bundle.js +0 -116
  70. package/concorde-shader.es.js +0 -8353
  71. package/concorde-webgpu.bundle.js +0 -307
  72. package/concorde-webgpu.es.js +0 -8627
  73. package/docs/altcha-widget.js +0 -2662
  74. package/docs/assets/index-BZiZXMU-.css +0 -1
  75. package/docs/assets/index-FmK4JaIM.js +0 -11992
  76. package/docs/crawl/core/components/functional/date/date.html +0 -190
  77. package/docs/crawl/core/components/functional/fetch/fetch.html +0 -109
  78. package/docs/crawl/core/components/functional/if/if.html +0 -27
  79. package/docs/crawl/core/components/functional/jsonata/jsonata.html +0 -79
  80. package/docs/crawl/core/components/functional/list/list.html +0 -70
  81. package/docs/crawl/core/components/functional/mix/mix.html +0 -45
  82. package/docs/crawl/core/components/functional/queue/queue.html +0 -96
  83. package/docs/crawl/core/components/functional/router/router.html +0 -106
  84. package/docs/crawl/core/components/functional/sdui/sdui.html +0 -269
  85. package/docs/crawl/core/components/functional/states/states.html +0 -66
  86. package/docs/crawl/core/components/functional/submit/submit.html +0 -160
  87. package/docs/crawl/core/components/functional/subscriber/subscriber.html +0 -67
  88. package/docs/crawl/core/components/functional/value/value.html +0 -51
  89. package/docs/crawl/core/components/ui/alert/alert.html +0 -110
  90. package/docs/crawl/core/components/ui/alert-messages/alert-messages.html +0 -23
  91. package/docs/crawl/core/components/ui/badge/badge.html +0 -119
  92. package/docs/crawl/core/components/ui/button/button.html +0 -149
  93. package/docs/crawl/core/components/ui/captcha/captcha.html +0 -30
  94. package/docs/crawl/core/components/ui/card/card.html +0 -99
  95. package/docs/crawl/core/components/ui/divider/divider.html +0 -58
  96. package/docs/crawl/core/components/ui/form/checkbox/checkbox.html +0 -73
  97. package/docs/crawl/core/components/ui/form/fieldset/fieldset.html +0 -135
  98. package/docs/crawl/core/components/ui/form/form-actions/form-actions.html +0 -79
  99. package/docs/crawl/core/components/ui/form/form-layout/form-layout.html +0 -62
  100. package/docs/crawl/core/components/ui/form/input/input.html +0 -109
  101. package/docs/crawl/core/components/ui/form/input-autocomplete/input-autocomplete.html +0 -106
  102. package/docs/crawl/core/components/ui/form/radio/radio.html +0 -56
  103. package/docs/crawl/core/components/ui/form/select/select.html +0 -73
  104. package/docs/crawl/core/components/ui/form/switch/switch.html +0 -56
  105. package/docs/crawl/core/components/ui/form/textarea/textarea.html +0 -59
  106. package/docs/crawl/core/components/ui/group/group.html +0 -82
  107. package/docs/crawl/core/components/ui/icon/icon.html +0 -133
  108. package/docs/crawl/core/components/ui/image/image.html +0 -110
  109. package/docs/crawl/core/components/ui/link/link.html +0 -42
  110. package/docs/crawl/core/components/ui/loader/loader.html +0 -50
  111. package/docs/crawl/core/components/ui/menu/menu.html +0 -322
  112. package/docs/crawl/core/components/ui/modal/modal.html +0 -117
  113. package/docs/crawl/core/components/ui/pop/pop.html +0 -102
  114. package/docs/crawl/core/components/ui/progress/progress.html +0 -58
  115. package/docs/crawl/core/components/ui/table/table.html +0 -413
  116. package/docs/crawl/core/components/ui/toast/toast.html +0 -122
  117. package/docs/crawl/core/components/ui/tooltip/tooltip.html +0 -79
  118. package/docs/crawl/docs/_core-concept/dataFlow.html +0 -177
  119. package/docs/crawl/docs/_core-concept/overview.html +0 -66
  120. package/docs/crawl/docs/_core-concept/subscriber.html +0 -75
  121. package/docs/crawl/docs/_decorators/ancestor-attribute.html +0 -116
  122. package/docs/crawl/docs/_decorators/auto-subscribe.html +0 -202
  123. package/docs/crawl/docs/_decorators/bind.html +0 -141
  124. package/docs/crawl/docs/_decorators/get.html +0 -149
  125. package/docs/crawl/docs/_decorators/handle.html +0 -165
  126. package/docs/crawl/docs/_decorators/on-assign.html +0 -355
  127. package/docs/crawl/docs/_decorators/patch.html +0 -78
  128. package/docs/crawl/docs/_decorators/post.html +0 -139
  129. package/docs/crawl/docs/_decorators/publish.html +0 -61
  130. package/docs/crawl/docs/_decorators/put.html +0 -78
  131. package/docs/crawl/docs/_decorators/subscribe.html +0 -109
  132. package/docs/crawl/docs/_decorators/wait-for-ancestors.html +0 -137
  133. package/docs/crawl/docs/_directives/sub.html +0 -93
  134. package/docs/crawl/docs/_getting-started/ai-agents.html +0 -173
  135. package/docs/crawl/docs/_getting-started/concorde-manual-install.html +0 -100
  136. package/docs/crawl/docs/_getting-started/concorde-outside.html +0 -58
  137. package/docs/crawl/docs/_getting-started/create-a-component.html +0 -111
  138. package/docs/crawl/docs/_getting-started/my-first-component.html +0 -189
  139. package/docs/crawl/docs/_getting-started/my-first-subscriber.html +0 -110
  140. package/docs/crawl/docs/_getting-started/pubsub.html +0 -62
  141. package/docs/crawl/docs/_getting-started/start.html +0 -76
  142. package/docs/crawl/docs/_getting-started/theming.html +0 -85
  143. package/docs/crawl/docs/_misc/api-configuration.html +0 -149
  144. package/docs/crawl/docs/_misc/dataProviderKey.html +0 -135
  145. package/docs/crawl/docs/_misc/docs-mock-api.html +0 -158
  146. package/docs/crawl/docs/_misc/dynamic-path.html +0 -180
  147. package/docs/crawl/docs/_misc/endpoint.html +0 -50
  148. package/docs/crawl/docs/_misc/formelement-subscriber-removal.html +0 -138
  149. package/docs/crawl/docs/_misc/html-integration.html +0 -48
  150. package/docs/crawl/hubs/decorators.html +0 -547
  151. package/docs/crawl/hubs/form-components.html +0 -357
  152. package/docs/crawl/hubs/remote-data.html +0 -452
  153. package/docs/crawl/index.html +0 -101
  154. package/docs/css/docs.css +0 -0
  155. package/docs/docs-mock-api-sw.js +0 -720
  156. package/docs/docs-mock-api-sw.js.map +0 -7
  157. package/docs/fonts/ClashGrotesk-Bold.eot +0 -0
  158. package/docs/fonts/ClashGrotesk-Bold.ttf +0 -0
  159. package/docs/fonts/ClashGrotesk-Bold.woff +0 -0
  160. package/docs/fonts/ClashGrotesk-Bold.woff2 +0 -0
  161. package/docs/fonts/ClashGrotesk-Extralight.eot +0 -0
  162. package/docs/fonts/ClashGrotesk-Extralight.ttf +0 -0
  163. package/docs/fonts/ClashGrotesk-Extralight.woff +0 -0
  164. package/docs/fonts/ClashGrotesk-Extralight.woff2 +0 -0
  165. package/docs/fonts/ClashGrotesk-Light.eot +0 -0
  166. package/docs/fonts/ClashGrotesk-Light.ttf +0 -0
  167. package/docs/fonts/ClashGrotesk-Light.woff +0 -0
  168. package/docs/fonts/ClashGrotesk-Light.woff2 +0 -0
  169. package/docs/fonts/ClashGrotesk-Medium.eot +0 -0
  170. package/docs/fonts/ClashGrotesk-Medium.ttf +0 -0
  171. package/docs/fonts/ClashGrotesk-Medium.woff +0 -0
  172. package/docs/fonts/ClashGrotesk-Medium.woff2 +0 -0
  173. package/docs/fonts/ClashGrotesk-Regular.eot +0 -0
  174. package/docs/fonts/ClashGrotesk-Regular.ttf +0 -0
  175. package/docs/fonts/ClashGrotesk-Regular.woff +0 -0
  176. package/docs/fonts/ClashGrotesk-Regular.woff2 +0 -0
  177. package/docs/fonts/ClashGrotesk-Semibold.eot +0 -0
  178. package/docs/fonts/ClashGrotesk-Semibold.ttf +0 -0
  179. package/docs/fonts/ClashGrotesk-Semibold.woff +0 -0
  180. package/docs/fonts/ClashGrotesk-Semibold.woff2 +0 -0
  181. package/docs/fonts/ClashGrotesk-Variable.eot +0 -0
  182. package/docs/fonts/ClashGrotesk-Variable.ttf +0 -0
  183. package/docs/fonts/ClashGrotesk-Variable.woff +0 -0
  184. package/docs/fonts/ClashGrotesk-Variable.woff2 +0 -0
  185. package/docs/img/concorde-icon.svg +0 -5
  186. package/docs/img/concorde-logo.svg +0 -1
  187. package/docs/img/concorde.png +0 -0
  188. package/docs/img/concorde_def.png +0 -0
  189. package/docs/img/concorde_seuil.png.webp +0 -0
  190. package/docs/img/concorde_seuil_invert.png +0 -0
  191. package/docs/img/paul_metrand.jpg +0 -0
  192. package/docs/img/paul_metrand_xs.jpg +0 -0
  193. package/docs/index.html +0 -93
  194. package/docs/llms-full.txt +0 -5787
  195. package/docs/llms.txt +0 -104
  196. package/docs/robots.txt +0 -16
  197. package/docs/sitemap.xml +0 -237
  198. package/docs/src/core/components/functional/date/date.md +0 -290
  199. package/docs/src/core/components/functional/fetch/fetch.md +0 -125
  200. package/docs/src/core/components/functional/if/if.md +0 -9
  201. package/docs/src/core/components/functional/jsonata/jsonata.md +0 -56
  202. package/docs/src/core/components/functional/list/list.md +0 -65
  203. package/docs/src/core/components/functional/mix/mix.md +0 -41
  204. package/docs/src/core/components/functional/queue/queue.md +0 -72
  205. package/docs/src/core/components/functional/router/router.md +0 -94
  206. package/docs/src/core/components/functional/sdui/default-library.json +0 -108
  207. package/docs/src/core/components/functional/sdui/example.json +0 -99
  208. package/docs/src/core/components/functional/sdui/sdui.md +0 -356
  209. package/docs/src/core/components/functional/states/states.md +0 -87
  210. package/docs/src/core/components/functional/submit/submit.md +0 -114
  211. package/docs/src/core/components/functional/subscriber/subscriber.md +0 -91
  212. package/docs/src/core/components/functional/value/value.md +0 -35
  213. package/docs/src/core/components/ui/alert/alert.md +0 -121
  214. package/docs/src/core/components/ui/alert-messages/alert-messages.md +0 -0
  215. package/docs/src/core/components/ui/badge/badge.md +0 -127
  216. package/docs/src/core/components/ui/button/button.md +0 -182
  217. package/docs/src/core/components/ui/captcha/captcha.md +0 -12
  218. package/docs/src/core/components/ui/card/card.md +0 -97
  219. package/docs/src/core/components/ui/divider/divider.md +0 -35
  220. package/docs/src/core/components/ui/form/checkbox/checkbox.md +0 -77
  221. package/docs/src/core/components/ui/form/fieldset/fieldset.md +0 -129
  222. package/docs/src/core/components/ui/form/form-actions/form-actions.md +0 -77
  223. package/docs/src/core/components/ui/form/form-layout/form-layout.md +0 -44
  224. package/docs/src/core/components/ui/form/input/input.md +0 -142
  225. package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +0 -133
  226. package/docs/src/core/components/ui/form/radio/radio.md +0 -57
  227. package/docs/src/core/components/ui/form/select/select.md +0 -71
  228. package/docs/src/core/components/ui/form/switch/switch.md +0 -57
  229. package/docs/src/core/components/ui/form/textarea/textarea.md +0 -65
  230. package/docs/src/core/components/ui/group/group.md +0 -75
  231. package/docs/src/core/components/ui/icon/icon.md +0 -125
  232. package/docs/src/core/components/ui/icon/icons.json +0 -1
  233. package/docs/src/core/components/ui/image/image.md +0 -107
  234. package/docs/src/core/components/ui/link/link.md +0 -43
  235. package/docs/src/core/components/ui/loader/loader.md +0 -55
  236. package/docs/src/core/components/ui/menu/menu.md +0 -329
  237. package/docs/src/core/components/ui/modal/modal.md +0 -119
  238. package/docs/src/core/components/ui/pop/pop.md +0 -96
  239. package/docs/src/core/components/ui/progress/progress.md +0 -63
  240. package/docs/src/core/components/ui/table/table.md +0 -455
  241. package/docs/src/core/components/ui/toast/toast.md +0 -166
  242. package/docs/src/core/components/ui/tooltip/tooltip.md +0 -82
  243. package/docs/src/docs/_core-concept/dataFlow.md +0 -88
  244. package/docs/src/docs/_core-concept/overview.md +0 -57
  245. package/docs/src/docs/_core-concept/subscriber.md +0 -75
  246. package/docs/src/docs/_decorators/ancestor-attribute.md +0 -127
  247. package/docs/src/docs/_decorators/auto-subscribe.md +0 -202
  248. package/docs/src/docs/_decorators/bind.md +0 -167
  249. package/docs/src/docs/_decorators/get.md +0 -162
  250. package/docs/src/docs/_decorators/handle.md +0 -175
  251. package/docs/src/docs/_decorators/on-assign.md +0 -390
  252. package/docs/src/docs/_decorators/patch.md +0 -70
  253. package/docs/src/docs/_decorators/post.md +0 -146
  254. package/docs/src/docs/_decorators/publish.md +0 -55
  255. package/docs/src/docs/_decorators/put.md +0 -70
  256. package/docs/src/docs/_decorators/subscribe.md +0 -100
  257. package/docs/src/docs/_decorators/wait-for-ancestors.md +0 -163
  258. package/docs/src/docs/_directives/sub.md +0 -91
  259. package/docs/src/docs/_getting-started/ai-agents.md +0 -76
  260. package/docs/src/docs/_getting-started/concorde-manual-install.md +0 -133
  261. package/docs/src/docs/_getting-started/concorde-outside.md +0 -33
  262. package/docs/src/docs/_getting-started/create-a-component.md +0 -139
  263. package/docs/src/docs/_getting-started/my-first-component.md +0 -236
  264. package/docs/src/docs/_getting-started/my-first-subscriber.md +0 -120
  265. package/docs/src/docs/_getting-started/pubsub.md +0 -37
  266. package/docs/src/docs/_getting-started/start.md +0 -47
  267. package/docs/src/docs/_getting-started/theming.md +0 -91
  268. package/docs/src/docs/_misc/api-configuration.md +0 -81
  269. package/docs/src/docs/_misc/dataProviderKey.md +0 -168
  270. package/docs/src/docs/_misc/docs-mock-api.md +0 -63
  271. package/docs/src/docs/_misc/dynamic-path.md +0 -71
  272. package/docs/src/docs/_misc/endpoint.md +0 -45
  273. package/docs/src/docs/_misc/formelement-subscriber-removal.md +0 -115
  274. package/docs/src/docs/_misc/html-integration.md +0 -15
  275. package/docs/src/docs/search/docs-search.json +0 -9512
  276. package/docs/src/tag-list.json +0 -1
  277. package/docs/src/tsconfig-model.json +0 -23
  278. package/docs/src/tsconfig.declarations.json +0 -22
  279. package/docs/src/tsconfig.emit.json +0 -23
  280. package/docs/src/tsconfig.json +0 -858
  281. package/docs/svg/regular/plane.svg +0 -1
  282. package/docs/svg/solid/plane.svg +0 -1
  283. package/gitlab/job_github_mirror.sh +0 -39
  284. package/scripts/github-export.mjs +0 -150
  285. package/src/css.d.ts +0 -19
  286. package/src/tsconfig.declarations.json +0 -22
  287. package/src/tsconfig.emit.json +0 -23
@@ -1,221 +1,245 @@
1
1
  import { html, LitElement, css, PropertyValues } from "lit";
2
- import { customElement, property } from "lit/decorators.js";
2
+ import { customElement, property, query } from "lit/decorators.js";
3
+ import {
4
+ computeFloatingPosition,
5
+ FloatingAlign,
6
+ FloatingSide,
7
+ getFloatingBounds,
8
+ startFrameLoop,
9
+ supportsPopover,
10
+ } from "@supersoniks/concorde/core/utils/positioning";
11
+
3
12
  const tagName = "sonic-tooltip";
13
+
14
+ type TooltipPlacement = FloatingSide | `${FloatingSide}-${FloatingAlign}`;
15
+
16
+ const GAP = 4;
17
+
4
18
  @customElement(tagName)
5
19
  export class Tooltip extends LitElement {
20
+ static readonly supportsPopover = supportsPopover;
21
+
6
22
  static styles = [
7
23
  css`
8
24
  :host {
9
- position: relative;
10
25
  display: inline-flex;
11
26
  align-items: center;
12
27
  text-align: center;
13
28
  --sc-tooltip-fw: var(--sc-font-weight-base, 400);
14
- --sc-tooltip-position: calc(100% + 0.25rem);
15
29
  }
16
30
 
17
- .tooltip:before {
18
- position: absolute;
19
- content: attr(data-tooltip-text);
20
- font-size: 0.85rem;
21
- display: block;
22
- opacity: 0;
31
+ .tooltip-bubble {
32
+ position: fixed;
33
+ inset: auto;
34
+ margin: 0;
35
+ border: none;
23
36
  pointer-events: none;
37
+ font-size: 0.85rem;
24
38
  background: var(--sc-base-content, #000);
25
39
  padding: 0.32rem 0.55rem;
26
40
  border-radius: var(--sc-rounded);
27
41
  color: var(--sc-base, #fff);
28
- z-index: 999;
29
42
  line-height: 1.1;
30
43
  width: max-content;
31
44
  max-width: 18rem;
32
45
  white-space: pre-line;
33
46
  font-weight: var(--sc-tooltip-fw);
34
- transition: 0.2s;
35
- scale: 0.9;
36
- will-change: opacity, transform;
47
+ opacity: 0;
48
+ transition:
49
+ opacity 0.2s,
50
+ display 0.2s allow-discrete,
51
+ overlay 0.2s allow-discrete;
37
52
  }
38
53
 
39
- :host(:focus-visible) .tooltip:before,
40
- :host(.keyboard-focus) .tooltip:before,
41
- .tooltip:hover:before {
54
+ .tooltip-bubble:popover-open {
42
55
  opacity: 1;
43
- scale: 1;
44
56
  }
45
57
 
46
- .disabled:before {
47
- opacity: 0 !important;
48
- scale: 0.9 !important;
49
- }
50
-
51
- :host(:not([placement])) .tooltip:before,
52
- :host([placement="top"]) .tooltip:before {
53
- bottom: var(--sc-tooltip-position);
54
- left: 50%;
55
- transform: translateX(-50%);
56
- }
57
-
58
- :host([placement="top-end"]) .tooltip:before {
59
- bottom: var(--sc-tooltip-position);
60
- right: 0;
61
- }
62
- :host([placement="top-start"]) .tooltip:before {
63
- bottom: var(--sc-tooltip-position);
64
- left: 0;
65
- }
66
-
67
- :host([placement="bottom"]) .tooltip:before {
68
- top: var(--sc-tooltip-position);
69
- left: 50%;
70
- transform: translateX(-50%);
71
- }
72
-
73
- :host([placement="bottom-start"]) .tooltip:before {
74
- top: var(--sc-tooltip-position);
75
- left: 0;
76
- }
77
- :host([placement="bottom-end"]) .tooltip:before {
78
- top: var(--sc-tooltip-position);
79
- right: 0;
80
- }
81
-
82
- :host([placement="left"]) .tooltip:before {
83
- top: 50%;
84
- right: var(--sc-tooltip-position);
85
- transform: translateY(-50%);
86
- }
87
-
88
- :host([placement="left-start"]) .tooltip:before {
89
- top: 0;
90
- right: var(--sc-tooltip-position);
91
- }
92
-
93
- :host([placement="left-end"]) .tooltip:before {
94
- bottom: 0;
95
- right: var(--sc-tooltip-position);
96
- }
97
-
98
- :host([placement="right"]) .tooltip:before {
99
- top: 50%;
100
- transform: translateY(-50%);
101
- left: var(--sc-tooltip-position);
58
+ @starting-style {
59
+ .tooltip-bubble:popover-open {
60
+ opacity: 0;
61
+ }
102
62
  }
103
63
 
104
- :host([placement="right-start"]) .tooltip:before {
105
- top: 0;
106
- left: var(--sc-tooltip-position);
64
+ .tooltip-bubble.disabled {
65
+ display: none !important;
107
66
  }
108
67
 
109
- :host([placement="right-end"]) .tooltip:before {
110
- bottom: 0;
111
- left: var(--sc-tooltip-position);
68
+ @media (prefers-reduced-motion: reduce) {
69
+ .tooltip-bubble {
70
+ transition: none;
71
+ }
112
72
  }
113
73
  `,
114
74
  ];
115
75
 
116
- @property({ type: String }) label = "";
117
- @property({ type: String, reflect: true }) placement?:
118
- | "top"
119
- | "top-start"
120
- | "top-end"
121
- | "right"
122
- | "right-start"
123
- | "right-end"
124
- | "bottom"
125
- | "bottom-start"
126
- | "bottom-end"
127
- | "left"
128
- | "left-start"
129
- | "left-end";
76
+ @property({ type: String }) label?: string;
77
+ @property({ type: String, reflect: true }) placement?: TooltipPlacement;
130
78
 
131
79
  @property({ type: Boolean }) disabled = false;
132
80
  @property({ type: Boolean }) focusable = false;
133
81
 
134
- private lastKeyWasTab = false;
135
- private boundHandleKeyDown: (e: KeyboardEvent) => void;
136
- private boundHandleMouseDown: () => void;
82
+ @query(".tooltip-bubble") private bubbleEl?: HTMLElement;
137
83
 
138
- constructor() {
139
- super();
140
- this.boundHandleKeyDown = this.handleKeyDown.bind(this);
141
- this.boundHandleMouseDown = this.handleMouseDown.bind(this);
84
+ private stopPositioning?: () => void;
85
+
86
+ private get isEffectivelyDisabled() {
87
+ return this.disabled || !this.formatLabel();
88
+ }
89
+
90
+ private formatLabel() {
91
+ return (this.label ?? "")
92
+ .replaceAll(" ", " ")
93
+ .trim()
94
+ .split("\n")
95
+ .map((line) => line.trim())
96
+ .join("\n");
142
97
  }
143
98
 
144
- private handleKeyDown = (e: KeyboardEvent) => {
145
- // Détecter si Tab ou Shift+Tab a été pressé
146
- if (e.key === "Tab") {
147
- this.lastKeyWasTab = true;
99
+ private computePosition() {
100
+ const bubble = this.bubbleEl;
101
+ if (!bubble) {
102
+ return;
148
103
  }
149
- };
150
104
 
151
- private handleMouseDown = () => {
152
- // Réinitialiser le flag si la souris est utilisée
153
- this.lastKeyWasTab = false;
154
- // Retirer la classe si elle existe (au cas où)
155
- this.classList.remove("keyboard-focus");
105
+ const [side = "top", align = "center"] = (this.placement ?? "top").split(
106
+ "-",
107
+ );
108
+
109
+ const { x, y } = computeFloatingPosition({
110
+ anchor: this.getBoundingClientRect(),
111
+ floating: bubble.getBoundingClientRect(),
112
+ side: side as FloatingSide,
113
+ align: align as FloatingAlign,
114
+ gap: GAP,
115
+ bounds: getFloatingBounds(this, GAP),
116
+ });
117
+
118
+ Object.assign(bubble.style, {
119
+ left: `${Math.round(x)}px`,
120
+ top: `${Math.round(y)}px`,
121
+ });
122
+ }
123
+
124
+ private showTooltip = () => {
125
+ if (this.isEffectivelyDisabled || !this.bubbleEl) {
126
+ return;
127
+ }
128
+
129
+ if (!this.bubbleEl.matches(":popover-open")) {
130
+ this.bubbleEl.showPopover();
131
+ }
156
132
  };
157
133
 
158
- private handleFocusIn = () => {
159
- // Ajouter la classe seulement si le focus est venu du clavier (Tab)
160
- if (this.lastKeyWasTab) {
161
- this.classList.add("keyboard-focus");
134
+ private hideTooltip = () => {
135
+ if (this.bubbleEl?.matches(":popover-open")) {
136
+ this.bubbleEl.hidePopover();
162
137
  }
163
- this.lastKeyWasTab = false;
164
138
  };
165
139
 
166
- private handleFocusOut = () => {
167
- this.classList.remove("keyboard-focus");
168
- this.lastKeyWasTab = false;
140
+ private handleBeforeToggle = (e: ToggleEvent) => {
141
+ if (e.newState === "open") {
142
+ this.stopPositioning ??= startFrameLoop(() => this.computePosition());
143
+ return;
144
+ }
145
+
146
+ this.endPositioning();
169
147
  };
170
148
 
149
+ private endPositioning() {
150
+ this.stopPositioning?.();
151
+ this.stopPositioning = undefined;
152
+ }
153
+
154
+ private syncTabIndex() {
155
+ if (this.disabled) {
156
+ this.setAttribute("tabindex", "-1");
157
+ } else if (this.focusable) {
158
+ this.setAttribute("tabindex", "0");
159
+ } else {
160
+ this.removeAttribute("tabindex");
161
+ }
162
+ }
163
+
164
+ /** Fallback natif quand Popover API indisponible */
165
+ private syncNativeTitle() {
166
+ if (Tooltip.supportsPopover || this.isEffectivelyDisabled) {
167
+ this.removeAttribute("title");
168
+ return;
169
+ }
170
+
171
+ this.setAttribute("title", this.formatLabel().replaceAll("\n", " "));
172
+ }
173
+
171
174
  connectedCallback() {
172
175
  super.connectedCallback();
173
- this.addEventListener("focusin", this.handleFocusIn);
174
- this.addEventListener("focusout", this.handleFocusOut);
175
- // Écouter les événements au niveau du document avec capture pour détecter Tab
176
- document.addEventListener("keydown", this.boundHandleKeyDown, true);
177
- document.addEventListener("mousedown", this.boundHandleMouseDown, true);
176
+
177
+ if (!Tooltip.supportsPopover) {
178
+ return;
179
+ }
180
+
181
+ this.addEventListener("focusin", this.showTooltip);
182
+ this.addEventListener("focusout", this.hideTooltip);
178
183
  }
179
184
 
180
185
  disconnectedCallback() {
186
+ this.endPositioning();
187
+
188
+ if (Tooltip.supportsPopover) {
189
+ this.removeEventListener("focusin", this.showTooltip);
190
+ this.removeEventListener("focusout", this.hideTooltip);
191
+ }
192
+
181
193
  super.disconnectedCallback();
182
- this.removeEventListener("focusin", this.handleFocusIn);
183
- this.removeEventListener("focusout", this.handleFocusOut);
184
- document.removeEventListener("keydown", this.boundHandleKeyDown, true);
185
- document.removeEventListener("mousedown", this.boundHandleMouseDown, true);
186
194
  }
187
195
 
188
- protected willUpdate(_changedProperties: PropertyValues): void {
189
- if (_changedProperties.has("disabled")) {
190
- const wasDisabledBefore = _changedProperties.get("disabled") === true;
191
- if (this.disabled) {
192
- this.setAttribute("tabindex", "-1");
193
- this.classList.remove("keyboard-focus");
194
- } else if (!this.disabled && wasDisabledBefore) {
195
- if (this.focusable) {
196
- this.setAttribute("tabindex", "0");
197
- } else {
198
- this.removeAttribute("tabindex");
199
- }
200
- }
196
+ protected willUpdate(changedProperties: PropertyValues): void {
197
+ if (
198
+ !this.hasUpdated ||
199
+ changedProperties.has("disabled") ||
200
+ changedProperties.has("focusable")
201
+ ) {
202
+ this.syncTabIndex();
201
203
  }
202
- if (_changedProperties.has("focusable")) {
203
- if (this.focusable && !this.disabled) {
204
- this.setAttribute("tabindex", "0");
205
- } else if (!this.focusable && !this.disabled) {
206
- this.removeAttribute("tabindex");
207
- }
204
+
205
+ if (
206
+ !this.hasUpdated ||
207
+ changedProperties.has("label") ||
208
+ changedProperties.has("disabled")
209
+ ) {
210
+ this.syncNativeTitle();
211
+ }
212
+
213
+ if (changedProperties.has("disabled") && this.disabled) {
214
+ this.hideTooltip();
215
+ }
216
+
217
+ if (changedProperties.has("label") && this.isEffectivelyDisabled) {
218
+ this.hideTooltip();
208
219
  }
209
- super.willUpdate(_changedProperties);
220
+
221
+ super.willUpdate(changedProperties);
210
222
  }
211
223
 
212
224
  render() {
213
- const disabledClass = this.disabled || this.label == "" ? "disabled" : "";
214
- return html`<div
215
- data-tooltip-text="${this.label.trim().replace("&nbsp;", " ")}"
216
- class="tooltip ${disabledClass}"
217
- >
218
- <slot></slot>
219
- </div>`;
225
+ if (!Tooltip.supportsPopover) {
226
+ return html`<slot></slot>`;
227
+ }
228
+
229
+ const disabledClass = this.isEffectivelyDisabled ? "disabled" : "";
230
+ const label = this.formatLabel();
231
+
232
+ return html`
233
+ <slot
234
+ @mouseenter=${this.showTooltip}
235
+ @mouseleave=${this.hideTooltip}
236
+ ></slot>
237
+ <span
238
+ popover="hint"
239
+ class="tooltip-bubble ${disabledClass}"
240
+ @beforetoggle=${this.handleBeforeToggle}
241
+ >${label}</span
242
+ >
243
+ `;
220
244
  }
221
245
  }
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ computeFloatingPosition,
4
+ FloatingBounds,
5
+ FloatingSide,
6
+ } from "./positioning";
7
+
8
+ type Rect = {
9
+ left: number;
10
+ top: number;
11
+ right: number;
12
+ bottom: number;
13
+ width: number;
14
+ height: number;
15
+ };
16
+ type Size = { width: number; height: number };
17
+
18
+ const rect = (left: number, top: number, width: number, height: number) => ({
19
+ left,
20
+ top,
21
+ width,
22
+ height,
23
+ right: left + width,
24
+ bottom: top + height,
25
+ });
26
+
27
+ /**
28
+ * Ancien Pop.computePosition, étape par étape, sur un contenu simulé dont
29
+ * chaque déplacement appliqué se retrouve tel quel dans la mesure suivante.
30
+ */
31
+ function legacyPopPosition(
32
+ thisRect: Rect,
33
+ size: Size,
34
+ placement: string,
35
+ { minX, minY, maxX, maxY }: FloatingBounds,
36
+ ) {
37
+ const [placementBase, placementSec] = placement.split(" ");
38
+ const x0 = thisRect.left;
39
+ const y0 = thisRect.top;
40
+ let x = x0,
41
+ y = y0;
42
+ const yTop = y0 - size.height;
43
+ const xLeft = x0 - size.width;
44
+ const xRight = x0 + thisRect.width;
45
+ const yBottom = y0 + thisRect.height;
46
+ const xCenter = x0 + (thisRect.width - size.width) * 0.5;
47
+ const yCenter = y0 + (thisRect.height - size.height) * 0.5;
48
+ switch (placementBase) {
49
+ case "bottom":
50
+ y = yBottom;
51
+ if (placementSec == "center") x = xCenter;
52
+ break;
53
+ case "top":
54
+ y = yTop;
55
+ if (placementSec == "center") x = xCenter;
56
+ break;
57
+ case "left":
58
+ x = xLeft;
59
+ if (placementSec == "center") y = yCenter;
60
+ break;
61
+ case "right":
62
+ x = xRight;
63
+ if (placementSec == "center") y = yCenter;
64
+ break;
65
+ }
66
+
67
+ let contentX = x;
68
+ let contentY = y;
69
+ if (contentX < minX && placementBase == "left") x = xRight;
70
+ if (contentY < minY && placementBase == "top") y = yBottom;
71
+ if (contentX + size.width > maxX && placementBase == "right") x = xLeft;
72
+ if (contentY + size.height > maxY && placementBase == "bottom") y = yTop;
73
+
74
+ contentX = x;
75
+ contentY = y;
76
+ if (contentX < minX) contentX = minX;
77
+ if (contentY < minY) contentY = minY;
78
+ if (contentX + size.width > maxX) contentX = maxX - size.width;
79
+ if (contentY + size.height > maxY) contentY = maxY - size.height;
80
+
81
+ return { x: contentX, y: contentY };
82
+ }
83
+
84
+ function popPosition(
85
+ anchor: Rect,
86
+ floating: Size,
87
+ placement: string,
88
+ bounds: FloatingBounds,
89
+ ) {
90
+ const [side, secondary] = placement.split(" ");
91
+ const { x, y } = computeFloatingPosition({
92
+ anchor,
93
+ floating,
94
+ side: side as FloatingSide,
95
+ align: secondary == "center" ? "center" : "start",
96
+ bounds,
97
+ });
98
+ return { x, y };
99
+ }
100
+
101
+ const bounds: FloatingBounds = { minX: 5, minY: 5, maxX: 795, maxY: 595 };
102
+
103
+ describe("computeFloatingPosition", () => {
104
+ describe("reproduit l'ancien positionnement de sonic-pop", () => {
105
+ const placements = [
106
+ "bottom",
107
+ "top",
108
+ "right",
109
+ "left",
110
+ "bottom center",
111
+ "top center",
112
+ "right center",
113
+ "left center",
114
+ "unknown",
115
+ ];
116
+ const anchors = {
117
+ "au centre": rect(380, 280, 40, 30),
118
+ "coin haut gauche": rect(0, 0, 40, 30),
119
+ "coin bas droit": rect(760, 570, 40, 30),
120
+ "bord gauche": rect(2, 300, 40, 30),
121
+ "bord droit": rect(780, 300, 40, 30),
122
+ "bord haut": rect(400, 2, 40, 30),
123
+ "bord bas": rect(400, 590, 40, 30),
124
+ };
125
+ const sizes = {
126
+ petit: { width: 120, height: 80 },
127
+ "plus grand que la zone": { width: 900, height: 700 },
128
+ };
129
+
130
+ for (const placement of placements) {
131
+ for (const [anchorName, anchor] of Object.entries(anchors)) {
132
+ for (const [sizeName, size] of Object.entries(sizes)) {
133
+ it(`${placement} / ${anchorName} / contenu ${sizeName}`, () => {
134
+ expect(popPosition(anchor, size, placement, bounds)).toEqual(
135
+ legacyPopPosition(anchor, size, placement, bounds),
136
+ );
137
+ });
138
+ }
139
+ }
140
+ }
141
+ });
142
+
143
+ describe("placements du tooltip", () => {
144
+ const anchor = rect(380, 280, 40, 30);
145
+ const floating = { width: 100, height: 20 };
146
+ const place = (side: FloatingSide, align?: "start" | "center" | "end") =>
147
+ computeFloatingPosition({
148
+ anchor,
149
+ floating,
150
+ side,
151
+ align,
152
+ gap: 4,
153
+ bounds,
154
+ });
155
+
156
+ it("centre par défaut", () => {
157
+ expect(place("top")).toEqual({ side: "top", x: 350, y: 256 });
158
+ expect(place("right")).toEqual({ side: "right", x: 424, y: 285 });
159
+ });
160
+
161
+ it("aligne start / end", () => {
162
+ expect(place("bottom", "start")).toMatchObject({ x: 380, y: 314 });
163
+ expect(place("bottom", "end")).toMatchObject({ x: 320, y: 314 });
164
+ expect(place("left", "end")).toMatchObject({ x: 276, y: 290 });
165
+ });
166
+
167
+ it("bascule sur le côté opposé quand il déborde", () => {
168
+ const nearTop = rect(380, 10, 40, 30);
169
+ expect(
170
+ computeFloatingPosition({
171
+ anchor: nearTop,
172
+ floating,
173
+ side: "top",
174
+ gap: 4,
175
+ bounds,
176
+ }),
177
+ ).toEqual({ side: "bottom", x: 350, y: 44 });
178
+ });
179
+ });
180
+ });