@sveltia/ui 0.67.2 → 0.68.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.
- package/dist/components/text-editor/shiki/generated.d.ts +1 -1
- package/dist/components/text-editor/shiki/generated.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/locales/de.yaml +116 -0
- package/dist/locales/sv.yaml +3 -0
- package/dist/services/popup.svelte.d.ts +9 -3
- package/dist/services/popup.svelte.js +149 -117
- package/package.json +6 -6
|
@@ -7,7 +7,7 @@ export const SHIKI_VERSION: "4.4.3";
|
|
|
7
7
|
/**
|
|
8
8
|
* Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
|
|
9
9
|
*/
|
|
10
|
-
export const UI_VERSION: "0.
|
|
10
|
+
export const UI_VERSION: "0.68.1";
|
|
11
11
|
/**
|
|
12
12
|
* Available syntax highlighting languages, sorted by display name.
|
|
13
13
|
* @type {{ id: string, name: string, aliases?: string[] }[]}
|
|
@@ -10,7 +10,7 @@ export const SHIKI_VERSION = "4.4.3";
|
|
|
10
10
|
/**
|
|
11
11
|
* Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
|
|
12
12
|
*/
|
|
13
|
-
export const UI_VERSION = "0.
|
|
13
|
+
export const UI_VERSION = "0.68.1";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Available syntax highlighting languages, sorted by display name.
|
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ export { default as EmptyState } from "./components/util/empty-state.svelte";
|
|
|
80
80
|
export { default as Group } from "./components/util/group.svelte";
|
|
81
81
|
export { default as Modal } from "./components/util/modal.svelte";
|
|
82
82
|
export { default as Placeholder } from "./components/util/placeholder.svelte";
|
|
83
|
+
export { default as Popup } from "./components/util/popup.svelte";
|
|
83
84
|
export { default as VisibilityObserver } from "./components/util/visibility-observer.svelte";
|
|
84
85
|
export { loadCodeHighlighter } from "./components/text-editor/core.js";
|
|
85
86
|
export { setCodeHighlighterCacheEnabled } from "./components/text-editor/shiki/cache.js";
|
package/dist/index.js
CHANGED
|
@@ -80,6 +80,7 @@ export { default as EmptyState } from './components/util/empty-state.svelte';
|
|
|
80
80
|
export { default as Group } from './components/util/group.svelte';
|
|
81
81
|
export { default as Modal } from './components/util/modal.svelte';
|
|
82
82
|
export { default as Placeholder } from './components/util/placeholder.svelte';
|
|
83
|
+
export { default as Popup } from './components/util/popup.svelte';
|
|
83
84
|
export { default as VisibilityObserver } from './components/util/visibility-observer.svelte';
|
|
84
85
|
export { loadCodeHighlighter } from './components/text-editor/core.js';
|
|
85
86
|
export { setCodeHighlighterCacheEnabled } from './components/text-editor/shiki/cache.js';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Dialog action button labels
|
|
2
|
+
ok: OK
|
|
3
|
+
cancel: Abbrechen
|
|
4
|
+
close: Schließen
|
|
5
|
+
|
|
6
|
+
# Button to clear content, e.g. search input or calendar date
|
|
7
|
+
clear: Leeren
|
|
8
|
+
|
|
9
|
+
# Text editor dialog buttons for link/image insertion
|
|
10
|
+
insert: Einfügen
|
|
11
|
+
update: Aktualisieren
|
|
12
|
+
remove: Entfernen
|
|
13
|
+
|
|
14
|
+
# Combobox dropdown toggle button aria-labels
|
|
15
|
+
collapse: Zuklappen
|
|
16
|
+
expand: Aufklappen
|
|
17
|
+
|
|
18
|
+
# Infobar/toast dismiss button label
|
|
19
|
+
dismiss: Ausblenden
|
|
20
|
+
|
|
21
|
+
# Emoji suggestion listbox aria-label, shared by the text fields and the text editor
|
|
22
|
+
emoji_suggestions: Emoji-Vorschläge
|
|
23
|
+
|
|
24
|
+
# Calendar component
|
|
25
|
+
calendar:
|
|
26
|
+
# View switcher group aria-labels and navigation button aria-labels
|
|
27
|
+
year: Jahr
|
|
28
|
+
previous_decade: Vorheriges Jahrzehnt
|
|
29
|
+
next_decade: Nächstes Jahrzehnt
|
|
30
|
+
month: Monat
|
|
31
|
+
previous_month: Vorheriger Monat
|
|
32
|
+
next_month: Nächster Monat
|
|
33
|
+
# “Today” button label in footer
|
|
34
|
+
today: Heute
|
|
35
|
+
|
|
36
|
+
# Split button component
|
|
37
|
+
split_button:
|
|
38
|
+
# Wrapper aria-label, e.g. “Save Options” when primary button is “Save”
|
|
39
|
+
x_options: 'Optionen für {$name}'
|
|
40
|
+
# Dropdown toggle aria-label
|
|
41
|
+
more_options: Weitere Optionen
|
|
42
|
+
|
|
43
|
+
# Combobox component
|
|
44
|
+
combobox:
|
|
45
|
+
# Placeholder when no option is selected
|
|
46
|
+
select_an_option: Option auswählen…
|
|
47
|
+
# Filter input aria-label
|
|
48
|
+
filter_options: Optionen filtern
|
|
49
|
+
# Empty state message when filter has no matches
|
|
50
|
+
no_matching_options: Keine passenden Optionen gefunden
|
|
51
|
+
|
|
52
|
+
# Number input component
|
|
53
|
+
number_input:
|
|
54
|
+
# Spin button aria-labels for incrementing/decrementing the number value
|
|
55
|
+
increase: Erhöhen
|
|
56
|
+
decrease: Verringern
|
|
57
|
+
|
|
58
|
+
# Password input component
|
|
59
|
+
password_input:
|
|
60
|
+
# Visibility toggle button aria-labels
|
|
61
|
+
show_password: Passwort anzeigen
|
|
62
|
+
hide_password: Passwort ausblenden
|
|
63
|
+
|
|
64
|
+
# Secret input component (API keys, tokens, etc.)
|
|
65
|
+
secret_input:
|
|
66
|
+
# Visibility toggle button aria-labels
|
|
67
|
+
show_secret: Geheimnis anzeigen
|
|
68
|
+
hide_secret: Geheimnis ausblenden
|
|
69
|
+
|
|
70
|
+
# Select tags component (multi-select)
|
|
71
|
+
select_tags:
|
|
72
|
+
# Listbox aria-label
|
|
73
|
+
selected_options: Ausgewählte Optionen
|
|
74
|
+
# Remove button aria-label, e.g. “Remove Option 1”
|
|
75
|
+
remove_x: '{$name} entfernen'
|
|
76
|
+
|
|
77
|
+
# Text editor component
|
|
78
|
+
text_editor:
|
|
79
|
+
# Toolbar aria-labels
|
|
80
|
+
text_editor: Texteditor
|
|
81
|
+
code_editor: Code-Editor
|
|
82
|
+
# Block style menu button and menu aria-labels
|
|
83
|
+
text_style_options: Textstil-Optionen
|
|
84
|
+
show_text_style_options: Textstil-Optionen anzeigen
|
|
85
|
+
# Block style menu items
|
|
86
|
+
paragraph: Absatz
|
|
87
|
+
heading_1: Überschrift 1
|
|
88
|
+
heading_2: Überschrift 2
|
|
89
|
+
heading_3: Überschrift 3
|
|
90
|
+
heading_4: Überschrift 4
|
|
91
|
+
heading_5: Überschrift 5
|
|
92
|
+
heading_6: Überschrift 6
|
|
93
|
+
bulleted_list: Aufzählungsliste
|
|
94
|
+
numbered_list: Nummerierte Liste
|
|
95
|
+
blockquote: Zitat
|
|
96
|
+
code_block: Code-Block
|
|
97
|
+
# Inline style button aria-labels
|
|
98
|
+
bold: Fett
|
|
99
|
+
italic: Kursiv
|
|
100
|
+
strikethrough: Durchgestrichen
|
|
101
|
+
code: Code
|
|
102
|
+
link: Link
|
|
103
|
+
# Link dialog title
|
|
104
|
+
insert_link: Link einfügen
|
|
105
|
+
update_link: Link aktualisieren
|
|
106
|
+
# Link dialog input field labels
|
|
107
|
+
text: Text
|
|
108
|
+
url: URL
|
|
109
|
+
# Markdown mode toggle button aria-label
|
|
110
|
+
edit_in_markdown: In Markdown bearbeiten
|
|
111
|
+
# Error message when rich text conversion fails
|
|
112
|
+
converter_error: Der Rich-Text-Modus konnte nicht aktiviert werden. Bitte verwenden Sie stattdessen den Klartext-Editor.
|
|
113
|
+
# Language selector aria-label
|
|
114
|
+
language: Sprache
|
|
115
|
+
# Plain text mode option in language selector
|
|
116
|
+
plain_text: Klartext
|
package/dist/locales/sv.yaml
CHANGED
|
@@ -18,6 +18,9 @@ expand: Fäll ut
|
|
|
18
18
|
# Infobar/toast dismiss button label
|
|
19
19
|
dismiss: Avfärda
|
|
20
20
|
|
|
21
|
+
# Emoji suggestion listbox aria-label, shared by the text fields and the text editor
|
|
22
|
+
emoji_suggestions: Emojiförslag
|
|
23
|
+
|
|
21
24
|
# Calendar component
|
|
22
25
|
calendar:
|
|
23
26
|
# View switcher group aria-labels and navigation button aria-labels
|
|
@@ -35,7 +35,6 @@ declare class Popup {
|
|
|
35
35
|
maxWidth: string | undefined;
|
|
36
36
|
height: string | undefined;
|
|
37
37
|
};
|
|
38
|
-
observer: IntersectionObserver;
|
|
39
38
|
/**
|
|
40
39
|
* A reference to the `<dialog>` element used for the popup, which also serves as the backdrop.
|
|
41
40
|
* This is `undefined` while the element is not in the DOM tree, which is the case for a closed
|
|
@@ -57,6 +56,7 @@ declare class Popup {
|
|
|
57
56
|
intersectionObserver: IntersectionObserver;
|
|
58
57
|
resizeObserver: ResizeObserver;
|
|
59
58
|
_rafId: number;
|
|
59
|
+
viewportResizeObserver: ResizeObserver;
|
|
60
60
|
/**
|
|
61
61
|
* Attach the `<dialog>` element used for the popup. This is called every time the element is
|
|
62
62
|
* mounted, which happens on each open.
|
|
@@ -83,8 +83,14 @@ declare class Popup {
|
|
|
83
83
|
*/
|
|
84
84
|
get isReadOnly(): boolean;
|
|
85
85
|
/**
|
|
86
|
-
* Check the position of the anchor element
|
|
87
|
-
*
|
|
86
|
+
* Check the position of the anchor element and, if the popup is mounted, recalculate and apply
|
|
87
|
+
* its inset and size. This is a no-op while the popup element is not in the DOM tree; the caller
|
|
88
|
+
* is expected to call this again once the element is attached.
|
|
89
|
+
*
|
|
90
|
+
* This measures the anchor synchronously with `getBoundingClientRect()` rather than reacting to
|
|
91
|
+
* an `IntersectionObserver`, whose delivery isn’t guaranteed to be timely or frame-aligned the
|
|
92
|
+
* way `ResizeObserver`’s is: a stale computation could arrive after a fresher one and silently
|
|
93
|
+
* overwrite it, occasionally leaving the popup mispositioned after a resize.
|
|
88
94
|
*/
|
|
89
95
|
checkPosition(): void;
|
|
90
96
|
/**
|
|
@@ -63,118 +63,6 @@ class Popup {
|
|
|
63
63
|
}),
|
|
64
64
|
);
|
|
65
65
|
|
|
66
|
-
observer = new IntersectionObserver((entries) => {
|
|
67
|
-
entries.forEach(({ intersectionRect, rootBounds }) => {
|
|
68
|
-
if (!intersectionRect || !rootBounds) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Use the tracked content element rather than searching the popup element, which for a nested
|
|
73
|
-
// popup is the shared parent `<dialog>` and would yield the parent’s content
|
|
74
|
-
const content = /** @type {HTMLElement | null} */ (
|
|
75
|
-
this.contentElement ?? this.popupElement?.querySelector('.content') ?? null
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// The content is not in the DOM tree yet; `checkPosition()` will be called again once the
|
|
79
|
-
// popup element is attached
|
|
80
|
-
if (!content) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const { scrollHeight: contentHeight, scrollWidth: contentWidth } = content;
|
|
85
|
-
const topMargin = intersectionRect.top - 8;
|
|
86
|
-
const bottomMargin = rootBounds.height - intersectionRect.bottom - 8;
|
|
87
|
-
let { position } = this;
|
|
88
|
-
let height;
|
|
89
|
-
|
|
90
|
-
// Normalize RTL-friendly positions to LTR for LTR documents
|
|
91
|
-
// @todo Rename `PopupPosition` enums to be direction-agnostic
|
|
92
|
-
if (document.dir === 'rtl') {
|
|
93
|
-
if (position.endsWith('-left')) {
|
|
94
|
-
position = /** @type {PopupPosition} */ (position.replace('-left', '-right'));
|
|
95
|
-
} else if (position.endsWith('-right')) {
|
|
96
|
-
position = /** @type {PopupPosition} */ (position.replace('-right', '-left'));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (position.startsWith('left-')) {
|
|
100
|
-
position = /** @type {PopupPosition} */ (position.replace('left-', 'right-'));
|
|
101
|
-
} else if (position.startsWith('right-')) {
|
|
102
|
-
position = /** @type {PopupPosition} */ (position.replace('right-', 'left-'));
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Alter the position if the space is limited
|
|
107
|
-
// @todo Handle more overflow cases
|
|
108
|
-
if (position.startsWith('bottom-')) {
|
|
109
|
-
if (contentHeight > bottomMargin) {
|
|
110
|
-
if (topMargin > bottomMargin) {
|
|
111
|
-
position = /** @type {PopupPosition} */ (position.replace('bottom-', 'top-'));
|
|
112
|
-
height = topMargin;
|
|
113
|
-
} else {
|
|
114
|
-
height = bottomMargin;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// If the popup overflows the viewport, change the position
|
|
120
|
-
if (position.endsWith('-left')) {
|
|
121
|
-
if (intersectionRect.left + contentWidth > rootBounds.width - 8) {
|
|
122
|
-
position = /** @type {PopupPosition} */ (position.replace('-left', '-right'));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (position.endsWith('-right')) {
|
|
127
|
-
if (intersectionRect.right - contentWidth < 8) {
|
|
128
|
-
position = /** @type {PopupPosition} */ (position.replace('-right', '-left'));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const top = position.startsWith('bottom-')
|
|
133
|
-
? `${Math.round(intersectionRect.bottom)}px`
|
|
134
|
-
: position.endsWith('-top')
|
|
135
|
-
? `${Math.round(intersectionRect.top)}px`
|
|
136
|
-
: 'auto';
|
|
137
|
-
|
|
138
|
-
const right = position.startsWith('left-')
|
|
139
|
-
? `${Math.round(rootBounds.width - intersectionRect.left)}px`
|
|
140
|
-
: position.endsWith('-right')
|
|
141
|
-
? `${Math.round(rootBounds.width - intersectionRect.right)}px`
|
|
142
|
-
: 'auto';
|
|
143
|
-
|
|
144
|
-
const bottom = position.startsWith('top-')
|
|
145
|
-
? `${Math.round(rootBounds.height - intersectionRect.top)}px`
|
|
146
|
-
: position.endsWith('-bottom')
|
|
147
|
-
? `${Math.round(rootBounds.height - intersectionRect.bottom)}px`
|
|
148
|
-
: 'auto';
|
|
149
|
-
|
|
150
|
-
const left = position.startsWith('right-')
|
|
151
|
-
? `${Math.round(intersectionRect.right)}px`
|
|
152
|
-
: position.endsWith('-left')
|
|
153
|
-
? `${Math.round(intersectionRect.left)}px`
|
|
154
|
-
: 'auto';
|
|
155
|
-
|
|
156
|
-
const style = {
|
|
157
|
-
inset: [top, right, bottom, left].join(' '),
|
|
158
|
-
zIndex: 1000,
|
|
159
|
-
minWidth: `${Math.round(intersectionRect.width)}px`,
|
|
160
|
-
maxWidth: position.endsWith('-left')
|
|
161
|
-
? `${Math.round(rootBounds.width - intersectionRect.left - 8)}px`
|
|
162
|
-
: `${Math.round(intersectionRect.right - 8)}px`,
|
|
163
|
-
height: height ? `${Math.round(height)}px` : 'auto',
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
style.inset !== this.style.inset ||
|
|
168
|
-
style.zIndex !== this.style.zIndex ||
|
|
169
|
-
style.minWidth !== this.style.minWidth ||
|
|
170
|
-
style.maxWidth !== this.style.maxWidth ||
|
|
171
|
-
style.height !== this.style.height
|
|
172
|
-
) {
|
|
173
|
-
this.style = style;
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
66
|
/**
|
|
179
67
|
* A reference to the `<dialog>` element used for the popup, which also serves as the backdrop.
|
|
180
68
|
* This is `undefined` while the element is not in the DOM tree, which is the case for a closed
|
|
@@ -253,6 +141,21 @@ class Popup {
|
|
|
253
141
|
});
|
|
254
142
|
this.resizeObserver.observe(this.positionBaseElement);
|
|
255
143
|
|
|
144
|
+
// Recalculate the position when the viewport is resized. This is needed in addition to the
|
|
145
|
+
// `ResizeObserver` above, which only reacts to the anchor’s own size changing, not to it
|
|
146
|
+
// simply moving — which happens, for example, when a dialog containing the anchor is
|
|
147
|
+
// re-centered as the viewport is resized. This deliberately avoids the `resize` event on
|
|
148
|
+
// `window`, which isn’t guaranteed to fire for every layout-affecting viewport change across
|
|
149
|
+
// browsers; observing this popup’s own `<dialog>` element instead is reliable, because that
|
|
150
|
+
// element is always sized to exactly `100dvw` × `100dvh` (see `Modal.svelte`), so its box
|
|
151
|
+
// changes precisely when the viewport does — independent of the host app’s own CSS, unlike
|
|
152
|
+
// watching `document.body` or `document.documentElement` would be.
|
|
153
|
+
this.viewportResizeObserver = new ResizeObserver(() => {
|
|
154
|
+
if (this.open) {
|
|
155
|
+
this.checkPosition();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
256
159
|
if (popupElement) {
|
|
257
160
|
this.attachPopupElement(popupElement);
|
|
258
161
|
}
|
|
@@ -274,6 +177,7 @@ class Popup {
|
|
|
274
177
|
this.detachPopupElement();
|
|
275
178
|
this.popupElement = popupElement;
|
|
276
179
|
this.contentElement = contentElement;
|
|
180
|
+
this.viewportResizeObserver.observe(popupElement);
|
|
277
181
|
|
|
278
182
|
// Identify the popup by the element that actually holds its content. Labelling the `<dialog>`
|
|
279
183
|
// would be wrong for a nested popup, which shares its parent’s: it would overwrite the parent’s
|
|
@@ -327,6 +231,10 @@ class Popup {
|
|
|
327
231
|
this.#removeEventListeners?.();
|
|
328
232
|
this.#removeEventListeners = undefined;
|
|
329
233
|
|
|
234
|
+
if (this.popupElement) {
|
|
235
|
+
this.viewportResizeObserver.unobserve(this.popupElement);
|
|
236
|
+
}
|
|
237
|
+
|
|
330
238
|
// The content is leaving the DOM tree, so the anchor must stop referencing it. Only clear a
|
|
331
239
|
// reference this popup owns; the consumer may have pointed the anchor somewhere else.
|
|
332
240
|
if (this.anchorElement.getAttribute('aria-controls') === this.id) {
|
|
@@ -354,16 +262,140 @@ class Popup {
|
|
|
354
262
|
}
|
|
355
263
|
|
|
356
264
|
/**
|
|
357
|
-
* Check the position of the anchor element
|
|
358
|
-
*
|
|
265
|
+
* Check the position of the anchor element and, if the popup is mounted, recalculate and apply
|
|
266
|
+
* its inset and size. This is a no-op while the popup element is not in the DOM tree; the caller
|
|
267
|
+
* is expected to call this again once the element is attached.
|
|
268
|
+
*
|
|
269
|
+
* This measures the anchor synchronously with `getBoundingClientRect()` rather than reacting to
|
|
270
|
+
* an `IntersectionObserver`, whose delivery isn’t guaranteed to be timely or frame-aligned the
|
|
271
|
+
* way `ResizeObserver`’s is: a stale computation could arrive after a fresher one and silently
|
|
272
|
+
* overwrite it, occasionally leaving the popup mispositioned after a resize.
|
|
359
273
|
*/
|
|
360
274
|
checkPosition() {
|
|
361
275
|
if (!this.popupElement) {
|
|
362
276
|
return;
|
|
363
277
|
}
|
|
364
278
|
|
|
365
|
-
|
|
366
|
-
|
|
279
|
+
// Use the tracked content element rather than searching the popup element, which for a nested
|
|
280
|
+
// popup is the shared parent `<dialog>` and would yield the parent’s content
|
|
281
|
+
const content = /** @type {HTMLElement | null} */ (
|
|
282
|
+
this.contentElement ?? this.popupElement?.querySelector('.content') ?? null
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// The content is not in the DOM tree yet; `checkPosition()` will be called again once the
|
|
286
|
+
// popup element is attached
|
|
287
|
+
if (!content) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const anchorRect = this.positionBaseElement.getBoundingClientRect();
|
|
292
|
+
const rootBounds = { width: window.innerWidth, height: window.innerHeight };
|
|
293
|
+
|
|
294
|
+
// Clip the anchor’s rect to the viewport, mirroring what `IntersectionObserver` used to report
|
|
295
|
+
// as `intersectionRect`
|
|
296
|
+
const intersectionRect = {
|
|
297
|
+
top: Math.max(anchorRect.top, 0),
|
|
298
|
+
left: Math.max(anchorRect.left, 0),
|
|
299
|
+
right: Math.min(anchorRect.right, rootBounds.width),
|
|
300
|
+
bottom: Math.min(anchorRect.bottom, rootBounds.height),
|
|
301
|
+
width: 0,
|
|
302
|
+
height: 0,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
intersectionRect.width = Math.max(0, intersectionRect.right - intersectionRect.left);
|
|
306
|
+
intersectionRect.height = Math.max(0, intersectionRect.bottom - intersectionRect.top);
|
|
307
|
+
|
|
308
|
+
const { scrollHeight: contentHeight, scrollWidth: contentWidth } = content;
|
|
309
|
+
const topMargin = intersectionRect.top - 8;
|
|
310
|
+
const bottomMargin = rootBounds.height - intersectionRect.bottom - 8;
|
|
311
|
+
let { position } = this;
|
|
312
|
+
let height;
|
|
313
|
+
|
|
314
|
+
// Normalize RTL-friendly positions to LTR for LTR documents
|
|
315
|
+
// @todo Rename `PopupPosition` enums to be direction-agnostic
|
|
316
|
+
if (document.dir === 'rtl') {
|
|
317
|
+
if (position.endsWith('-left')) {
|
|
318
|
+
position = /** @type {PopupPosition} */ (position.replace('-left', '-right'));
|
|
319
|
+
} else if (position.endsWith('-right')) {
|
|
320
|
+
position = /** @type {PopupPosition} */ (position.replace('-right', '-left'));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (position.startsWith('left-')) {
|
|
324
|
+
position = /** @type {PopupPosition} */ (position.replace('left-', 'right-'));
|
|
325
|
+
} else if (position.startsWith('right-')) {
|
|
326
|
+
position = /** @type {PopupPosition} */ (position.replace('right-', 'left-'));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Alter the position if the space is limited
|
|
331
|
+
// @todo Handle more overflow cases
|
|
332
|
+
if (position.startsWith('bottom-')) {
|
|
333
|
+
if (contentHeight > bottomMargin) {
|
|
334
|
+
if (topMargin > bottomMargin) {
|
|
335
|
+
position = /** @type {PopupPosition} */ (position.replace('bottom-', 'top-'));
|
|
336
|
+
height = topMargin;
|
|
337
|
+
} else {
|
|
338
|
+
height = bottomMargin;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// If the popup overflows the viewport, change the position
|
|
344
|
+
if (position.endsWith('-left')) {
|
|
345
|
+
if (intersectionRect.left + contentWidth > rootBounds.width - 8) {
|
|
346
|
+
position = /** @type {PopupPosition} */ (position.replace('-left', '-right'));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (position.endsWith('-right')) {
|
|
351
|
+
if (intersectionRect.right - contentWidth < 8) {
|
|
352
|
+
position = /** @type {PopupPosition} */ (position.replace('-right', '-left'));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const top = position.startsWith('bottom-')
|
|
357
|
+
? `${Math.round(intersectionRect.bottom)}px`
|
|
358
|
+
: position.endsWith('-top')
|
|
359
|
+
? `${Math.round(intersectionRect.top)}px`
|
|
360
|
+
: 'auto';
|
|
361
|
+
|
|
362
|
+
const right = position.startsWith('left-')
|
|
363
|
+
? `${Math.round(rootBounds.width - intersectionRect.left)}px`
|
|
364
|
+
: position.endsWith('-right')
|
|
365
|
+
? `${Math.round(rootBounds.width - intersectionRect.right)}px`
|
|
366
|
+
: 'auto';
|
|
367
|
+
|
|
368
|
+
const bottom = position.startsWith('top-')
|
|
369
|
+
? `${Math.round(rootBounds.height - intersectionRect.top)}px`
|
|
370
|
+
: position.endsWith('-bottom')
|
|
371
|
+
? `${Math.round(rootBounds.height - intersectionRect.bottom)}px`
|
|
372
|
+
: 'auto';
|
|
373
|
+
|
|
374
|
+
const left = position.startsWith('right-')
|
|
375
|
+
? `${Math.round(intersectionRect.right)}px`
|
|
376
|
+
: position.endsWith('-left')
|
|
377
|
+
? `${Math.round(intersectionRect.left)}px`
|
|
378
|
+
: 'auto';
|
|
379
|
+
|
|
380
|
+
const style = {
|
|
381
|
+
inset: [top, right, bottom, left].join(' '),
|
|
382
|
+
zIndex: 1000,
|
|
383
|
+
minWidth: `${Math.round(intersectionRect.width)}px`,
|
|
384
|
+
maxWidth: position.endsWith('-left')
|
|
385
|
+
? `${Math.round(rootBounds.width - intersectionRect.left - 8)}px`
|
|
386
|
+
: `${Math.round(intersectionRect.right - 8)}px`,
|
|
387
|
+
height: height ? `${Math.round(height)}px` : 'auto',
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
style.inset !== this.style.inset ||
|
|
392
|
+
style.zIndex !== this.style.zIndex ||
|
|
393
|
+
style.minWidth !== this.style.minWidth ||
|
|
394
|
+
style.maxWidth !== this.style.maxWidth ||
|
|
395
|
+
style.height !== this.style.height
|
|
396
|
+
) {
|
|
397
|
+
this.style = style;
|
|
398
|
+
}
|
|
367
399
|
}
|
|
368
400
|
|
|
369
401
|
/**
|
|
@@ -390,7 +422,7 @@ class Popup {
|
|
|
390
422
|
this.detachPopupElement();
|
|
391
423
|
this.intersectionObserver?.disconnect();
|
|
392
424
|
this.resizeObserver?.disconnect();
|
|
393
|
-
this.
|
|
425
|
+
this.viewportResizeObserver?.disconnect();
|
|
394
426
|
|
|
395
427
|
if (this._rafId) {
|
|
396
428
|
cancelAnimationFrame(this._rafId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltia/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.1",
|
|
4
4
|
"description": "A collection of Svelte components and utilities for building user interfaces.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@lexical/selection": "^0.49.0",
|
|
47
47
|
"@lexical/table": "^0.49.0",
|
|
48
48
|
"@lexical/utils": "^0.49.0",
|
|
49
|
-
"@sveltia/i18n": "^1.
|
|
49
|
+
"@sveltia/i18n": "^1.3.0",
|
|
50
50
|
"@sveltia/utils": "^0.12.1",
|
|
51
51
|
"lexical": "^0.49.0"
|
|
52
52
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@sveltejs/package": "^2.5.8",
|
|
58
58
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
59
59
|
"@vitest/coverage-v8": "^4.1.11",
|
|
60
|
-
"cspell": "^10.
|
|
60
|
+
"cspell": "^10.1.1",
|
|
61
61
|
"eslint": "^9.39.5",
|
|
62
62
|
"eslint-config-airbnb-extended": "^3.2.0",
|
|
63
63
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -66,13 +66,13 @@
|
|
|
66
66
|
"eslint-plugin-package-json": "^1.7.1",
|
|
67
67
|
"eslint-plugin-svelte": "^3.23.0",
|
|
68
68
|
"globals": "^17.11.0",
|
|
69
|
-
"happy-dom": "^20.11.
|
|
70
|
-
"oxlint": "^1.
|
|
69
|
+
"happy-dom": "^20.11.8",
|
|
70
|
+
"oxlint": "^1.80.0",
|
|
71
71
|
"postcss": "^8.5.26",
|
|
72
72
|
"postcss-html": "^2.0.0",
|
|
73
73
|
"prettier": "^3.9.6",
|
|
74
74
|
"prettier-plugin-svelte": "^4.1.1",
|
|
75
|
-
"rolldown": "^1.2.
|
|
75
|
+
"rolldown": "^1.2.6",
|
|
76
76
|
"sass": "^1.103.1",
|
|
77
77
|
"shiki": "^4.4.3",
|
|
78
78
|
"stylelint": "^17.14.1",
|