vanilla-jui 1.4.8 → 1.5.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 (53) hide show
  1. package/README.md +10 -4
  2. package/dist/index.d.ts +402 -977
  3. package/dist/index.js +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -3
  7. package/src/components/accordion.ts +467 -349
  8. package/src/components/flow.ts +779 -1133
  9. package/src/components/form.ts +666 -509
  10. package/src/components/menu.ts +199 -326
  11. package/src/components/modal.ts +510 -747
  12. package/src/components/offcanvas.ts +281 -361
  13. package/src/components/pagination.ts +207 -374
  14. package/src/components/swiper.ts +688 -1168
  15. package/src/components/tabs.ts +468 -788
  16. package/src/components/toc.ts +191 -244
  17. package/src/core/component.ts +332 -0
  18. package/src/core/motion.ts +298 -0
  19. package/src/core/presence.ts +233 -0
  20. package/src/core/scheduler.ts +91 -0
  21. package/src/core/view.ts +31 -0
  22. package/src/css/accordion.css +94 -3
  23. package/src/css/content.css +18 -24
  24. package/src/css/drop.css +4 -1
  25. package/src/css/form.css +8 -0
  26. package/src/css/menu.css +10 -5
  27. package/src/css/modal.css +2 -1
  28. package/src/css/offcanvas.css +5 -17
  29. package/src/css/swiper.css +2 -1
  30. package/src/css/tabs.css +8 -17
  31. package/src/css/themes/default/accordion.css +0 -48
  32. package/src/css/themes/default/swiper.css +1 -1
  33. package/src/css/themes/default/tabs.css +4 -6
  34. package/src/css/toast.css +1 -17
  35. package/src/css/tooltip.css +2 -1
  36. package/src/index.ts +8 -3
  37. package/src/locales/index.ts +3 -0
  38. package/src/primitives/drop.ts +253 -262
  39. package/src/primitives/loading.ts +22 -4
  40. package/src/primitives/parabola.ts +123 -164
  41. package/src/primitives/sticky.ts +297 -0
  42. package/src/primitives/theme.ts +164 -222
  43. package/src/primitives/toast.ts +309 -284
  44. package/src/primitives/tooltip.ts +65 -66
  45. package/src/utilities/browser.ts +38 -0
  46. package/src/utilities/dom.ts +0 -54
  47. package/src/utilities/refs.ts +61 -0
  48. package/src/utilities/state.ts +73 -0
  49. package/src/utilities/timer.ts +11 -31
  50. package/src/validation/validator.ts +289 -389
  51. package/src/components/sticky.ts +0 -297
  52. package/src/core/Component.ts +0 -377
  53. package/src/utilities/scheduler.ts +0 -156
@@ -0,0 +1,233 @@
1
+ import { flushSync } from 'vanilla-signal';
2
+
3
+ import { type MotionController } from './motion.ts';
4
+
5
+ export type PresencePhase = 'hidden' | 'entering' | 'visible' | 'leaving';
6
+
7
+ export interface PresenceOptions {
8
+ elements: () => readonly (Element | null | undefined)[];
9
+ mount: () => void;
10
+ activate: () => void;
11
+ deactivate: () => void;
12
+ unmount: () => void;
13
+ motion?: MotionController;
14
+ }
15
+
16
+ export interface PresenceController {
17
+ readonly phase: PresencePhase;
18
+ enter: () => Promise<boolean>;
19
+ leave: () => Promise<boolean>;
20
+ cancel: () => void;
21
+ }
22
+
23
+ function connectedElements(
24
+ values: readonly (Element | null | undefined)[]
25
+ ): Element[] {
26
+ return values.filter(
27
+ (value): value is Element => value != null && value.isConnected
28
+ );
29
+ }
30
+
31
+ function commitInitialStyles(elements: readonly Element[]): void {
32
+ elements[0]?.getBoundingClientRect();
33
+ }
34
+
35
+ function timeList(value: string): number[] {
36
+ return value.split(',').map((part) => {
37
+ const time = part.trim();
38
+ if (time.endsWith('ms')) return Number.parseFloat(time) || 0;
39
+ if (time.endsWith('s')) return (Number.parseFloat(time) || 0) * 1000;
40
+ return 0;
41
+ });
42
+ }
43
+
44
+ function numberList(value: string): number[] {
45
+ return value.split(',').map((part) => {
46
+ const count = Number.parseFloat(part);
47
+ return Number.isFinite(count) ? count : 0;
48
+ });
49
+ }
50
+
51
+ function timelineDuration(
52
+ durations: number[],
53
+ delays: number[],
54
+ iterations: number[] = [1],
55
+ enabled: boolean[] = [true]
56
+ ): number {
57
+ const count = Math.max(
58
+ durations.length,
59
+ delays.length,
60
+ iterations.length,
61
+ enabled.length
62
+ );
63
+ let duration = 0;
64
+ for (let index = 0; index < count; index += 1) {
65
+ if (!enabled[index % enabled.length]) continue;
66
+ const active =
67
+ (durations[index % durations.length] || 0) *
68
+ (iterations[index % iterations.length] || 0) +
69
+ (delays[index % delays.length] || 0);
70
+ duration = Math.max(duration, active);
71
+ }
72
+ return Math.max(0, duration);
73
+ }
74
+
75
+ function computedMotionDuration(element: Element): number {
76
+ const view = element.ownerDocument.defaultView;
77
+ if (!view) return 0;
78
+ const style = view.getComputedStyle(element);
79
+ const transition = timelineDuration(
80
+ timeList(style.transitionDuration),
81
+ timeList(style.transitionDelay),
82
+ [1],
83
+ style.transitionProperty
84
+ .split(',')
85
+ .map((property) => property.trim() !== 'none')
86
+ );
87
+ const animation = timelineDuration(
88
+ timeList(style.animationDuration),
89
+ timeList(style.animationDelay),
90
+ numberList(style.animationIterationCount),
91
+ style.animationName.split(',').map((name) => name.trim() !== 'none')
92
+ );
93
+ return Math.max(transition, animation);
94
+ }
95
+
96
+ function waitForDuration(
97
+ duration: number,
98
+ signal?: AbortSignal
99
+ ): Promise<void> {
100
+ return new Promise((resolve) => {
101
+ if (signal?.aborted) {
102
+ resolve();
103
+ return;
104
+ }
105
+ const finish = (): void => {
106
+ clearTimeout(timeout);
107
+ signal?.removeEventListener('abort', finish);
108
+ resolve();
109
+ };
110
+ const timeout = setTimeout(finish, duration);
111
+ signal?.addEventListener('abort', finish, { once: true });
112
+ });
113
+ }
114
+
115
+ export async function waitForMotion(
116
+ elements: readonly Element[],
117
+ signal?: AbortSignal
118
+ ): Promise<void> {
119
+ const animations = new Set<Animation>();
120
+ let fallbackDuration = 0;
121
+ for (const element of elements) {
122
+ if (typeof element.getAnimations !== 'function') {
123
+ fallbackDuration = Math.max(
124
+ fallbackDuration,
125
+ computedMotionDuration(element)
126
+ );
127
+ continue;
128
+ }
129
+ for (const animation of element.getAnimations()) {
130
+ const endTime = animation.effect?.getComputedTiming().endTime;
131
+ if (typeof endTime !== 'number' || Number.isFinite(endTime)) {
132
+ animations.add(animation);
133
+ }
134
+ }
135
+ }
136
+ const pending = Array.from(
137
+ animations,
138
+ (animation) => animation.finished as Promise<unknown>
139
+ );
140
+ if (fallbackDuration > 0) {
141
+ pending.push(waitForDuration(fallbackDuration, signal));
142
+ }
143
+ if (pending.length === 0) return;
144
+ const settled = Promise.allSettled(pending);
145
+ if (!signal) {
146
+ await settled;
147
+ return;
148
+ }
149
+ await new Promise<void>((resolve) => {
150
+ const finish = (): void => {
151
+ signal.removeEventListener('abort', finish);
152
+ resolve();
153
+ };
154
+ if (signal.aborted) {
155
+ finish();
156
+ return;
157
+ }
158
+ signal.addEventListener('abort', finish, { once: true });
159
+ void settled.then(finish);
160
+ });
161
+ }
162
+
163
+ export function createPresence(options: PresenceOptions): PresenceController {
164
+ let phase: PresencePhase = 'hidden';
165
+ let operation = 0;
166
+ let entering: Promise<boolean> | null = null;
167
+ let leaving: Promise<boolean> | null = null;
168
+ let motionController: AbortController | null = null;
169
+
170
+ const nextMotion = (): AbortSignal => {
171
+ motionController?.abort();
172
+ motionController = new AbortController();
173
+ return motionController.signal;
174
+ };
175
+
176
+ const enter = (): Promise<boolean> => {
177
+ if (phase === 'visible') return Promise.resolve(false);
178
+ if (phase === 'entering' && entering) return entering;
179
+
180
+ const currentOperation = ++operation;
181
+ const signal = nextMotion();
182
+ phase = 'entering';
183
+ entering = (async () => {
184
+ options.mount();
185
+ const elements = connectedElements(options.elements());
186
+ commitInitialStyles(elements);
187
+ flushSync(options.activate);
188
+ if (options.motion) await options.motion.enter(signal);
189
+ else await waitForMotion(elements, signal);
190
+ if (currentOperation !== operation) return false;
191
+ phase = 'visible';
192
+ return true;
193
+ })();
194
+ return entering;
195
+ };
196
+
197
+ const leave = (): Promise<boolean> => {
198
+ if (phase === 'hidden') return Promise.resolve(false);
199
+ if (phase === 'leaving' && leaving) return leaving;
200
+
201
+ const currentOperation = ++operation;
202
+ const signal = nextMotion();
203
+ phase = 'leaving';
204
+ leaving = (async () => {
205
+ flushSync(options.deactivate);
206
+ const elements = connectedElements(options.elements());
207
+ if (options.motion) await options.motion.leave(signal);
208
+ else await waitForMotion(elements, signal);
209
+ if (currentOperation !== operation) return false;
210
+ options.unmount();
211
+ phase = 'hidden';
212
+ return true;
213
+ })();
214
+ return leaving;
215
+ };
216
+
217
+ return {
218
+ get phase() {
219
+ return phase;
220
+ },
221
+ enter,
222
+ leave,
223
+ cancel() {
224
+ operation += 1;
225
+ motionController?.abort();
226
+ motionController = null;
227
+ options.motion?.cancel();
228
+ phase = 'hidden';
229
+ entering = null;
230
+ leaving = null;
231
+ },
232
+ };
233
+ }
@@ -0,0 +1,91 @@
1
+ interface QueueNode {
2
+ queued: boolean;
3
+ enqueued: boolean;
4
+ disposed: boolean;
5
+ next: QueueNode | null;
6
+ run: () => void;
7
+ }
8
+
9
+ interface ScheduledTask {
10
+ schedule: () => void;
11
+ flush: () => void;
12
+ cancel: () => void;
13
+ dispose: () => void;
14
+ }
15
+
16
+ let head: QueueNode | null = null;
17
+ let tail: QueueNode | null = null;
18
+ let pending = false;
19
+
20
+ function queueTask(callback: () => void): void {
21
+ if (typeof queueMicrotask === 'function') {
22
+ queueMicrotask(callback);
23
+ return;
24
+ }
25
+ void Promise.resolve().then(callback);
26
+ }
27
+
28
+ function requestFlush(): void {
29
+ if (pending) return;
30
+ pending = true;
31
+ queueTask(flushQueue);
32
+ }
33
+
34
+ function enqueue(node: QueueNode): void {
35
+ node.enqueued = true;
36
+ if (tail) tail.next = node;
37
+ else head = node;
38
+ tail = node;
39
+ requestFlush();
40
+ }
41
+
42
+ function flushQueue(): void {
43
+ pending = false;
44
+ const start = head;
45
+ head = null;
46
+ tail = null;
47
+
48
+ let node = start;
49
+ while (node) {
50
+ const next = node.next;
51
+ node.next = null;
52
+ node.enqueued = false;
53
+
54
+ if (!node.disposed && node.queued) {
55
+ node.queued = false;
56
+ node.run();
57
+ }
58
+
59
+ node = next;
60
+ }
61
+ }
62
+
63
+ export function createScheduledTask(run: () => void): ScheduledTask {
64
+ const node: QueueNode = {
65
+ queued: false,
66
+ enqueued: false,
67
+ disposed: false,
68
+ next: null,
69
+ run,
70
+ };
71
+
72
+ return {
73
+ schedule() {
74
+ if (node.disposed || node.queued) return;
75
+ node.queued = true;
76
+ if (!node.enqueued) enqueue(node);
77
+ },
78
+ flush() {
79
+ if (node.disposed) return;
80
+ node.queued = false;
81
+ node.run();
82
+ },
83
+ cancel() {
84
+ node.queued = false;
85
+ },
86
+ dispose() {
87
+ node.disposed = true;
88
+ node.queued = false;
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,31 @@
1
+ import { createRoot, onCleanup, untrack } from 'vanilla-signal';
2
+
3
+ export interface OwnedView<TElement extends Element> {
4
+ element: TElement;
5
+ dispose: () => void;
6
+ }
7
+
8
+ export interface OwnedViewOptions {
9
+ removeOnDispose?: boolean;
10
+ }
11
+
12
+ /**
13
+ * Creates one stable view inside a vanilla-signal owner.
14
+ * Reactive accessors declared by the factory retain the owner, while the
15
+ * factory itself is not turned into a replaceable dynamic region.
16
+ */
17
+ export function createOwnedView<TElement extends Element>(
18
+ factory: () => TElement,
19
+ options: OwnedViewOptions = {}
20
+ ): OwnedView<TElement> {
21
+ let element!: TElement;
22
+ const removeOnDispose = options.removeOnDispose !== false;
23
+
24
+ const dispose = createRoot((disposeRoot) => {
25
+ element = untrack(factory);
26
+ if (removeOnDispose) onCleanup(() => element.remove());
27
+ return disposeRoot;
28
+ }) as () => void;
29
+
30
+ return { element, dispose };
31
+ }
@@ -1,9 +1,51 @@
1
1
  @layer components {
2
+ .j-accordion {
3
+ display: flex;
4
+ flex-direction: column;
5
+ }
6
+
7
+ .j-accordion[data-direction='horizontal'] {
8
+ flex-direction: row;
9
+ }
10
+
2
11
  .j-accordion .accordion-header {
3
12
  display: flex;
4
13
  align-items: center;
5
14
  cursor: pointer;
6
15
  user-select: none;
16
+
17
+ padding: var(--accordion-header-padding);
18
+ margin: var(--accordion-header-margin);
19
+ font-weight: var(--accordion-font-weight);
20
+ font-size: var(--accordion-header-font-size);
21
+ background: var(--accordion-bg);
22
+ border-bottom: var(--accordion-border);
23
+ }
24
+
25
+ .j-accordion[data-direction='horizontal'] .accordion-header {
26
+ gap: var(--space);
27
+ border-bottom: 0;
28
+ border-right: var(--accordion-border);
29
+ }
30
+
31
+ .j-accordion .accordion-header[aria-expanded='true'] {
32
+ border-bottom: 0;
33
+ }
34
+
35
+ .j-accordion[data-direction='horizontal'] .accordion-header[aria-expanded='true'] {
36
+ border-right: 0;
37
+ }
38
+
39
+ .j-accordion .accordion-header[aria-expanded='false']:hover {
40
+ --accordion-bg: var(--ui-surface-hover);
41
+ }
42
+
43
+ .j-accordion .accordion-header[aria-expanded='false']:nth-last-child(2) {
44
+ border-bottom: 0;
45
+ }
46
+
47
+ .j-accordion[data-direction='horizontal'] .accordion-header[aria-expanded='false']:nth-last-child(2) {
48
+ border-right: 0;
7
49
  }
8
50
 
9
51
  .j-accordion .accordion-header .header-title {
@@ -11,17 +53,66 @@
11
53
  text-align: left;
12
54
  }
13
55
 
56
+ .j-accordion .accordion-header[aria-expanded='true'] .header-title {
57
+ --accordion-font-weight: var(--font-bold);
58
+ }
59
+
14
60
  .j-accordion .accordion-header .header-arrow {
15
61
  display: flex;
16
62
  justify-content: center;
17
63
  align-items: center;
64
+ transition: transform 0.3s ease-out;
65
+ transform: var(--accordion-icon-transform);
66
+ }
67
+
68
+ .j-accordion[data-direction='horizontal'] .accordion-header .header-arrow {
69
+ --accordion-icon-transform: rotate(-90deg);
70
+ }
71
+
72
+ .j-accordion .accordion-header[aria-expanded='true'] .header-arrow {
73
+ --accordion-icon-transform: rotate(180deg);
74
+ }
75
+
76
+ .j-accordion[data-direction='horizontal'] .accordion-header[aria-expanded='true'] .header-arrow {
77
+ --accordion-icon-transform: rotate(90deg);
18
78
  }
19
79
 
20
80
  .j-accordion .accordion-panel {
21
- display: none;
81
+ margin: var(--accordion-panel-margin);
82
+ font-size: var(--accordion-font-size);
83
+ border-bottom: var(--accordion-border);
84
+ position: relative;
22
85
  }
23
86
 
24
87
  .j-accordion .accordion-panel[aria-hidden='false'] {
25
- display: block;
88
+ min-height: 2rem;
89
+ }
90
+
91
+ .j-accordion[data-direction='horizontal'] .accordion-panel[aria-hidden='false'] {
92
+ border-right: var(--accordion-border);
93
+ border-bottom: 0;
94
+ flex: 1;
95
+ }
96
+
97
+ .j-accordion .accordion-panel:last-child {
98
+ border-bottom: 0;
99
+ }
100
+
101
+ .j-accordion[data-direction='horizontal'] .accordion-panel:last-child {
102
+ border-right: 0;
103
+ }
104
+
105
+ .j-accordion .accordion-panel .panel-content {
106
+ margin: var(--accordion-panel-content-margin);
107
+ padding: var(--accordion-panel-content-padding);
108
+ position: relative;
109
+ min-height: inherit;
110
+ }
111
+
112
+ .j-accordion[data-direction='horizontal'] .accordion-panel[aria-hidden='false'] .panel-content {
113
+ display: flex;
114
+ flex-direction: column;
115
+ justify-content: center;
116
+ height: 100%;
26
117
  }
27
- }
118
+ }
@@ -1,4 +1,5 @@
1
1
  @layer content {
2
+
2
3
  /* ============================================
3
4
  Content - 文章阅读/编辑器预览样式
4
5
  ============================================ */
@@ -18,12 +19,6 @@
18
19
  /* ============================================
19
20
  Typography - 段落与标题
20
21
  ============================================ */
21
- .j-content *,
22
- .j-content *::before,
23
- .j-content *::after {
24
- box-sizing: border-box;
25
- }
26
-
27
22
  .j-content p {
28
23
  margin: 0 0 var(--content-paragraph-space);
29
24
  }
@@ -34,8 +29,7 @@
34
29
  .j-content h4,
35
30
  .j-content h5,
36
31
  .j-content h6 {
37
- margin: var(--content-heading-space) 0
38
- calc(var(--content-heading-space) * 0.75);
32
+ margin: var(--content-heading-space) 0 calc(var(--content-heading-space) * 0.75);
39
33
  font-weight: 600;
40
34
  line-height: 1.3;
41
35
  color: var(--content-heading-fg);
@@ -70,12 +64,12 @@
70
64
  }
71
65
 
72
66
  /* 标题后的段落减少间距 */
73
- .j-content h1 + p,
74
- .j-content h2 + p,
75
- .j-content h3 + p,
76
- .j-content h4 + p,
77
- .j-content h5 + p,
78
- .j-content h6 + p {
67
+ .j-content h1+p,
68
+ .j-content h2+p,
69
+ .j-content h3+p,
70
+ .j-content h4+p,
71
+ .j-content h5+p,
72
+ .j-content h6+p {
79
73
  margin-top: calc(var(--content-paragraph-space) * -0.5);
80
74
  }
81
75
 
@@ -255,7 +249,7 @@
255
249
  color: var(--tone-body);
256
250
  }
257
251
 
258
- .j-content dd + dt {
252
+ .j-content dd+dt {
259
253
  margin-top: 1em;
260
254
  }
261
255
 
@@ -283,7 +277,7 @@
283
277
  margin: 0;
284
278
  }
285
279
 
286
- .j-content blockquote p + p {
280
+ .j-content blockquote p+p {
287
281
  margin-top: 0.75em;
288
282
  }
289
283
 
@@ -399,7 +393,7 @@
399
393
  padding: 1.25em 1.5em;
400
394
  }
401
395
 
402
- .j-content pre.j-code-editor .code-header + code[class*='language-'] {
396
+ .j-content pre.j-code-editor .code-header+code[class*='language-'] {
403
397
  padding: 0.5rem 1rem 1rem;
404
398
  }
405
399
 
@@ -647,14 +641,14 @@
647
641
  transition: transform 0.2s ease;
648
642
  }
649
643
 
650
- .j-content details[open] > summary::before {
644
+ .j-content details[open]>summary::before {
651
645
  transform: rotate(90deg);
652
646
  }
653
647
 
654
648
  .j-content details[open] p,
655
649
  .j-content details[open] ul,
656
650
  .j-content details[open] ol,
657
- .j-content details[open] > * {
651
+ .j-content details[open]>* {
658
652
  margin-bottom: 1em;
659
653
  }
660
654
 
@@ -662,11 +656,11 @@
662
656
  margin-bottom: 0.75em;
663
657
  } */
664
658
 
665
- .j-content details[open] > summary + * {
659
+ .j-content details[open]>summary+* {
666
660
  margin-top: 0;
667
661
  }
668
662
 
669
- .j-content details[open] > *:last-child {
663
+ .j-content details[open]>*:last-child {
670
664
  margin-bottom: 0;
671
665
  }
672
666
 
@@ -711,11 +705,11 @@
711
705
  }
712
706
 
713
707
  /* First/Last child margin reset */
714
- .j-content > *:first-child {
708
+ .j-content>*:first-child {
715
709
  margin-top: 0;
716
710
  }
717
711
 
718
- .j-content > *:last-child {
712
+ .j-content>*:last-child {
719
713
  margin-bottom: 0;
720
714
  }
721
- }
715
+ }
package/src/css/drop.css CHANGED
@@ -24,5 +24,8 @@
24
24
  background: var(--drop-bg);
25
25
  color: var(--drop-fg);
26
26
  box-shadow: var(--drop-box-shadow);
27
+ position: relative;
28
+ min-width: 64px;
29
+ min-height: 32px;
27
30
  }
28
- }
31
+ }
package/src/css/form.css CHANGED
@@ -67,6 +67,14 @@
67
67
  .j-form .form-buttons {
68
68
  display: flex;
69
69
  gap: var(--form-buttons-gap, var(--s2));
70
+ margin-top: 1rem;
71
+ }
72
+
73
+ .j-form [data-form-buttons] button {
74
+ position: relative;
75
+ }
76
+
77
+ .j-form.is-horizontal .form-buttons {
70
78
  margin: 0;
71
79
  }
72
80
 
package/src/css/menu.css CHANGED
@@ -77,7 +77,10 @@
77
77
  height: var(--menu-item-icon-size);
78
78
  }
79
79
 
80
- .j-menu .menu .menu-item.menu-item-has-children > a {
80
+ .j-menu:not([data-menu-type='mobile']):not([data-menu-type='bottom'])
81
+ .menu
82
+ .menu-item.menu-item-has-children
83
+ > a {
81
84
  pointer-events: none;
82
85
  }
83
86
 
@@ -413,14 +416,13 @@
413
416
  width: 100%;
414
417
  visibility: hidden;
415
418
  opacity: 0;
416
- transform: translateX(100%);
419
+ transform: translateX(80%);
417
420
  transition:
421
+ transform 0.3s ease-in-out,
418
422
  opacity 0.3s ease-in-out,
419
423
  visibility 0.3s ease-in-out;
420
-
421
424
  max-width: 100%;
422
425
  background: var(--ui-bg);
423
- z-index: -1;
424
426
  }
425
427
 
426
428
  .j-menu[data-menu-type='mobile'] .menu .sub-menu .menu-item:hover a {
@@ -520,11 +522,13 @@
520
522
  min-width: var(--columns-6xs);
521
523
  max-width: var(--columns-2xs);
522
524
  left: 50%;
523
- transform: translateX(-50%);
525
+ transform: translateX(-50%) translateY(100%);
524
526
  bottom: calc(100% + var(--space));
525
527
  transition:
528
+ transform 0.3s ease-in-out,
526
529
  visibility 0.3s ease-in-out,
527
530
  opacity 0.3s ease-in-out;
531
+ z-index: -1;
528
532
  }
529
533
 
530
534
  .j-menu[data-menu-type='bottom']
@@ -533,6 +537,7 @@
533
537
  > .sub-menu {
534
538
  opacity: 1;
535
539
  visibility: visible;
540
+ transform: translateX(-50%) translateY(0%);
536
541
  }
537
542
 
538
543
  .j-menu[data-menu-type='bottom']
package/src/css/modal.css CHANGED
@@ -11,7 +11,6 @@
11
11
  max-width: var(--modal-max-width);
12
12
  max-height: var(--modal-max-height);
13
13
  overflow: hidden;
14
- animation: animate-fade-in 0.35s ease-in-out;
15
14
  display: flex;
16
15
  flex-direction: column;
17
16
  position: relative;
@@ -48,6 +47,8 @@
48
47
  overflow-y: auto;
49
48
  overflow-wrap: break-word;
50
49
  flex: 1;
50
+ position: relative;
51
+ min-height: 32px;
51
52
  }
52
53
 
53
54
  .j-modal .modal-body p {