@sveltia/ui 0.78.4 → 0.79.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 (181) hide show
  1. package/dist/components/alert/alert-content.svelte +51 -0
  2. package/dist/components/alert/alert-content.svelte.d.ts +39 -0
  3. package/dist/components/alert/alert.d.ts +3 -0
  4. package/dist/components/alert/alert.js +21 -0
  5. package/dist/components/alert/alert.svelte +6 -30
  6. package/dist/components/alert/infobar.svelte +7 -21
  7. package/dist/components/alert/infobar.svelte.d.ts +2 -2
  8. package/dist/components/button/button.svelte +11 -11
  9. package/dist/components/button/button.svelte.d.ts +2 -2
  10. package/dist/components/button/select-button.svelte +1 -1
  11. package/dist/components/button/select-button.svelte.d.ts +2 -2
  12. package/dist/components/button/split-button.svelte +1 -1
  13. package/dist/components/button/split-button.svelte.d.ts +3 -3
  14. package/dist/components/calendar/calendar.d.ts +2 -0
  15. package/dist/components/calendar/calendar.js +34 -0
  16. package/dist/components/calendar/calendar.svelte +49 -5
  17. package/dist/components/checkbox/checkbox.svelte +11 -4
  18. package/dist/components/checkbox/checkbox.svelte.d.ts +2 -2
  19. package/dist/components/dialog/alert-dialog.svelte +1 -1
  20. package/dist/components/dialog/alert-dialog.svelte.d.ts +2 -2
  21. package/dist/components/dialog/confirmation-dialog.svelte +1 -1
  22. package/dist/components/dialog/confirmation-dialog.svelte.d.ts +2 -2
  23. package/dist/components/dialog/dialog.svelte +8 -18
  24. package/dist/components/dialog/dialog.svelte.d.ts +2 -2
  25. package/dist/components/dialog/modal-close-button.svelte +50 -0
  26. package/dist/components/dialog/modal-close-button.svelte.d.ts +43 -0
  27. package/dist/components/dialog/prompt-dialog.svelte +1 -1
  28. package/dist/components/dialog/prompt-dialog.svelte.d.ts +4 -4
  29. package/dist/components/disclosure/disclosure.svelte +1 -1
  30. package/dist/components/drawer/drawer.svelte +12 -34
  31. package/dist/components/drawer/drawer.svelte.d.ts +1 -1
  32. package/dist/components/emoji/caret.d.ts +1 -1
  33. package/dist/components/emoji/caret.js +1 -1
  34. package/dist/components/emoji/emoji-suggestions.svelte +1 -1
  35. package/dist/components/emoji/emoji-suggestions.svelte.d.ts +3 -3
  36. package/dist/components/emoji/emoji.d.ts +2 -2
  37. package/dist/components/emoji/emoji.js +1 -1
  38. package/dist/components/emoji/position.d.ts +2 -2
  39. package/dist/components/emoji/position.js +1 -1
  40. package/dist/components/grid/grid-body.svelte +6 -2
  41. package/dist/components/grid/grid-cell.svelte +3 -9
  42. package/dist/components/grid/grid-col-header.svelte +3 -9
  43. package/dist/components/grid/grid-foot.svelte +4 -11
  44. package/dist/components/grid/grid-head.svelte +4 -11
  45. package/dist/components/grid/grid-row-header.svelte +3 -10
  46. package/dist/components/grid/grid-row.svelte +3 -16
  47. package/dist/components/listbox/listbox.svelte +1 -1
  48. package/dist/components/listbox/listbox.svelte.d.ts +1 -1
  49. package/dist/components/listbox/option.svelte +1 -1
  50. package/dist/components/listbox/option.svelte.d.ts +2 -2
  51. package/dist/components/menu/menu-button.svelte +2 -2
  52. package/dist/components/menu/menu-button.svelte.d.ts +4 -4
  53. package/dist/components/menu/menu-item-checkable.svelte +71 -0
  54. package/dist/components/menu/menu-item-checkable.svelte.d.ts +25 -0
  55. package/dist/components/menu/menu-item-checkbox.svelte +3 -36
  56. package/dist/components/menu/menu-item-checkbox.svelte.d.ts +3 -3
  57. package/dist/components/menu/menu-item-group.svelte +8 -5
  58. package/dist/components/menu/menu-item-group.svelte.d.ts +9 -1
  59. package/dist/components/menu/menu-item-radio.svelte +3 -36
  60. package/dist/components/menu/menu-item-radio.svelte.d.ts +3 -3
  61. package/dist/components/menu/menu-item.svelte +22 -6
  62. package/dist/components/menu/menu-item.svelte.d.ts +4 -4
  63. package/dist/components/radio/radio.svelte +12 -1
  64. package/dist/components/resizable-pane/resizable-handle.svelte +14 -18
  65. package/dist/components/resizable-pane/resizable-pane-group.svelte +9 -6
  66. package/dist/components/resizable-pane/resizable-pane.svelte +4 -4
  67. package/dist/components/resizable-pane/resizable-pane.svelte.d.ts +7 -6
  68. package/dist/components/resizable-pane/sizing.d.ts +5 -4
  69. package/dist/components/resizable-pane/sizing.js +84 -25
  70. package/dist/components/scroll/infinite-scroll.svelte +16 -21
  71. package/dist/components/select/combobox.svelte +43 -52
  72. package/dist/components/select/combobox.svelte.d.ts +2 -2
  73. package/dist/components/select/select-tags.svelte +7 -1
  74. package/dist/components/select/select.svelte +2 -22
  75. package/dist/components/select/select.svelte.d.ts +1 -1
  76. package/dist/components/slider/slider.d.ts +6 -0
  77. package/dist/components/slider/slider.js +55 -2
  78. package/dist/components/slider/slider.svelte +63 -32
  79. package/dist/components/table/table-cell.svelte +3 -9
  80. package/dist/components/table/table-col-header.svelte +3 -9
  81. package/dist/components/table/table-foot.svelte +4 -11
  82. package/dist/components/table/table-head.svelte +4 -11
  83. package/dist/components/table/table-part.svelte +69 -0
  84. package/dist/components/table/table-part.svelte.d.ts +48 -0
  85. package/dist/components/table/table-row-header.svelte +3 -10
  86. package/dist/components/table/table-row.svelte +3 -10
  87. package/dist/components/tabs/tab-list.svelte +12 -0
  88. package/dist/components/tabs/tab.svelte +1 -1
  89. package/dist/components/tabs/tab.svelte.d.ts +2 -2
  90. package/dist/components/text-editor/constants.d.ts +5 -5
  91. package/dist/components/text-editor/constants.js +1 -1
  92. package/dist/components/text-editor/core.d.ts +4 -5
  93. package/dist/components/text-editor/core.js +372 -285
  94. package/dist/components/text-editor/emoji-autocomplete.svelte +1 -1
  95. package/dist/components/text-editor/lexical-root.svelte +1 -1
  96. package/dist/components/text-editor/raw-markdown.d.ts +6 -33
  97. package/dist/components/text-editor/raw-markdown.js +63 -8
  98. package/dist/components/text-editor/shiki/facade.d.ts +1 -1
  99. package/dist/components/text-editor/shiki/facade.js +63 -48
  100. package/dist/components/text-editor/shiki/generated.d.ts +1 -1
  101. package/dist/components/text-editor/shiki/generated.js +1 -1
  102. package/dist/components/text-editor/shiki/highlighter.d.ts +6 -1
  103. package/dist/components/text-editor/shiki/highlighter.js +3 -4
  104. package/dist/components/text-editor/shiki/loader.d.ts +1 -1
  105. package/dist/components/text-editor/shiki/loader.js +1 -1
  106. package/dist/components/text-editor/shiki/version.d.ts +1 -1
  107. package/dist/components/text-editor/shiki/version.js +1 -1
  108. package/dist/components/text-editor/store.svelte.d.ts +1 -1
  109. package/dist/components/text-editor/store.svelte.js +5 -2
  110. package/dist/components/text-editor/text-editor.svelte +2 -2
  111. package/dist/components/text-editor/text-editor.svelte.d.ts +3 -3
  112. package/dist/components/text-editor/toolbar/code-language-switcher.svelte +24 -16
  113. package/dist/components/text-editor/toolbar/format-text-button.svelte +7 -31
  114. package/dist/components/text-editor/toolbar/format-text-button.svelte.d.ts +1 -1
  115. package/dist/components/text-editor/toolbar/insert-component.d.ts +3 -0
  116. package/dist/components/text-editor/toolbar/insert-component.js +34 -0
  117. package/dist/components/text-editor/toolbar/insert-item-button.svelte +4 -24
  118. package/dist/components/text-editor/toolbar/insert-item-button.svelte.d.ts +1 -1
  119. package/dist/components/text-editor/toolbar/insert-link-button.svelte +9 -25
  120. package/dist/components/text-editor/toolbar/insert-menu-button.svelte +4 -24
  121. package/dist/components/text-editor/toolbar/insert-menu-button.svelte.d.ts +1 -1
  122. package/dist/components/text-editor/toolbar/text-editor-toolbar.svelte +1 -1
  123. package/dist/components/text-editor/toolbar/toggle-block-menu-item.svelte +31 -35
  124. package/dist/components/text-editor/toolbar/toggle-block-menu-item.svelte.d.ts +1 -1
  125. package/dist/components/text-editor/toolbar/toolbar-wrapper.svelte +0 -10
  126. package/dist/components/text-editor/toolbar/toolbar-wrapper.svelte.d.ts +1 -11
  127. package/dist/components/text-editor/transformers/table.d.ts +0 -1
  128. package/dist/components/text-editor/transformers/table.js +1 -2
  129. package/dist/components/text-field/emoji-autocomplete.svelte +1 -1
  130. package/dist/components/text-field/masked-input.svelte +179 -0
  131. package/dist/components/text-field/masked-input.svelte.d.ts +42 -0
  132. package/dist/components/text-field/number-input.js +7 -1
  133. package/dist/components/text-field/number-input.svelte +13 -5
  134. package/dist/components/text-field/number-input.svelte.d.ts +3 -3
  135. package/dist/components/text-field/password-input.svelte +3 -116
  136. package/dist/components/text-field/password-input.svelte.d.ts +3 -3
  137. package/dist/components/text-field/search-bar.svelte +1 -1
  138. package/dist/components/text-field/search-bar.svelte.d.ts +3 -3
  139. package/dist/components/text-field/secret-input.svelte +3 -119
  140. package/dist/components/text-field/secret-input.svelte.d.ts +3 -3
  141. package/dist/components/text-field/text-area.svelte +1 -1
  142. package/dist/components/text-field/text-area.svelte.d.ts +3 -3
  143. package/dist/components/text-field/text-input.svelte +1 -1
  144. package/dist/components/text-field/text-input.svelte.d.ts +3 -3
  145. package/dist/components/toast/toast.svelte +11 -20
  146. package/dist/components/toast/toast.svelte.d.ts +1 -1
  147. package/dist/components/tree/tree-item.svelte +1 -1
  148. package/dist/components/tree/tree-item.svelte.d.ts +1 -1
  149. package/dist/components/tree/tree.svelte +1 -1
  150. package/dist/components/tree/tree.svelte.d.ts +1 -1
  151. package/dist/components/util/font-links.svelte +1 -0
  152. package/dist/components/util/modal.svelte +37 -1
  153. package/dist/components/util/modal.svelte.d.ts +1 -1
  154. package/dist/components/util/popup.svelte +2 -5
  155. package/dist/components/util/popup.svelte.d.ts +2 -2
  156. package/dist/components/util/visibility-observer.svelte +1 -0
  157. package/dist/locales/ar.yaml +1 -1
  158. package/dist/locales/sk.yaml +136 -0
  159. package/dist/services/group.svelte.d.ts +10 -1
  160. package/dist/services/group.svelte.js +361 -251
  161. package/dist/services/navigation.d.ts +19 -0
  162. package/dist/services/navigation.js +84 -0
  163. package/dist/services/popup-position.d.ts +90 -0
  164. package/dist/services/popup-position.js +275 -0
  165. package/dist/services/popup.svelte.d.ts +16 -17
  166. package/dist/services/popup.svelte.js +25 -169
  167. package/dist/services/scroll.d.ts +1 -0
  168. package/dist/services/scroll.js +17 -0
  169. package/dist/services/select.svelte.d.ts +2 -2
  170. package/dist/services/select.svelte.js +1 -1
  171. package/dist/services/text.d.ts +2 -0
  172. package/dist/services/text.js +28 -0
  173. package/dist/services/tree.svelte.d.ts +3 -11
  174. package/dist/services/tree.svelte.js +35 -77
  175. package/dist/services/type-ahead.d.ts +0 -4
  176. package/dist/services/type-ahead.js +3 -1
  177. package/dist/services/widget.d.ts +8 -0
  178. package/dist/services/widget.js +39 -0
  179. package/dist/shiki-engine.js +15 -15
  180. package/dist/styles/mixins.scss +126 -0
  181. package/package.json +24 -20
@@ -0,0 +1,19 @@
1
+ export function getArrowKeys(orientation: string, rtl: boolean): {
2
+ prevKey: string;
3
+ nextKey: string;
4
+ };
5
+ export function getLinearTargetIndex({ key, index, count, prevKey, nextKey }: {
6
+ key: string;
7
+ index: number;
8
+ count: number;
9
+ prevKey: string;
10
+ nextKey: string;
11
+ }): number;
12
+ export function getGridTargetIndex({ key, index, count, columnCount, prevKey, nextKey }: {
13
+ key: string;
14
+ index: number;
15
+ count: number;
16
+ columnCount: number;
17
+ prevKey: string;
18
+ nextKey: string;
19
+ }): number;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Arrow key navigation shared by the composite widgets. These functions only map a key and the
3
+ * current position to a new position; finding the members and moving focus is up to the caller.
4
+ */
5
+
6
+ /**
7
+ * Get the arrow keys that move to the previous and next member along the given orientation. In a
8
+ * right-to-left layout, the inline arrows are mirrored: ArrowLeft moves forward, ArrowRight back.
9
+ * @param {string} orientation `horizontal` or `vertical`.
10
+ * @param {boolean} rtl Whether the layout runs right to left.
11
+ * @returns {{ prevKey: string, nextKey: string }} Keys.
12
+ */
13
+ export const getArrowKeys = (orientation, rtl) => {
14
+ if (orientation === 'horizontal') {
15
+ return rtl
16
+ ? { prevKey: 'ArrowRight', nextKey: 'ArrowLeft' }
17
+ : { prevKey: 'ArrowLeft', nextKey: 'ArrowRight' };
18
+ }
19
+
20
+ return { prevKey: 'ArrowUp', nextKey: 'ArrowDown' };
21
+ };
22
+
23
+ /**
24
+ * Get the index to move to in a list when an arrow key is pressed. The movement wraps around at
25
+ * either end, and with nothing current yet, the previous key starts from the last member and the
26
+ * next key from the first one.
27
+ * @param {object} args Arguments.
28
+ * @param {string} args.key Pressed key.
29
+ * @param {number} args.index Index of the current member, or `-1` if there’s none.
30
+ * @param {number} args.count Number of members.
31
+ * @param {string} args.prevKey Key that moves to the previous member.
32
+ * @param {string} args.nextKey Key that moves to the next member.
33
+ * @returns {number} Index of the member to move to, or `-1` if the key doesn’t move.
34
+ */
35
+ export const getLinearTargetIndex = ({ key, index, count, prevKey, nextKey }) => {
36
+ if (key === prevKey) {
37
+ return index > 0 ? index - 1 : count - 1;
38
+ }
39
+
40
+ if (key === nextKey) {
41
+ return index < count - 1 ? index + 1 : 0;
42
+ }
43
+
44
+ return -1;
45
+ };
46
+
47
+ /**
48
+ * Get the index to move to in a grid layout when an arrow key is pressed. The vertical arrows move
49
+ * by a visual row and the inline arrows by a single member, and none of them wraps around. With
50
+ * nothing current yet, the arrows start from either end, as in a list.
51
+ * @param {object} args Arguments.
52
+ * @param {string} args.key Pressed key.
53
+ * @param {number} args.index Index of the current member, or `-1` if there’s none.
54
+ * @param {number} args.count Number of members.
55
+ * @param {number} args.columnCount Number of members per visual row.
56
+ * @param {string} args.prevKey Inline key that moves to the previous member.
57
+ * @param {string} args.nextKey Inline key that moves to the next member.
58
+ * @returns {number} Index of the member to move to, or `-1` if the key doesn’t move.
59
+ */
60
+ export const getGridTargetIndex = ({ key, index, count, columnCount, prevKey, nextKey }) => {
61
+ const lastIndex = count - 1;
62
+ const backward = key === 'ArrowUp' || key === prevKey;
63
+ const forward = key === 'ArrowDown' || key === nextKey;
64
+
65
+ if (index === -1) {
66
+ if (forward) {
67
+ return 0;
68
+ }
69
+
70
+ return backward ? lastIndex : -1;
71
+ }
72
+
73
+ if (backward && index > 0) {
74
+ // Up by a row, stopping at the first member
75
+ return key === 'ArrowUp' ? Math.max(index - columnCount, 0) : index - 1;
76
+ }
77
+
78
+ if (forward && index < lastIndex) {
79
+ // Down by a row; a partial last row still gets reached
80
+ return key === 'ArrowDown' ? Math.min(index + columnCount, lastIndex) : index + 1;
81
+ }
82
+
83
+ return -1;
84
+ };
@@ -0,0 +1,90 @@
1
+ export function mirrorPosition(position: PopupPosition): PopupPosition;
2
+ export function clipRect(anchorRect: {
3
+ top: number;
4
+ left: number;
5
+ right: number;
6
+ bottom: number;
7
+ }, viewport: ViewportSize): ClippedRect;
8
+ export function flip({ position, from, to, size, room, otherRoom }: {
9
+ position: PopupPosition;
10
+ from: string;
11
+ to: string;
12
+ size: number;
13
+ room: number;
14
+ otherRoom: number;
15
+ }): {
16
+ position: PopupPosition;
17
+ cap: number | undefined;
18
+ };
19
+ export function calculatePopupStyle({ position: preferredPosition, anchorRect, viewport, contentWidth, contentHeight, }: {
20
+ position: PopupPosition;
21
+ anchorRect: {
22
+ top: number;
23
+ left: number;
24
+ right: number;
25
+ bottom: number;
26
+ };
27
+ viewport: ViewportSize;
28
+ contentWidth: number;
29
+ contentHeight: number;
30
+ }): PopupStyle;
31
+ export function isShallowEqual(a: Record<string, any>, b: Record<string, any>): boolean;
32
+ export type PopupStyle = {
33
+ /**
34
+ * The `inset` CSS property.
35
+ */
36
+ inset: string | undefined;
37
+ /**
38
+ * The `z-index` CSS property.
39
+ */
40
+ zIndex: number | undefined;
41
+ /**
42
+ * The `min-width` CSS property.
43
+ */
44
+ minWidth: string | undefined;
45
+ /**
46
+ * The `max-width` CSS property.
47
+ */
48
+ maxWidth: string | undefined;
49
+ /**
50
+ * The `max-height` CSS property.
51
+ */
52
+ height: string | undefined;
53
+ };
54
+ export type ViewportSize = {
55
+ /**
56
+ * Viewport width.
57
+ */
58
+ width: number;
59
+ /**
60
+ * Viewport height.
61
+ */
62
+ height: number;
63
+ };
64
+ export type ClippedRect = {
65
+ /**
66
+ * Top edge.
67
+ */
68
+ top: number;
69
+ /**
70
+ * Left edge.
71
+ */
72
+ left: number;
73
+ /**
74
+ * Right edge.
75
+ */
76
+ right: number;
77
+ /**
78
+ * Bottom edge.
79
+ */
80
+ bottom: number;
81
+ /**
82
+ * Width.
83
+ */
84
+ width: number;
85
+ /**
86
+ * Height.
87
+ */
88
+ height: number;
89
+ };
90
+ import type { PopupPosition } from '../typedefs.js';
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Pure helpers that work out where a popup goes and how large it can be, given the anchor’s rect
3
+ * and the viewport size. They don’t touch the DOM, so they can be tested on their own.
4
+ */
5
+
6
+ /**
7
+ * @import { PopupPosition } from '../typedefs.js';
8
+ */
9
+
10
+ /**
11
+ * @typedef {object} PopupStyle
12
+ * @property {string | undefined} inset The `inset` CSS property.
13
+ * @property {number | undefined} zIndex The `z-index` CSS property.
14
+ * @property {string | undefined} minWidth The `min-width` CSS property.
15
+ * @property {string | undefined} maxWidth The `max-width` CSS property.
16
+ * @property {string | undefined} height The `max-height` CSS property.
17
+ */
18
+
19
+ /**
20
+ * @typedef {object} ViewportSize
21
+ * @property {number} width Viewport width.
22
+ * @property {number} height Viewport height.
23
+ */
24
+
25
+ /**
26
+ * @typedef {object} ClippedRect
27
+ * @property {number} top Top edge.
28
+ * @property {number} left Left edge.
29
+ * @property {number} right Right edge.
30
+ * @property {number} bottom Bottom edge.
31
+ * @property {number} width Width.
32
+ * @property {number} height Height.
33
+ */
34
+
35
+ /**
36
+ * Mirror a popup position for an RTL document, so `-left` becomes `-right`, `left-` becomes
37
+ * `right-` and vice versa. The positions are written for LTR documents.
38
+ * @internal
39
+ * @param {PopupPosition} position Original position.
40
+ * @returns {PopupPosition} Mirrored position.
41
+ * @todo Rename `PopupPosition` enums to be direction-agnostic.
42
+ */
43
+ export const mirrorPosition = (position) => {
44
+ let mirrored = position;
45
+
46
+ if (mirrored.endsWith('-left')) {
47
+ mirrored = /** @type {PopupPosition} */ (mirrored.replace('-left', '-right'));
48
+ } else if (mirrored.endsWith('-right')) {
49
+ mirrored = /** @type {PopupPosition} */ (mirrored.replace('-right', '-left'));
50
+ }
51
+
52
+ if (mirrored.startsWith('left-')) {
53
+ mirrored = /** @type {PopupPosition} */ (mirrored.replace('left-', 'right-'));
54
+ } else if (mirrored.startsWith('right-')) {
55
+ mirrored = /** @type {PopupPosition} */ (mirrored.replace('right-', 'left-'));
56
+ }
57
+
58
+ return mirrored;
59
+ };
60
+
61
+ /**
62
+ * Clip the anchor’s rect to the viewport, mirroring what `IntersectionObserver` used to report as
63
+ * `intersectionRect`.
64
+ * @internal
65
+ * @param {{ top: number, left: number, right: number, bottom: number }} anchorRect Anchor’s
66
+ * bounding rect.
67
+ * @param {ViewportSize} viewport Viewport size.
68
+ * @returns {ClippedRect} Clipped rect.
69
+ */
70
+ export const clipRect = (anchorRect, viewport) => {
71
+ const top = Math.max(anchorRect.top, 0);
72
+ const left = Math.max(anchorRect.left, 0);
73
+ const right = Math.min(anchorRect.right, viewport.width);
74
+ const bottom = Math.min(anchorRect.bottom, viewport.height);
75
+
76
+ return {
77
+ top,
78
+ left,
79
+ right,
80
+ bottom,
81
+ width: Math.max(0, right - left),
82
+ height: Math.max(0, bottom - top),
83
+ };
84
+ };
85
+
86
+ /**
87
+ * Move a popup to the opposite side of its anchor when the content doesn’t fit on the current side
88
+ * and the opposite side has more room. When the content doesn’t fit, the popup is also capped by
89
+ * the room on the side it ends up on, so it scrolls instead of running past the viewport.
90
+ * @internal
91
+ * @param {object} args Arguments.
92
+ * @param {PopupPosition} args.position Current position.
93
+ * @param {string} args.from Part of the position denoting the current side, e.g. `bottom-`.
94
+ * @param {string} args.to Part of the position denoting the opposite side, e.g. `top-`.
95
+ * @param {number} args.size Content size along the axis.
96
+ * @param {number} args.room Room on the current side.
97
+ * @param {number} args.otherRoom Room on the opposite side.
98
+ * @returns {{ position: PopupPosition, cap: number | undefined }} New position, and the size limit
99
+ * if the content doesn’t fit, `undefined` otherwise.
100
+ */
101
+ export const flip = ({ position, from, to, size, room, otherRoom }) => {
102
+ if (size <= room) {
103
+ return { position, cap: undefined };
104
+ }
105
+
106
+ if (otherRoom > room) {
107
+ return { position: /** @type {PopupPosition} */ (position.replace(from, to)), cap: otherRoom };
108
+ }
109
+
110
+ return { position, cap: room };
111
+ };
112
+
113
+ /**
114
+ * Calculate the popup’s inset and size from the anchor’s rect, the viewport size and the content’s
115
+ * natural size. The position may be altered if the space is limited.
116
+ * @internal
117
+ * @param {object} args Arguments.
118
+ * @param {PopupPosition} args.position Preferred position, already mirrored for RTL if needed.
119
+ * @param {{ top: number, left: number, right: number, bottom: number }} args.anchorRect Anchor’s
120
+ * bounding rect.
121
+ * @param {ViewportSize} args.viewport Viewport size.
122
+ * @param {number} args.contentWidth Content’s natural width.
123
+ * @param {number} args.contentHeight Content’s natural height.
124
+ * @returns {PopupStyle} Popup style.
125
+ */
126
+ export const calculatePopupStyle = ({
127
+ position: preferredPosition,
128
+ anchorRect,
129
+ viewport,
130
+ contentWidth,
131
+ contentHeight,
132
+ }) => {
133
+ const rect = clipRect(anchorRect, viewport);
134
+ const topMargin = rect.top - 8;
135
+ const bottomMargin = viewport.height - rect.bottom - 8;
136
+ // A popup that opens beside its anchor is aligned with one of the anchor’s edges and extends
137
+ // across it, so its room is measured from that edge, not from the opposite one a dropdown
138
+ // hangs off
139
+ const leftMargin = rect.left - 8;
140
+ const rightMargin = viewport.width - rect.right - 8;
141
+ const downwardMargin = viewport.height - rect.top - 8;
142
+ const upwardMargin = rect.bottom - 8;
143
+ let position = preferredPosition;
144
+ /** @type {number | undefined} */
145
+ let height;
146
+
147
+ /**
148
+ * Apply {@link flip} vertically, keeping any height limit already set if the content fits.
149
+ * @param {string} from Current side.
150
+ * @param {string} to Opposite side.
151
+ * @param {number} room Room on the current side.
152
+ * @param {number} otherRoom Room on the opposite side.
153
+ */
154
+ const flipVertically = (from, to, room, otherRoom) => {
155
+ const result = flip({ position, from, to, size: contentHeight, room, otherRoom });
156
+
157
+ position = result.position;
158
+ height = result.cap ?? height;
159
+ };
160
+
161
+ // Alter the position if the space is limited
162
+ if (position.startsWith('bottom-')) {
163
+ flipVertically('bottom-', 'top-', bottomMargin, topMargin);
164
+ } else if (position.startsWith('top-')) {
165
+ flipVertically('top-', 'bottom-', topMargin, bottomMargin);
166
+ }
167
+
168
+ // Only a popup that opens beside its anchor carries a `-top`/`-bottom` suffix, and it grows
169
+ // away from the edge it’s aligned with: down from the anchor’s top, or up from its bottom. It
170
+ // gets the same treatment as the dropdown above — align with the other edge when the content
171
+ // doesn’t fit and that edge has more room, and cap it either way, so a long submenu scrolls
172
+ // instead of running past the viewport
173
+ if (position.endsWith('-top')) {
174
+ flipVertically('-top', '-bottom', downwardMargin, upwardMargin);
175
+ } else if (position.endsWith('-bottom')) {
176
+ flipVertically('-bottom', '-top', upwardMargin, downwardMargin);
177
+ }
178
+
179
+ // If the popup overflows the viewport, change the position
180
+ if (position.endsWith('-left')) {
181
+ if (rect.left + contentWidth > viewport.width - 8) {
182
+ position = /** @type {PopupPosition} */ (position.replace('-left', '-right'));
183
+ }
184
+ }
185
+
186
+ if (position.endsWith('-right')) {
187
+ if (rect.right - contentWidth < 8) {
188
+ position = /** @type {PopupPosition} */ (position.replace('-right', '-left'));
189
+ }
190
+ }
191
+
192
+ // The two checks above align a dropdown that opens below or above its anchor, so neither
193
+ // covers a popup that opens beside one — a submenu — running off the edge it opens towards.
194
+ // That gets the same treatment as the vertical flip above: switch to the other side, but only
195
+ // when it has more room, so a submenu with nowhere to go stays where the caller put it. The
196
+ // width isn’t capped here; that’s done with `maxWidth` below.
197
+ if (position.startsWith('right-')) {
198
+ ({ position } = flip({
199
+ position,
200
+ from: 'right-',
201
+ to: 'left-',
202
+ size: contentWidth,
203
+ room: rightMargin,
204
+ otherRoom: leftMargin,
205
+ }));
206
+ } else if (position.startsWith('left-')) {
207
+ ({ position } = flip({
208
+ position,
209
+ from: 'left-',
210
+ to: 'right-',
211
+ size: contentWidth,
212
+ room: leftMargin,
213
+ otherRoom: rightMargin,
214
+ }));
215
+ }
216
+
217
+ const top = position.startsWith('bottom-')
218
+ ? `${Math.round(rect.bottom)}px`
219
+ : position.endsWith('-top')
220
+ ? `${Math.round(rect.top)}px`
221
+ : 'auto';
222
+
223
+ const right = position.startsWith('left-')
224
+ ? `${Math.round(viewport.width - rect.left)}px`
225
+ : position.endsWith('-right')
226
+ ? `${Math.round(viewport.width - rect.right)}px`
227
+ : 'auto';
228
+
229
+ const bottom = position.startsWith('top-')
230
+ ? `${Math.round(viewport.height - rect.top)}px`
231
+ : position.endsWith('-bottom')
232
+ ? `${Math.round(viewport.height - rect.bottom)}px`
233
+ : 'auto';
234
+
235
+ const left = position.startsWith('right-')
236
+ ? `${Math.round(rect.right)}px`
237
+ : position.endsWith('-left')
238
+ ? `${Math.round(rect.left)}px`
239
+ : 'auto';
240
+
241
+ return {
242
+ inset: [top, right, bottom, left].join(' '),
243
+ zIndex: 1000,
244
+ minWidth: `${Math.round(rect.width)}px`,
245
+ // A popup opening beside the anchor is capped by the room on that side, not by the width the
246
+ // anchor’s own edges leave, which is what the two dropdown cases below measure
247
+ maxWidth: position.startsWith('right-')
248
+ ? `${Math.round(rightMargin)}px`
249
+ : position.startsWith('left-')
250
+ ? `${Math.round(leftMargin)}px`
251
+ : position.endsWith('-left')
252
+ ? `${Math.round(viewport.width - rect.left - 8)}px`
253
+ : `${Math.round(rect.right - 8)}px`,
254
+ // `undefined` removes the `max-height` the popup is given, letting it take its natural size
255
+ // again. `auto` would look equivalent but isn’t a valid `max-height`, so the browser would
256
+ // drop it and leave whatever limit was applied last in place.
257
+ height: height ? `${Math.round(height)}px` : undefined,
258
+ };
259
+ };
260
+
261
+ /**
262
+ * Check if two objects have the same keys with strictly equal values.
263
+ * @internal
264
+ * @param {Record<string, any>} a One object.
265
+ * @param {Record<string, any>} b Another object.
266
+ * @returns {boolean} Whether the objects are shallowly equal.
267
+ */
268
+ export const isShallowEqual = (a, b) => {
269
+ const keys = Object.keys(a);
270
+
271
+ return (
272
+ keys.length === Object.keys(b).length &&
273
+ keys.every((key) => Object.hasOwn(b, key) && a[key] === b[key])
274
+ );
275
+ };
@@ -1,6 +1,8 @@
1
- export function activatePopup(...args: any[]): Popup;
1
+ export function activatePopup(anchorElement: HTMLElement, popupElement: HTMLDialogElement | undefined, position: PopupPosition, positionBaseElement?: HTMLElement | undefined): Popup;
2
+ import type { PopupPosition } from '../typedefs.js';
2
3
  /**
3
- * @import { PopupPosition } from '../typedefs';
4
+ * @import { PopupPosition } from '../typedefs.js';
5
+ * @import { PopupStyle } from './popup-position.js';
4
6
  */
5
7
  /**
6
8
  * Implement the popup handler.
@@ -10,14 +12,14 @@ declare class Popup {
10
12
  * Initialize a new `Popup` instance. Note that the `popupElement` is optional, because the
11
13
  * element is typically mounted only while the popup is open. Use {@link attachPopupElement} to
12
14
  * provide it later.
13
- * @param {HTMLButtonElement} anchorElement `<button>` element that triggers the popup.
15
+ * @param {HTMLElement} anchorElement Element that triggers the popup, typically a `<button>`.
14
16
  * @param {HTMLDialogElement | undefined} popupElement `<dialog>` element to be used for the
15
17
  * popup, if it’s already in the DOM tree.
16
18
  * @param {PopupPosition} position Where to show the popup content.
17
19
  * @param {HTMLElement} [positionBaseElement] The base element of the `position`. If omitted, this
18
20
  * will be the `anchorElement`.
19
21
  */
20
- constructor(anchorElement: HTMLButtonElement, popupElement: HTMLDialogElement | undefined, position: PopupPosition, positionBaseElement?: HTMLElement);
22
+ constructor(anchorElement: HTMLElement, popupElement: HTMLDialogElement | undefined, position: PopupPosition, positionBaseElement?: HTMLElement);
21
23
  /**
22
24
  * Open or close the popup, running side effects synchronously.
23
25
  * @param {boolean} value `true` to open, `false` to close.
@@ -28,13 +30,7 @@ declare class Popup {
28
30
  * @returns {boolean} `true` if the popup is open.
29
31
  */
30
32
  get open(): boolean;
31
- style: {
32
- inset: string | undefined;
33
- zIndex: number | undefined;
34
- minWidth: string | undefined;
35
- maxWidth: string | undefined;
36
- height: string | undefined;
37
- };
33
+ style: PopupStyle;
38
34
  /**
39
35
  * A reference to the `<dialog>` element used for the popup, which also serves as the backdrop.
40
36
  * This is `undefined` while the element is not in the DOM tree, which is the case for a closed
@@ -49,13 +45,17 @@ declare class Popup {
49
45
  * @type {HTMLElement | undefined}
50
46
  */
51
47
  contentElement: HTMLElement | undefined;
52
- anchorElement: HTMLButtonElement;
48
+ /**
49
+ * ID of the animation frame requested to recalculate the position after a resize.
50
+ * @type {number}
51
+ */
52
+ _rafId: number;
53
+ anchorElement: HTMLElement;
53
54
  position: PopupPosition;
54
55
  positionBaseElement: HTMLElement;
55
56
  id: string;
56
57
  intersectionObserver: IntersectionObserver;
57
58
  resizeObserver: ResizeObserver;
58
- _rafId: number;
59
59
  viewportResizeObserver: ResizeObserver;
60
60
  /**
61
61
  * Attach the `<dialog>` element used for the popup. This is called every time the element is
@@ -67,9 +67,8 @@ declare class Popup {
67
67
  */
68
68
  attachPopupElement(popupElement: HTMLDialogElement, contentElement?: HTMLElement): void;
69
69
  /**
70
- * Detach the `<dialog>` element, typically because it’s being unmounted. The `aria-controls`
71
- * attribute on the anchor is left to the {@link open} setter, which removes it once the closing
72
- * animation is complete.
70
+ * Detach the `<dialog>` element, typically because it’s being unmounted. The anchor’s
71
+ * `aria-controls` attribute is removed as well, since the content is leaving the DOM tree.
73
72
  */
74
73
  detachPopupElement(): void;
75
74
  /**
@@ -103,5 +102,5 @@ declare class Popup {
103
102
  destroy(): void;
104
103
  #private;
105
104
  }
106
- import type { PopupPosition } from '../typedefs';
105
+ import type { PopupStyle } from './popup-position.js';
107
106
  export {};