@rimelight/ui 0.0.2 → 0.0.4
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/package.json +6 -3
- package/src/components/carousel/carousel-script.ts +188 -188
- package/src/components/carousel/index.ts +1 -1
- package/src/components/input-otp/InputOtpTypes.ts +6 -6
- package/src/components/radio-group/RadioGroupTypes.ts +6 -6
- package/src/components/select/SelectTypes.ts +13 -13
- package/src/components/switch/SwitchTypes.ts +6 -6
- package/src/components/toast/index.ts +1 -1
- package/src/components/toast/toast-manager.ts +234 -234
- package/src/components/toggle/ToggleTypes.ts +14 -14
- package/src/config/cookies.ts +5 -5
- package/src/config/index.ts +3 -3
- package/src/config/links.ts +4 -4
- package/src/config/site.config.ts +27 -45
- package/src/domain/cms/index.ts +1 -1
- package/src/domain/cms/types.ts +20 -20
- package/src/domain/i18n/constants.ts +21 -21
- package/src/domain/i18n/cookie-storage.ts +13 -13
- package/src/domain/i18n/country-to-locale-map.ts +67 -67
- package/src/domain/i18n/fetcher.ts +278 -268
- package/src/domain/i18n/flags.ts +16 -16
- package/src/domain/i18n/format.ts +90 -90
- package/src/domain/i18n/index.ts +10 -10
- package/src/domain/i18n/middleware/i18n.ts +161 -160
- package/src/domain/i18n/resolve-locale.ts +55 -55
- package/src/domain/i18n/schema.ts +29 -29
- package/src/domain/index.ts +4 -4
- package/src/domain/seo/constants.ts +6 -20
- package/src/domain/seo/index.ts +3 -3
- package/src/domain/seo/services/index.ts +3 -3
- package/src/domain/seo/services/llms.ts +56 -53
- package/src/domain/seo/services/robots.ts +12 -12
- package/src/domain/seo/services/sitemap.ts +56 -56
- package/src/domain/seo/utils.ts +36 -37
- package/src/domain/theme/constants.ts +6 -6
- package/src/domain/theme/index.ts +2 -2
- package/src/domain/theme/schema.ts +10 -10
- package/src/env.d.ts +49 -49
- package/src/integrations/index.ts +1 -0
- package/src/integrations/sri.ts +92 -0
- package/src/lib/component-preview.ts +73 -73
- package/src/lib/index.ts +6 -6
- package/src/lib/remark/remark-modified-time.ts +13 -13
- package/src/lib/showcase-preview-elements.ts +408 -408
- package/src/lib/showcase-renderer.ts +218 -218
- package/src/lib/showcase-utils.ts +189 -189
- package/src/lib/turnstile.ts +35 -35
- package/src/lib/utils/starwind/positioning.ts +318 -318
- package/src/locales/de/blog.json +9 -9
- package/src/locales/de/common.json +19 -19
- package/src/locales/de/errors.json +6 -6
- package/src/locales/de/home.json +20 -20
- package/src/locales/de/messages.json +8 -8
- package/src/locales/de/nav.json +13 -13
- package/src/locales/de/projects.json +9 -9
- package/src/locales/de/services.json +43 -43
- package/src/locales/en/blog.json +9 -9
- package/src/locales/en/chat.json +29 -29
- package/src/locales/en/common.json +19 -19
- package/src/locales/en/cookie_notice.json +11 -11
- package/src/locales/en/editor.json +24 -24
- package/src/locales/en/errors.json +6 -6
- package/src/locales/en/home.json +20 -20
- package/src/locales/en/messages.json +8 -8
- package/src/locales/en/nav.json +13 -13
- package/src/locales/en/projects.json +9 -9
- package/src/locales/en/services.json +43 -43
- package/src/locales/es/blog.json +9 -9
- package/src/locales/es/common.json +19 -19
- package/src/locales/es/errors.json +6 -6
- package/src/locales/es/home.json +20 -20
- package/src/locales/es/messages.json +8 -8
- package/src/locales/es/nav.json +13 -13
- package/src/locales/es/projects.json +9 -9
- package/src/locales/es/services.json +43 -43
- package/src/locales/ja/blog.json +9 -9
- package/src/locales/ja/common.json +19 -19
- package/src/locales/ja/errors.json +6 -6
- package/src/locales/ja/home.json +20 -20
- package/src/locales/ja/messages.json +8 -8
- package/src/locales/ja/nav.json +13 -13
- package/src/locales/ja/projects.json +9 -9
- package/src/locales/ja/services.json +43 -43
- package/src/locales/pt/blog.json +9 -9
- package/src/locales/pt/chat.json +29 -29
- package/src/locales/pt/common.json +19 -19
- package/src/locales/pt/cookie_notice.json +11 -11
- package/src/locales/pt/editor.json +24 -24
- package/src/locales/pt/errors.json +6 -6
- package/src/locales/pt/home.json +20 -20
- package/src/locales/pt/messages.json +8 -8
- package/src/locales/pt/nav.json +13 -13
- package/src/locales/pt/projects.json +9 -9
- package/src/locales/pt/services.json +43 -43
- package/src/middleware/index.ts +2 -0
- package/src/middleware/security.ts +15 -0
- package/src/middleware/sri.ts +78 -0
- package/src/utils/index.ts +2 -2
- package/src/utils/server/cookies.ts +98 -98
- package/src/utils/server/index.ts +1 -1
- package/src/utils/shared/deep-merge.ts +36 -36
- package/src/utils/shared/embed.ts +105 -105
- package/src/utils/shared/index.ts +3 -3
- package/src/utils/shared/parse-boolean.ts +14 -14
- package/worker-configuration.d.ts +0 -14791
|
@@ -1,318 +1,318 @@
|
|
|
1
|
-
export type FloatingSide = "top" | "bottom" | "left" | "right"
|
|
2
|
-
export type FloatingAlign = "start" | "center" | "end"
|
|
3
|
-
|
|
4
|
-
interface Placement {
|
|
5
|
-
side: FloatingSide
|
|
6
|
-
align: FloatingAlign
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface Position {
|
|
10
|
-
top: number
|
|
11
|
-
left: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface OverflowAmount {
|
|
15
|
-
left: number
|
|
16
|
-
right: number
|
|
17
|
-
top: number
|
|
18
|
-
bottom: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ResolvePlacementOptions {
|
|
22
|
-
side: FloatingSide
|
|
23
|
-
align: FloatingAlign
|
|
24
|
-
sideOffset: number
|
|
25
|
-
triggerRect: DOMRect
|
|
26
|
-
contentWidth: number
|
|
27
|
-
contentHeight: number
|
|
28
|
-
viewportWidth: number
|
|
29
|
-
viewportHeight: number
|
|
30
|
-
viewportPadding: number
|
|
31
|
-
avoidCollisions?: boolean
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface ResolvePlacementResult {
|
|
35
|
-
side: FloatingSide
|
|
36
|
-
align: FloatingAlign
|
|
37
|
-
top: number
|
|
38
|
-
left: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getOppositeSide(side: FloatingSide): FloatingSide {
|
|
42
|
-
switch (side) {
|
|
43
|
-
case "top":
|
|
44
|
-
return "bottom"
|
|
45
|
-
case "bottom":
|
|
46
|
-
return "top"
|
|
47
|
-
case "left":
|
|
48
|
-
return "right"
|
|
49
|
-
case "right":
|
|
50
|
-
return "left"
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function getOppositeAlign(align: FloatingAlign): FloatingAlign {
|
|
55
|
-
switch (align) {
|
|
56
|
-
case "start":
|
|
57
|
-
return "end"
|
|
58
|
-
case "end":
|
|
59
|
-
return "start"
|
|
60
|
-
case "center":
|
|
61
|
-
return "center"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getPlacementCandidates(side: FloatingSide, align: FloatingAlign): Placement[] {
|
|
66
|
-
const placements: Placement[] = []
|
|
67
|
-
|
|
68
|
-
const pushUnique = (candidate: Placement) => {
|
|
69
|
-
if (
|
|
70
|
-
!placements.some(
|
|
71
|
-
(placement) => placement.side === candidate.side && placement.align === candidate.align
|
|
72
|
-
)
|
|
73
|
-
) {
|
|
74
|
-
placements.push(candidate)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const oppositeSide = getOppositeSide(side)
|
|
79
|
-
const oppositeAlign = getOppositeAlign(align)
|
|
80
|
-
|
|
81
|
-
pushUnique({ side, align })
|
|
82
|
-
pushUnique({ side: oppositeSide, align })
|
|
83
|
-
pushUnique({ side, align: "center" })
|
|
84
|
-
pushUnique({ side: oppositeSide, align: "center" })
|
|
85
|
-
pushUnique({ side, align: oppositeAlign })
|
|
86
|
-
pushUnique({ side: oppositeSide, align: oppositeAlign })
|
|
87
|
-
|
|
88
|
-
if (side === "top" || side === "bottom") {
|
|
89
|
-
for (const fallbackAlign of ["start", "center", "end"] as const) {
|
|
90
|
-
pushUnique({ side: "right", align: fallbackAlign })
|
|
91
|
-
pushUnique({ side: "left", align: fallbackAlign })
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
for (const fallbackAlign of ["start", "center", "end"] as const) {
|
|
95
|
-
pushUnique({ side: "bottom", align: fallbackAlign })
|
|
96
|
-
pushUnique({ side: "top", align: fallbackAlign })
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return placements
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getPlacementPosition(
|
|
104
|
-
placement: Placement,
|
|
105
|
-
triggerRect: DOMRect,
|
|
106
|
-
contentWidth: number,
|
|
107
|
-
contentHeight: number,
|
|
108
|
-
sideOffset: number
|
|
109
|
-
): Position {
|
|
110
|
-
let top = 0
|
|
111
|
-
let left = 0
|
|
112
|
-
|
|
113
|
-
if (placement.side === "bottom" || placement.side === "top") {
|
|
114
|
-
top =
|
|
115
|
-
placement.side === "bottom"
|
|
116
|
-
? triggerRect.bottom + sideOffset
|
|
117
|
-
: triggerRect.top - contentHeight - sideOffset
|
|
118
|
-
|
|
119
|
-
if (placement.align === "start") {
|
|
120
|
-
left = triggerRect.left
|
|
121
|
-
} else if (placement.align === "end") {
|
|
122
|
-
left = triggerRect.right - contentWidth
|
|
123
|
-
} else {
|
|
124
|
-
left = triggerRect.left + (triggerRect.width - contentWidth) / 2
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
left =
|
|
128
|
-
placement.side === "right"
|
|
129
|
-
? triggerRect.right + sideOffset
|
|
130
|
-
: triggerRect.left - contentWidth - sideOffset
|
|
131
|
-
|
|
132
|
-
if (placement.align === "start") {
|
|
133
|
-
top = triggerRect.top
|
|
134
|
-
} else if (placement.align === "end") {
|
|
135
|
-
top = triggerRect.bottom - contentHeight
|
|
136
|
-
} else {
|
|
137
|
-
top = triggerRect.top + (triggerRect.height - contentHeight) / 2
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return { top, left }
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function clampPositionToViewport(
|
|
145
|
-
position: Position,
|
|
146
|
-
contentWidth: number,
|
|
147
|
-
contentHeight: number,
|
|
148
|
-
viewportWidth: number,
|
|
149
|
-
viewportHeight: number,
|
|
150
|
-
viewportPadding: number
|
|
151
|
-
): Position {
|
|
152
|
-
const maxLeft = Math.max(viewportPadding, viewportWidth - contentWidth - viewportPadding)
|
|
153
|
-
const maxTop = Math.max(viewportPadding, viewportHeight - contentHeight - viewportPadding)
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
left: Math.min(Math.max(viewportPadding, position.left), maxLeft),
|
|
157
|
-
top: Math.min(Math.max(viewportPadding, position.top), maxTop)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function getOverflowAmount(
|
|
162
|
-
position: Position,
|
|
163
|
-
contentWidth: number,
|
|
164
|
-
contentHeight: number,
|
|
165
|
-
viewportWidth: number,
|
|
166
|
-
viewportHeight: number,
|
|
167
|
-
viewportPadding: number
|
|
168
|
-
): OverflowAmount {
|
|
169
|
-
const right = position.left + contentWidth
|
|
170
|
-
const bottom = position.top + contentHeight
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
left: Math.max(0, viewportPadding - position.left),
|
|
174
|
-
top: Math.max(0, viewportPadding - position.top),
|
|
175
|
-
right: Math.max(0, right - (viewportWidth - viewportPadding)),
|
|
176
|
-
bottom: Math.max(0, bottom - (viewportHeight - viewportPadding))
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function getMainAxisShift(
|
|
181
|
-
side: FloatingSide,
|
|
182
|
-
preferredPosition: Position,
|
|
183
|
-
clampedPosition: Position
|
|
184
|
-
): number {
|
|
185
|
-
if (side === "top" || side === "bottom") {
|
|
186
|
-
return Math.abs(clampedPosition.top - preferredPosition.top)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return Math.abs(clampedPosition.left - preferredPosition.left)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function getCrossAxisShift(
|
|
193
|
-
side: FloatingSide,
|
|
194
|
-
preferredPosition: Position,
|
|
195
|
-
clampedPosition: Position
|
|
196
|
-
): number {
|
|
197
|
-
if (side === "top" || side === "bottom") {
|
|
198
|
-
return Math.abs(clampedPosition.left - preferredPosition.left)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return Math.abs(clampedPosition.top - preferredPosition.top)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Resolves the best side/alignment placement for floating content.
|
|
206
|
-
*/
|
|
207
|
-
export function resolvePlacement(options: ResolvePlacementOptions): ResolvePlacementResult {
|
|
208
|
-
const {
|
|
209
|
-
side,
|
|
210
|
-
align,
|
|
211
|
-
sideOffset,
|
|
212
|
-
triggerRect,
|
|
213
|
-
contentWidth,
|
|
214
|
-
contentHeight,
|
|
215
|
-
viewportWidth,
|
|
216
|
-
viewportHeight,
|
|
217
|
-
viewportPadding,
|
|
218
|
-
avoidCollisions = true
|
|
219
|
-
} = options
|
|
220
|
-
|
|
221
|
-
const preferredPlacement: Placement = { side, align }
|
|
222
|
-
|
|
223
|
-
if (!avoidCollisions) {
|
|
224
|
-
const preferredPosition = getPlacementPosition(
|
|
225
|
-
preferredPlacement,
|
|
226
|
-
triggerRect,
|
|
227
|
-
contentWidth,
|
|
228
|
-
contentHeight,
|
|
229
|
-
sideOffset
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
side,
|
|
234
|
-
align,
|
|
235
|
-
top: preferredPosition.top,
|
|
236
|
-
left: preferredPosition.left
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const candidates = getPlacementCandidates(side, align)
|
|
241
|
-
|
|
242
|
-
let bestPlacement = candidates[0] ?? preferredPlacement
|
|
243
|
-
let bestPosition = clampPositionToViewport(
|
|
244
|
-
getPlacementPosition(bestPlacement, triggerRect, contentWidth, contentHeight, sideOffset),
|
|
245
|
-
contentWidth,
|
|
246
|
-
contentHeight,
|
|
247
|
-
viewportWidth,
|
|
248
|
-
viewportHeight,
|
|
249
|
-
viewportPadding
|
|
250
|
-
)
|
|
251
|
-
let bestScore = Number.POSITIVE_INFINITY
|
|
252
|
-
|
|
253
|
-
for (const placement of candidates) {
|
|
254
|
-
const preferredPosition = getPlacementPosition(
|
|
255
|
-
placement,
|
|
256
|
-
triggerRect,
|
|
257
|
-
contentWidth,
|
|
258
|
-
contentHeight,
|
|
259
|
-
sideOffset
|
|
260
|
-
)
|
|
261
|
-
const clampedPosition = clampPositionToViewport(
|
|
262
|
-
preferredPosition,
|
|
263
|
-
contentWidth,
|
|
264
|
-
contentHeight,
|
|
265
|
-
viewportWidth,
|
|
266
|
-
viewportHeight,
|
|
267
|
-
viewportPadding
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
const overflow = getOverflowAmount(
|
|
271
|
-
preferredPosition,
|
|
272
|
-
contentWidth,
|
|
273
|
-
contentHeight,
|
|
274
|
-
viewportWidth,
|
|
275
|
-
viewportHeight,
|
|
276
|
-
viewportPadding
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
const overflowTotal = overflow.left + overflow.right + overflow.top + overflow.bottom
|
|
280
|
-
const mainAxisShift = getMainAxisShift(placement.side, preferredPosition, clampedPosition)
|
|
281
|
-
const crossAxisShift = getCrossAxisShift(placement.side, preferredPosition, clampedPosition)
|
|
282
|
-
|
|
283
|
-
const sidePenalty = placement.side === side ? 0 : 32
|
|
284
|
-
const alignPenalty = placement.align === align ? 0 : 8
|
|
285
|
-
const score =
|
|
286
|
-
overflowTotal * 24 + mainAxisShift * 8 + crossAxisShift * 3 + sidePenalty + alignPenalty
|
|
287
|
-
|
|
288
|
-
if (score < bestScore) {
|
|
289
|
-
bestScore = score
|
|
290
|
-
bestPlacement = placement
|
|
291
|
-
bestPosition = clampedPosition
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
side: bestPlacement.side,
|
|
297
|
-
align: bestPlacement.align,
|
|
298
|
-
top: bestPosition.top,
|
|
299
|
-
left: bestPosition.left
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Returns a transform-origin value that matches the resolved placement.
|
|
305
|
-
*/
|
|
306
|
-
export function getTransformOrigin(side: FloatingSide, align: FloatingAlign): string {
|
|
307
|
-
if (side === "top" || side === "bottom") {
|
|
308
|
-
const vertical = side === "bottom" ? "top" : "bottom"
|
|
309
|
-
const horizontal = align === "start" ? "left" : align === "end" ? "right" : "center"
|
|
310
|
-
|
|
311
|
-
return `${horizontal} ${vertical}`
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const horizontal = side === "right" ? "left" : "right"
|
|
315
|
-
const vertical = align === "start" ? "top" : align === "end" ? "bottom" : "center"
|
|
316
|
-
|
|
317
|
-
return `${horizontal} ${vertical}`
|
|
318
|
-
}
|
|
1
|
+
export type FloatingSide = "top" | "bottom" | "left" | "right"
|
|
2
|
+
export type FloatingAlign = "start" | "center" | "end"
|
|
3
|
+
|
|
4
|
+
interface Placement {
|
|
5
|
+
side: FloatingSide
|
|
6
|
+
align: FloatingAlign
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Position {
|
|
10
|
+
top: number
|
|
11
|
+
left: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface OverflowAmount {
|
|
15
|
+
left: number
|
|
16
|
+
right: number
|
|
17
|
+
top: number
|
|
18
|
+
bottom: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ResolvePlacementOptions {
|
|
22
|
+
side: FloatingSide
|
|
23
|
+
align: FloatingAlign
|
|
24
|
+
sideOffset: number
|
|
25
|
+
triggerRect: DOMRect
|
|
26
|
+
contentWidth: number
|
|
27
|
+
contentHeight: number
|
|
28
|
+
viewportWidth: number
|
|
29
|
+
viewportHeight: number
|
|
30
|
+
viewportPadding: number
|
|
31
|
+
avoidCollisions?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ResolvePlacementResult {
|
|
35
|
+
side: FloatingSide
|
|
36
|
+
align: FloatingAlign
|
|
37
|
+
top: number
|
|
38
|
+
left: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getOppositeSide(side: FloatingSide): FloatingSide {
|
|
42
|
+
switch (side) {
|
|
43
|
+
case "top":
|
|
44
|
+
return "bottom"
|
|
45
|
+
case "bottom":
|
|
46
|
+
return "top"
|
|
47
|
+
case "left":
|
|
48
|
+
return "right"
|
|
49
|
+
case "right":
|
|
50
|
+
return "left"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getOppositeAlign(align: FloatingAlign): FloatingAlign {
|
|
55
|
+
switch (align) {
|
|
56
|
+
case "start":
|
|
57
|
+
return "end"
|
|
58
|
+
case "end":
|
|
59
|
+
return "start"
|
|
60
|
+
case "center":
|
|
61
|
+
return "center"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getPlacementCandidates(side: FloatingSide, align: FloatingAlign): Placement[] {
|
|
66
|
+
const placements: Placement[] = []
|
|
67
|
+
|
|
68
|
+
const pushUnique = (candidate: Placement) => {
|
|
69
|
+
if (
|
|
70
|
+
!placements.some(
|
|
71
|
+
(placement) => placement.side === candidate.side && placement.align === candidate.align
|
|
72
|
+
)
|
|
73
|
+
) {
|
|
74
|
+
placements.push(candidate)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const oppositeSide = getOppositeSide(side)
|
|
79
|
+
const oppositeAlign = getOppositeAlign(align)
|
|
80
|
+
|
|
81
|
+
pushUnique({ side, align })
|
|
82
|
+
pushUnique({ side: oppositeSide, align })
|
|
83
|
+
pushUnique({ side, align: "center" })
|
|
84
|
+
pushUnique({ side: oppositeSide, align: "center" })
|
|
85
|
+
pushUnique({ side, align: oppositeAlign })
|
|
86
|
+
pushUnique({ side: oppositeSide, align: oppositeAlign })
|
|
87
|
+
|
|
88
|
+
if (side === "top" || side === "bottom") {
|
|
89
|
+
for (const fallbackAlign of ["start", "center", "end"] as const) {
|
|
90
|
+
pushUnique({ side: "right", align: fallbackAlign })
|
|
91
|
+
pushUnique({ side: "left", align: fallbackAlign })
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
for (const fallbackAlign of ["start", "center", "end"] as const) {
|
|
95
|
+
pushUnique({ side: "bottom", align: fallbackAlign })
|
|
96
|
+
pushUnique({ side: "top", align: fallbackAlign })
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return placements
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getPlacementPosition(
|
|
104
|
+
placement: Placement,
|
|
105
|
+
triggerRect: DOMRect,
|
|
106
|
+
contentWidth: number,
|
|
107
|
+
contentHeight: number,
|
|
108
|
+
sideOffset: number
|
|
109
|
+
): Position {
|
|
110
|
+
let top = 0
|
|
111
|
+
let left = 0
|
|
112
|
+
|
|
113
|
+
if (placement.side === "bottom" || placement.side === "top") {
|
|
114
|
+
top =
|
|
115
|
+
placement.side === "bottom"
|
|
116
|
+
? triggerRect.bottom + sideOffset
|
|
117
|
+
: triggerRect.top - contentHeight - sideOffset
|
|
118
|
+
|
|
119
|
+
if (placement.align === "start") {
|
|
120
|
+
left = triggerRect.left
|
|
121
|
+
} else if (placement.align === "end") {
|
|
122
|
+
left = triggerRect.right - contentWidth
|
|
123
|
+
} else {
|
|
124
|
+
left = triggerRect.left + (triggerRect.width - contentWidth) / 2
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
left =
|
|
128
|
+
placement.side === "right"
|
|
129
|
+
? triggerRect.right + sideOffset
|
|
130
|
+
: triggerRect.left - contentWidth - sideOffset
|
|
131
|
+
|
|
132
|
+
if (placement.align === "start") {
|
|
133
|
+
top = triggerRect.top
|
|
134
|
+
} else if (placement.align === "end") {
|
|
135
|
+
top = triggerRect.bottom - contentHeight
|
|
136
|
+
} else {
|
|
137
|
+
top = triggerRect.top + (triggerRect.height - contentHeight) / 2
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { top, left }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function clampPositionToViewport(
|
|
145
|
+
position: Position,
|
|
146
|
+
contentWidth: number,
|
|
147
|
+
contentHeight: number,
|
|
148
|
+
viewportWidth: number,
|
|
149
|
+
viewportHeight: number,
|
|
150
|
+
viewportPadding: number
|
|
151
|
+
): Position {
|
|
152
|
+
const maxLeft = Math.max(viewportPadding, viewportWidth - contentWidth - viewportPadding)
|
|
153
|
+
const maxTop = Math.max(viewportPadding, viewportHeight - contentHeight - viewportPadding)
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
left: Math.min(Math.max(viewportPadding, position.left), maxLeft),
|
|
157
|
+
top: Math.min(Math.max(viewportPadding, position.top), maxTop)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getOverflowAmount(
|
|
162
|
+
position: Position,
|
|
163
|
+
contentWidth: number,
|
|
164
|
+
contentHeight: number,
|
|
165
|
+
viewportWidth: number,
|
|
166
|
+
viewportHeight: number,
|
|
167
|
+
viewportPadding: number
|
|
168
|
+
): OverflowAmount {
|
|
169
|
+
const right = position.left + contentWidth
|
|
170
|
+
const bottom = position.top + contentHeight
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
left: Math.max(0, viewportPadding - position.left),
|
|
174
|
+
top: Math.max(0, viewportPadding - position.top),
|
|
175
|
+
right: Math.max(0, right - (viewportWidth - viewportPadding)),
|
|
176
|
+
bottom: Math.max(0, bottom - (viewportHeight - viewportPadding))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getMainAxisShift(
|
|
181
|
+
side: FloatingSide,
|
|
182
|
+
preferredPosition: Position,
|
|
183
|
+
clampedPosition: Position
|
|
184
|
+
): number {
|
|
185
|
+
if (side === "top" || side === "bottom") {
|
|
186
|
+
return Math.abs(clampedPosition.top - preferredPosition.top)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return Math.abs(clampedPosition.left - preferredPosition.left)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getCrossAxisShift(
|
|
193
|
+
side: FloatingSide,
|
|
194
|
+
preferredPosition: Position,
|
|
195
|
+
clampedPosition: Position
|
|
196
|
+
): number {
|
|
197
|
+
if (side === "top" || side === "bottom") {
|
|
198
|
+
return Math.abs(clampedPosition.left - preferredPosition.left)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return Math.abs(clampedPosition.top - preferredPosition.top)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolves the best side/alignment placement for floating content.
|
|
206
|
+
*/
|
|
207
|
+
export function resolvePlacement(options: ResolvePlacementOptions): ResolvePlacementResult {
|
|
208
|
+
const {
|
|
209
|
+
side,
|
|
210
|
+
align,
|
|
211
|
+
sideOffset,
|
|
212
|
+
triggerRect,
|
|
213
|
+
contentWidth,
|
|
214
|
+
contentHeight,
|
|
215
|
+
viewportWidth,
|
|
216
|
+
viewportHeight,
|
|
217
|
+
viewportPadding,
|
|
218
|
+
avoidCollisions = true
|
|
219
|
+
} = options
|
|
220
|
+
|
|
221
|
+
const preferredPlacement: Placement = { side, align }
|
|
222
|
+
|
|
223
|
+
if (!avoidCollisions) {
|
|
224
|
+
const preferredPosition = getPlacementPosition(
|
|
225
|
+
preferredPlacement,
|
|
226
|
+
triggerRect,
|
|
227
|
+
contentWidth,
|
|
228
|
+
contentHeight,
|
|
229
|
+
sideOffset
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
side,
|
|
234
|
+
align,
|
|
235
|
+
top: preferredPosition.top,
|
|
236
|
+
left: preferredPosition.left
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const candidates = getPlacementCandidates(side, align)
|
|
241
|
+
|
|
242
|
+
let bestPlacement = candidates[0] ?? preferredPlacement
|
|
243
|
+
let bestPosition = clampPositionToViewport(
|
|
244
|
+
getPlacementPosition(bestPlacement, triggerRect, contentWidth, contentHeight, sideOffset),
|
|
245
|
+
contentWidth,
|
|
246
|
+
contentHeight,
|
|
247
|
+
viewportWidth,
|
|
248
|
+
viewportHeight,
|
|
249
|
+
viewportPadding
|
|
250
|
+
)
|
|
251
|
+
let bestScore = Number.POSITIVE_INFINITY
|
|
252
|
+
|
|
253
|
+
for (const placement of candidates) {
|
|
254
|
+
const preferredPosition = getPlacementPosition(
|
|
255
|
+
placement,
|
|
256
|
+
triggerRect,
|
|
257
|
+
contentWidth,
|
|
258
|
+
contentHeight,
|
|
259
|
+
sideOffset
|
|
260
|
+
)
|
|
261
|
+
const clampedPosition = clampPositionToViewport(
|
|
262
|
+
preferredPosition,
|
|
263
|
+
contentWidth,
|
|
264
|
+
contentHeight,
|
|
265
|
+
viewportWidth,
|
|
266
|
+
viewportHeight,
|
|
267
|
+
viewportPadding
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
const overflow = getOverflowAmount(
|
|
271
|
+
preferredPosition,
|
|
272
|
+
contentWidth,
|
|
273
|
+
contentHeight,
|
|
274
|
+
viewportWidth,
|
|
275
|
+
viewportHeight,
|
|
276
|
+
viewportPadding
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
const overflowTotal = overflow.left + overflow.right + overflow.top + overflow.bottom
|
|
280
|
+
const mainAxisShift = getMainAxisShift(placement.side, preferredPosition, clampedPosition)
|
|
281
|
+
const crossAxisShift = getCrossAxisShift(placement.side, preferredPosition, clampedPosition)
|
|
282
|
+
|
|
283
|
+
const sidePenalty = placement.side === side ? 0 : 32
|
|
284
|
+
const alignPenalty = placement.align === align ? 0 : 8
|
|
285
|
+
const score =
|
|
286
|
+
overflowTotal * 24 + mainAxisShift * 8 + crossAxisShift * 3 + sidePenalty + alignPenalty
|
|
287
|
+
|
|
288
|
+
if (score < bestScore) {
|
|
289
|
+
bestScore = score
|
|
290
|
+
bestPlacement = placement
|
|
291
|
+
bestPosition = clampedPosition
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
side: bestPlacement.side,
|
|
297
|
+
align: bestPlacement.align,
|
|
298
|
+
top: bestPosition.top,
|
|
299
|
+
left: bestPosition.left
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Returns a transform-origin value that matches the resolved placement.
|
|
305
|
+
*/
|
|
306
|
+
export function getTransformOrigin(side: FloatingSide, align: FloatingAlign): string {
|
|
307
|
+
if (side === "top" || side === "bottom") {
|
|
308
|
+
const vertical = side === "bottom" ? "top" : "bottom"
|
|
309
|
+
const horizontal = align === "start" ? "left" : align === "end" ? "right" : "center"
|
|
310
|
+
|
|
311
|
+
return `${horizontal} ${vertical}`
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const horizontal = side === "right" ? "left" : "right"
|
|
315
|
+
const vertical = align === "start" ? "top" : align === "end" ? "bottom" : "center"
|
|
316
|
+
|
|
317
|
+
return `${horizontal} ${vertical}`
|
|
318
|
+
}
|
package/src/locales/de/blog.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"meta_title": "Blog | Daniel Marchi",
|
|
3
|
-
"hero_title": "Blog",
|
|
4
|
-
"hero_description": "Meine Einblicke auf meiner Reise als Entwickler.",
|
|
5
|
-
"latest_article": "Neuester Artikel",
|
|
6
|
-
"latest": "Neu",
|
|
7
|
-
"read_article": "Artikel lesen",
|
|
8
|
-
"all_posts": "Alle Beiträge"
|
|
9
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"meta_title": "Blog | Daniel Marchi",
|
|
3
|
+
"hero_title": "Blog",
|
|
4
|
+
"hero_description": "Meine Einblicke auf meiner Reise als Entwickler.",
|
|
5
|
+
"latest_article": "Neuester Artikel",
|
|
6
|
+
"latest": "Neu",
|
|
7
|
+
"read_article": "Artikel lesen",
|
|
8
|
+
"all_posts": "Alle Beiträge"
|
|
9
|
+
}
|