@blotch/dolan 0.0.0-stage → 22.2.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.
- package/dist/commands/add.d.ts +22 -0
- package/dist/commands/add.js +132 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/add.test.d.ts +1 -0
- package/dist/commands/add.test.js +209 -0
- package/dist/commands/add.test.js.map +1 -0
- package/dist/commands/diff.d.ts +13 -0
- package/dist/commands/diff.js +155 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/diff.test.d.ts +1 -0
- package/dist/commands/diff.test.js +327 -0
- package/dist/commands/diff.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +15 -0
- package/dist/commands/doctor.js +248 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +1 -0
- package/dist/commands/doctor.test.js +301 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/eject.d.ts +11 -0
- package/dist/commands/eject.js +55 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/eject.test.d.ts +1 -0
- package/dist/commands/eject.test.js +151 -0
- package/dist/commands/eject.test.js.map +1 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +73 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/info.test.d.ts +1 -0
- package/dist/commands/info.test.js +143 -0
- package/dist/commands/info.test.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +98 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/commands/init.test.js +154 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/list.d.ts +16 -0
- package/dist/commands/list.js +124 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/list.test.d.ts +1 -0
- package/dist/commands/list.test.js +285 -0
- package/dist/commands/list.test.js.map +1 -0
- package/dist/commands/outdated.d.ts +13 -0
- package/dist/commands/outdated.js +116 -0
- package/dist/commands/outdated.js.map +1 -0
- package/dist/commands/outdated.test.d.ts +1 -0
- package/dist/commands/outdated.test.js +208 -0
- package/dist/commands/outdated.test.js.map +1 -0
- package/dist/commands/remove.d.ts +18 -0
- package/dist/commands/remove.js +161 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/remove.test.d.ts +1 -0
- package/dist/commands/remove.test.js +361 -0
- package/dist/commands/remove.test.js.map +1 -0
- package/dist/commands/theme.d.ts +1 -0
- package/dist/commands/theme.js +66 -0
- package/dist/commands/theme.js.map +1 -0
- package/dist/commands/theme.test.d.ts +1 -0
- package/dist/commands/theme.test.js +133 -0
- package/dist/commands/theme.test.js.map +1 -0
- package/dist/commands/update.d.ts +31 -0
- package/dist/commands/update.js +601 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/update.test.d.ts +1 -0
- package/dist/commands/update.test.js +1233 -0
- package/dist/commands/update.test.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +48 -0
- package/dist/config.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/lockfile.d.ts +21 -0
- package/dist/lockfile.js +39 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +85 -0
- package/dist/registry.js.map +1 -0
- package/dist/registry.test.d.ts +1 -0
- package/dist/registry.test.js +95 -0
- package/dist/registry.test.js.map +1 -0
- package/dist/test-utils.d.ts +16 -0
- package/dist/test-utils.js +65 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/ui/components/accordion/accordion.test.ts +194 -0
- package/dist/ui/components/accordion/accordion.ts +173 -0
- package/dist/ui/components/accordion/index.ts +6 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +211 -0
- package/dist/ui/components/action-sheet/action-sheet.ts +353 -0
- package/dist/ui/components/action-sheet/index.ts +6 -0
- package/dist/ui/components/alert/alert.test.ts +48 -0
- package/dist/ui/components/alert/alert.ts +104 -0
- package/dist/ui/components/alert/index.ts +1 -0
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +141 -0
- package/dist/ui/components/alert-dialog/alert-dialog.ts +243 -0
- package/dist/ui/components/alert-dialog/index.ts +7 -0
- package/dist/ui/components/area-chart/area-chart-render.test.ts +121 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +158 -0
- package/dist/ui/components/area-chart/area-chart.ts +349 -0
- package/dist/ui/components/area-chart/index.ts +6 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.test.ts +10 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.ts +28 -0
- package/dist/ui/components/aspect-ratio/index.ts +1 -0
- package/dist/ui/components/avatar/avatar.test.ts +69 -0
- package/dist/ui/components/avatar/avatar.ts +131 -0
- package/dist/ui/components/avatar/index.ts +1 -0
- package/dist/ui/components/badge/badge.test.ts +49 -0
- package/dist/ui/components/badge/badge.ts +97 -0
- package/dist/ui/components/badge/index.ts +1 -0
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +174 -0
- package/dist/ui/components/bar-chart/bar-chart.test.ts +73 -0
- package/dist/ui/components/bar-chart/bar-chart.ts +327 -0
- package/dist/ui/components/bar-chart/index.ts +6 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +163 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +339 -0
- package/dist/ui/components/bottom-sheet/index.ts +1 -0
- package/dist/ui/components/button/button-render.test.ts +113 -0
- package/dist/ui/components/button/button.test.ts +70 -0
- package/dist/ui/components/button/button.ts +170 -0
- package/dist/ui/components/button/index.ts +6 -0
- package/dist/ui/components/button-group/button-group-render.test.ts +146 -0
- package/dist/ui/components/button-group/button-group.test.ts +54 -0
- package/dist/ui/components/button-group/button-group.ts +209 -0
- package/dist/ui/components/button-group/index.ts +8 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +174 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +96 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +355 -0
- package/dist/ui/components/candlestick-chart/index.ts +7 -0
- package/dist/ui/components/card/card.test.ts +110 -0
- package/dist/ui/components/card/card.ts +162 -0
- package/dist/ui/components/card/index.ts +8 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +188 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +549 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +1140 -0
- package/dist/ui/components/cartesian-chart/index.ts +18 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +59 -0
- package/dist/ui/components/checkbox/checkbox.ts +96 -0
- package/dist/ui/components/checkbox/index.ts +1 -0
- package/dist/ui/components/collapsible/collapsible.test.ts +112 -0
- package/dist/ui/components/collapsible/collapsible.ts +122 -0
- package/dist/ui/components/collapsible/index.ts +5 -0
- package/dist/ui/components/dialog/dialog.test.ts +157 -0
- package/dist/ui/components/dialog/dialog.ts +248 -0
- package/dist/ui/components/dialog/index.ts +7 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +60 -0
- package/dist/ui/components/empty-state/empty-state.ts +46 -0
- package/dist/ui/components/empty-state/index.ts +1 -0
- package/dist/ui/components/icon/icon.test.ts +52 -0
- package/dist/ui/components/icon/icon.ts +54 -0
- package/dist/ui/components/icon/icons.ts +569 -0
- package/dist/ui/components/icon/index.ts +2 -0
- package/dist/ui/components/input/index.ts +1 -0
- package/dist/ui/components/input/input.test.ts +134 -0
- package/dist/ui/components/input/input.ts +189 -0
- package/dist/ui/components/label/index.ts +1 -0
- package/dist/ui/components/label/label.test.ts +38 -0
- package/dist/ui/components/label/label.ts +24 -0
- package/dist/ui/components/line-chart/index.ts +6 -0
- package/dist/ui/components/line-chart/line-chart-render.test.ts +121 -0
- package/dist/ui/components/line-chart/line-chart.test.ts +81 -0
- package/dist/ui/components/line-chart/line-chart.ts +273 -0
- package/dist/ui/components/list/index.ts +1 -0
- package/dist/ui/components/list/list.test.ts +35 -0
- package/dist/ui/components/list/list.ts +71 -0
- package/dist/ui/components/nav-drawer/index.ts +8 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +267 -0
- package/dist/ui/components/nav-drawer/nav-drawer.ts +504 -0
- package/dist/ui/components/pagination/index.ts +1 -0
- package/dist/ui/components/pagination/pagination.test.ts +96 -0
- package/dist/ui/components/pagination/pagination.ts +147 -0
- package/dist/ui/components/progress/index.ts +1 -0
- package/dist/ui/components/progress/progress.test.ts +71 -0
- package/dist/ui/components/progress/progress.ts +118 -0
- package/dist/ui/components/radio-group/index.ts +1 -0
- package/dist/ui/components/radio-group/radio-group.test.ts +115 -0
- package/dist/ui/components/radio-group/radio-group.ts +129 -0
- package/dist/ui/components/scatter-chart/index.ts +6 -0
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +157 -0
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -0
- package/dist/ui/components/scatter-chart/scatter-chart.ts +277 -0
- package/dist/ui/components/scroll-area/index.ts +1 -0
- package/dist/ui/components/scroll-area/scroll-area.test.ts +40 -0
- package/dist/ui/components/scroll-area/scroll-area.ts +73 -0
- package/dist/ui/components/select/index.ts +1 -0
- package/dist/ui/components/select/select.test.ts +107 -0
- package/dist/ui/components/select/select.ts +146 -0
- package/dist/ui/components/separator/index.ts +1 -0
- package/dist/ui/components/separator/separator.test.ts +26 -0
- package/dist/ui/components/separator/separator.ts +45 -0
- package/dist/ui/components/sheet/index.ts +7 -0
- package/dist/ui/components/sheet/sheet.test.ts +44 -0
- package/dist/ui/components/sheet/sheet.ts +114 -0
- package/dist/ui/components/skeleton/index.ts +1 -0
- package/dist/ui/components/skeleton/skeleton.test.ts +13 -0
- package/dist/ui/components/skeleton/skeleton.ts +45 -0
- package/dist/ui/components/spinner/index.ts +1 -0
- package/dist/ui/components/spinner/spinner.test.ts +39 -0
- package/dist/ui/components/spinner/spinner.ts +83 -0
- package/dist/ui/components/switch/index.ts +1 -0
- package/dist/ui/components/switch/switch.test.ts +63 -0
- package/dist/ui/components/switch/switch.ts +98 -0
- package/dist/ui/components/tabs/index.ts +1 -0
- package/dist/ui/components/tabs/tabs.test.ts +199 -0
- package/dist/ui/components/tabs/tabs.ts +317 -0
- package/dist/ui/components/textarea/index.ts +1 -0
- package/dist/ui/components/textarea/textarea.test.ts +138 -0
- package/dist/ui/components/textarea/textarea.ts +221 -0
- package/dist/ui/components/toast/index.ts +9 -0
- package/dist/ui/components/toast/toast-state.test.ts +54 -0
- package/dist/ui/components/toast/toast-state.ts +46 -0
- package/dist/ui/components/toast/toast.test.ts +342 -0
- package/dist/ui/components/toast/toast.ts +527 -0
- package/dist/ui/components/toggle/index.ts +7 -0
- package/dist/ui/components/toggle/toggle.test.ts +186 -0
- package/dist/ui/components/toggle/toggle.ts +218 -0
- package/dist/ui/components/typography/index.ts +5 -0
- package/dist/ui/components/typography/typography.test.ts +55 -0
- package/dist/ui/components/typography/typography.ts +44 -0
- package/dist/ui/theme/dark.css +41 -0
- package/dist/ui/theme/default.css +56 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +124 -0
- package/dist/ui/theme/tailwind-plugin.ts +151 -0
- package/dist/ui/utils/animate.test.ts +324 -0
- package/dist/ui/utils/animate.ts +544 -0
- package/dist/ui/utils/cn.test.ts +27 -0
- package/dist/ui/utils/cn.ts +6 -0
- package/dist/utils/analyze.d.ts +32 -0
- package/dist/utils/analyze.js +109 -0
- package/dist/utils/analyze.js.map +1 -0
- package/dist/utils/analyze.test.d.ts +1 -0
- package/dist/utils/analyze.test.js +146 -0
- package/dist/utils/analyze.test.js.map +1 -0
- package/dist/utils/animate.d.ts +210 -0
- package/dist/utils/animate.js +423 -0
- package/dist/utils/animate.js.map +1 -0
- package/dist/utils/animate.test.d.ts +1 -0
- package/dist/utils/animate.test.js +365 -0
- package/dist/utils/animate.test.js.map +1 -0
- package/dist/utils/banner.d.ts +1 -0
- package/dist/utils/banner.js +15 -0
- package/dist/utils/banner.js.map +1 -0
- package/dist/utils/banner.test.d.ts +1 -0
- package/dist/utils/banner.test.js +29 -0
- package/dist/utils/banner.test.js.map +1 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +6 -0
- package/dist/utils/cn.js.map +1 -0
- package/dist/utils/cn.test.d.ts +1 -0
- package/dist/utils/cn.test.js +21 -0
- package/dist/utils/cn.test.js.map +1 -0
- package/dist/utils/format.d.ts +31 -0
- package/dist/utils/format.js +95 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/format.test.d.ts +1 -0
- package/dist/utils/format.test.js +71 -0
- package/dist/utils/format.test.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +16 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logger.test.d.ts +1 -0
- package/dist/utils/logger.test.js +36 -0
- package/dist/utils/logger.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +12 -0
- package/dist/utils/resolve-paths.js +29 -0
- package/dist/utils/resolve-paths.js.map +1 -0
- package/dist/utils/resolve-paths.test.d.ts +1 -0
- package/dist/utils/resolve-paths.test.js +52 -0
- package/dist/utils/resolve-paths.test.js.map +1 -0
- package/package.json +56 -4
- package/README.md +0 -3
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { addSafeAreaUtilities, blotchPlugin } from './tailwind-plugin';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* These tests exercise the utility generator in isolation with mock Tailwind
|
|
7
|
+
* plugin callbacks — no real Tailwind runtime is needed. We assert the exact
|
|
8
|
+
* CSS strings emitted so a regression in the `env()` / `calc()` wiring (which is
|
|
9
|
+
* what makes safe area work on Lynx) is caught immediately.
|
|
10
|
+
*/
|
|
11
|
+
const setup = () => {
|
|
12
|
+
const addUtilities = vi.fn();
|
|
13
|
+
const matchUtilities = vi.fn();
|
|
14
|
+
// Minimal spacing scale — enough to prove offset utilities read the theme.
|
|
15
|
+
const spacing = { '0': '0px', '2': '0.5rem', '4': '1rem' };
|
|
16
|
+
const theme = vi.fn((key: string) =>
|
|
17
|
+
key === 'spacing' ? spacing : undefined,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
addSafeAreaUtilities({
|
|
21
|
+
addUtilities: addUtilities as never,
|
|
22
|
+
matchUtilities: matchUtilities as never,
|
|
23
|
+
theme: theme as never,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return { addUtilities, matchUtilities, spacing };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe('addSafeAreaUtilities', () => {
|
|
30
|
+
it('registers bare padding utilities equal to the inset', () => {
|
|
31
|
+
const { addUtilities } = setup();
|
|
32
|
+
const utilities = addUtilities.mock.calls[0][0];
|
|
33
|
+
|
|
34
|
+
expect(utilities['.pt-safe']).toEqual({
|
|
35
|
+
'padding-top': 'env(safe-area-inset-top)',
|
|
36
|
+
});
|
|
37
|
+
expect(utilities['.pb-safe']).toEqual({
|
|
38
|
+
'padding-bottom': 'env(safe-area-inset-bottom)',
|
|
39
|
+
});
|
|
40
|
+
expect(utilities['.px-safe']).toEqual({
|
|
41
|
+
'padding-left': 'env(safe-area-inset-left)',
|
|
42
|
+
'padding-right': 'env(safe-area-inset-right)',
|
|
43
|
+
});
|
|
44
|
+
expect(utilities['.p-safe']).toEqual({
|
|
45
|
+
'padding-top': 'env(safe-area-inset-top)',
|
|
46
|
+
'padding-right': 'env(safe-area-inset-right)',
|
|
47
|
+
'padding-bottom': 'env(safe-area-inset-bottom)',
|
|
48
|
+
'padding-left': 'env(safe-area-inset-left)',
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('registers one offset family per side, seeded from the spacing scale', () => {
|
|
53
|
+
const { matchUtilities, spacing } = setup();
|
|
54
|
+
|
|
55
|
+
// One matchUtilities call per physical side.
|
|
56
|
+
expect(matchUtilities).toHaveBeenCalledTimes(4);
|
|
57
|
+
|
|
58
|
+
// Every call must feed off the theme spacing scale so the safe offsets stay
|
|
59
|
+
// in sync with the normal padding scale.
|
|
60
|
+
for (const [, options] of matchUtilities.mock.calls) {
|
|
61
|
+
expect(options).toEqual({ values: spacing });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('offset utilities compose base spacing with the inset via calc()', () => {
|
|
66
|
+
const { matchUtilities } = setup();
|
|
67
|
+
|
|
68
|
+
// Find the bottom family (`pb-safe`) and invoke its generator.
|
|
69
|
+
const bottomCall = matchUtilities.mock.calls.find(
|
|
70
|
+
([utils]) => 'pb-safe' in utils,
|
|
71
|
+
);
|
|
72
|
+
expect(bottomCall).toBeDefined();
|
|
73
|
+
|
|
74
|
+
const generate = bottomCall![0]['pb-safe'];
|
|
75
|
+
expect(generate('1rem')).toEqual({
|
|
76
|
+
'padding-bottom': 'calc(1rem + env(safe-area-inset-bottom))',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('blotchPlugin', () => {
|
|
82
|
+
it('runs the plugin setup callback, registering base styles and safe area utilities', () => {
|
|
83
|
+
// Reach the plugin's `handler` (the first arg to the tailwind `plugin()`
|
|
84
|
+
// factory) so its `addBase` + `addSafeAreaUtilities` calls are covered.
|
|
85
|
+
const addBase = vi.fn();
|
|
86
|
+
const addUtilities = vi.fn();
|
|
87
|
+
const matchUtilities = vi.fn();
|
|
88
|
+
const theme = vi.fn(() => ({ '4': '1rem' }));
|
|
89
|
+
|
|
90
|
+
// The tailwindcss `plugin()` factory returns a `{ handler, config }`
|
|
91
|
+
// object at runtime. Cast through unknown so TypeScript accepts the
|
|
92
|
+
// internal shape.
|
|
93
|
+
const factory = blotchPlugin as unknown as {
|
|
94
|
+
handler: (api: unknown) => void;
|
|
95
|
+
config?: unknown;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
factory.handler({
|
|
99
|
+
addBase,
|
|
100
|
+
addUtilities,
|
|
101
|
+
matchUtilities,
|
|
102
|
+
theme,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(addBase).toHaveBeenCalledWith({
|
|
106
|
+
'*': { 'border-color': 'var(--border)' },
|
|
107
|
+
});
|
|
108
|
+
// The safe area utilities came through — bare + matched variants added.
|
|
109
|
+
expect(addUtilities).toHaveBeenCalled();
|
|
110
|
+
expect(matchUtilities).toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('exposes a config object containing the theme extension colors', () => {
|
|
114
|
+
// The second argument to `plugin()` is a config object that Tailwind
|
|
115
|
+
// deep-merges into the user's config. Verify our theme extensions round-
|
|
116
|
+
// trip so the colors are exposed to the utility classes.
|
|
117
|
+
const factory = blotchPlugin as unknown as {
|
|
118
|
+
config?: { theme?: { extend?: { colors?: Record<string, unknown> } } };
|
|
119
|
+
};
|
|
120
|
+
const colors = factory.config?.theme?.extend?.colors;
|
|
121
|
+
expect(colors).toBeDefined();
|
|
122
|
+
expect(colors!['background']).toBe('var(--background)');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import plugin from 'tailwindcss/plugin';
|
|
2
|
+
import type { PluginAPI } from 'tailwindcss/types/config';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The four physical sides, paired with their safe-area `env()` inset. Used to
|
|
6
|
+
* generate the padding utilities below without repeating the mapping.
|
|
7
|
+
*/
|
|
8
|
+
const SAFE_AREA_SIDES = {
|
|
9
|
+
t: { prop: 'padding-top', inset: 'env(safe-area-inset-top)' },
|
|
10
|
+
r: { prop: 'padding-right', inset: 'env(safe-area-inset-right)' },
|
|
11
|
+
b: { prop: 'padding-bottom', inset: 'env(safe-area-inset-bottom)' },
|
|
12
|
+
l: { prop: 'padding-left', inset: 'env(safe-area-inset-left)' },
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Registers safe-area-aware padding utilities driven by CSS `env(safe-area-inset-*)`.
|
|
17
|
+
*
|
|
18
|
+
* Why `env()` works on Lynx: the native CSS engine parses `env(safe-area-inset-*)`
|
|
19
|
+
* as a length (see references/lynx .../css_style_utils.cc `GetEnvValue`), and its
|
|
20
|
+
* calc parser accepts `env()` *inside* `calc()` too — so `calc(1rem + env(...))`
|
|
21
|
+
* is valid on device. The inset values default to `0` until the native host app
|
|
22
|
+
* populates them, so every utility here degrades gracefully to plain padding
|
|
23
|
+
* (the offset variants) or to no padding (the bare variants).
|
|
24
|
+
*
|
|
25
|
+
* Two flavors:
|
|
26
|
+
* - Bare (`.pb-safe`, `.px-safe`, `.p-safe`, …) — padding equal to the inset alone.
|
|
27
|
+
* - Offset (`.pb-safe-4`, `.pt-safe-2`, …) — a spacing-scale value PLUS the inset,
|
|
28
|
+
* e.g. `calc(1rem + env(safe-area-inset-bottom))`. Use these when an element
|
|
29
|
+
* already wants base padding but must also clear the notch/home indicator
|
|
30
|
+
* (the toast wrapper is the canonical case).
|
|
31
|
+
*/
|
|
32
|
+
export const addSafeAreaUtilities = ({
|
|
33
|
+
addUtilities,
|
|
34
|
+
matchUtilities,
|
|
35
|
+
theme,
|
|
36
|
+
}: Pick<PluginAPI, 'addUtilities' | 'matchUtilities' | 'theme'>): void => {
|
|
37
|
+
const { t, r, b, l } = SAFE_AREA_SIDES;
|
|
38
|
+
|
|
39
|
+
// Bare utilities: padding = the inset itself.
|
|
40
|
+
addUtilities({
|
|
41
|
+
'.pt-safe': { [t.prop]: t.inset },
|
|
42
|
+
'.pr-safe': { [r.prop]: r.inset },
|
|
43
|
+
'.pb-safe': { [b.prop]: b.inset },
|
|
44
|
+
'.pl-safe': { [l.prop]: l.inset },
|
|
45
|
+
'.px-safe': { [l.prop]: l.inset, [r.prop]: r.inset },
|
|
46
|
+
'.py-safe': { [t.prop]: t.inset, [b.prop]: b.inset },
|
|
47
|
+
'.p-safe': {
|
|
48
|
+
[t.prop]: t.inset,
|
|
49
|
+
[r.prop]: r.inset,
|
|
50
|
+
[b.prop]: b.inset,
|
|
51
|
+
[l.prop]: l.inset,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Offset utilities: base spacing + the inset, one family per side. Generated
|
|
56
|
+
// from the theme spacing scale so `pb-safe-4`, `pt-safe-2`, etc. all exist and
|
|
57
|
+
// stay in sync with the rest of the padding scale.
|
|
58
|
+
const spacing = theme('spacing') as Record<string, string>;
|
|
59
|
+
for (const { prop, inset } of [t, r, b, l]) {
|
|
60
|
+
const side = prop.replace('padding-', '')[0]; // 't' | 'r' | 'b' | 'l'
|
|
61
|
+
matchUtilities(
|
|
62
|
+
{
|
|
63
|
+
[`p${side}-safe`]: (value) => ({
|
|
64
|
+
[prop]: `calc(${value} + ${inset})`,
|
|
65
|
+
}),
|
|
66
|
+
},
|
|
67
|
+
{ values: spacing },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Tailwind plugin that maps @blotch/ui CSS variables to semantic color utilities
|
|
74
|
+
* and registers safe-area-aware padding utilities.
|
|
75
|
+
*
|
|
76
|
+
* Usage in tailwind.config.ts:
|
|
77
|
+
* import { blotchPlugin } from '@blotch/ui/theme/tailwind-plugin';
|
|
78
|
+
* export default { plugins: [blotchPlugin] };
|
|
79
|
+
*
|
|
80
|
+
* Colors reference CSS variables DIRECTLY (`var(--x)`) rather than the shadcn/ui
|
|
81
|
+
* pattern of wrapping raw HSL channels in `hsl(var(--x) / <alpha-value>)`. Lynx's
|
|
82
|
+
* native CSS engine only parses comma-separated hsl(); Tailwind emits the
|
|
83
|
+
* space-separated form `hsl(var(--x) / 1)`, which Lynx silently drops (colors
|
|
84
|
+
* render transparent on device while looking fine in the web preview). Storing
|
|
85
|
+
* complete rgba() values in the variables (see theme/default.css) and
|
|
86
|
+
* referencing them directly avoids color-function parsing entirely. This matches
|
|
87
|
+
* the official React Lynx Tailwind example.
|
|
88
|
+
*
|
|
89
|
+
* Tradeoff: Tailwind opacity modifiers on semantic colors (bg-primary/50) don't
|
|
90
|
+
* work — the value is opaque with no separable channels. For the rare
|
|
91
|
+
* translucent-semantic need, add a dedicated rgba() variable (see
|
|
92
|
+
* `--destructive-subtle` → `destructive.subtle`) or use `opacity-*` on the element.
|
|
93
|
+
*
|
|
94
|
+
* Safe area: see `addSafeAreaUtilities` for the `*-safe` / `*-safe-<n>` padding
|
|
95
|
+
* utilities that keep content clear of notches and home indicators.
|
|
96
|
+
*/
|
|
97
|
+
export const blotchPlugin = plugin(
|
|
98
|
+
({ addBase, addUtilities, matchUtilities, theme }) => {
|
|
99
|
+
addBase({
|
|
100
|
+
'*': {
|
|
101
|
+
'border-color': 'var(--border)',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
addSafeAreaUtilities({ addUtilities, matchUtilities, theme });
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
theme: {
|
|
108
|
+
extend: {
|
|
109
|
+
colors: {
|
|
110
|
+
background: 'var(--background)',
|
|
111
|
+
foreground: 'var(--foreground)',
|
|
112
|
+
primary: {
|
|
113
|
+
DEFAULT: 'var(--primary)',
|
|
114
|
+
foreground: 'var(--primary-foreground)',
|
|
115
|
+
},
|
|
116
|
+
secondary: {
|
|
117
|
+
DEFAULT: 'var(--secondary)',
|
|
118
|
+
foreground: 'var(--secondary-foreground)',
|
|
119
|
+
},
|
|
120
|
+
muted: {
|
|
121
|
+
DEFAULT: 'var(--muted)',
|
|
122
|
+
foreground: 'var(--muted-foreground)',
|
|
123
|
+
},
|
|
124
|
+
accent: {
|
|
125
|
+
DEFAULT: 'var(--accent)',
|
|
126
|
+
foreground: 'var(--accent-foreground)',
|
|
127
|
+
},
|
|
128
|
+
destructive: {
|
|
129
|
+
DEFAULT: 'var(--destructive)',
|
|
130
|
+
foreground: 'var(--destructive-foreground)',
|
|
131
|
+
// Pre-composed translucent red for subtle destructive backgrounds,
|
|
132
|
+
// since bg-destructive/10 (opacity modifier) can't work here.
|
|
133
|
+
subtle: 'var(--destructive-subtle)',
|
|
134
|
+
},
|
|
135
|
+
border: 'var(--border)',
|
|
136
|
+
input: 'var(--input)',
|
|
137
|
+
ring: 'var(--ring)',
|
|
138
|
+
card: {
|
|
139
|
+
DEFAULT: 'var(--card)',
|
|
140
|
+
foreground: 'var(--card-foreground)',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
borderRadius: {
|
|
144
|
+
lg: 'var(--radius)',
|
|
145
|
+
md: 'calc(var(--radius) - 2px)',
|
|
146
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
);
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// Exercises every helper in animate.ts against a mock element with a spy on
|
|
2
|
+
// `.animate()`/.setStyle(). The Lynx animate() API doesn't run in jsdom
|
|
3
|
+
// (nothing renders), but the helpers still need to be called so their keyframe
|
|
4
|
+
// / options wiring is covered. Assert on the arguments handed to the mock —
|
|
5
|
+
// each helper's keyframe shape and duration/easing defaults are the meaningful
|
|
6
|
+
// behavior to lock in.
|
|
7
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
DURATION,
|
|
10
|
+
EASING,
|
|
11
|
+
SCALE,
|
|
12
|
+
directionalSlideIn,
|
|
13
|
+
fadeIn,
|
|
14
|
+
fadeOut,
|
|
15
|
+
overlayClose,
|
|
16
|
+
overlayOpen,
|
|
17
|
+
popIn,
|
|
18
|
+
popOut,
|
|
19
|
+
pressDown,
|
|
20
|
+
pressRelease,
|
|
21
|
+
pulse,
|
|
22
|
+
revealIn,
|
|
23
|
+
scaleIn,
|
|
24
|
+
scaleOut,
|
|
25
|
+
shake,
|
|
26
|
+
slideIn,
|
|
27
|
+
slideOut,
|
|
28
|
+
springTranslateX,
|
|
29
|
+
} from './animate';
|
|
30
|
+
|
|
31
|
+
const makeEl = () => ({
|
|
32
|
+
animate: vi.fn(() => ({ cancel: vi.fn() })),
|
|
33
|
+
setStyle: vi.fn(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('animate — constants', () => {
|
|
37
|
+
it('exposes DURATION, EASING, SCALE lookup tables', () => {
|
|
38
|
+
expect(DURATION.instant).toBe(100);
|
|
39
|
+
expect(EASING.decelerate).toContain('cubic-bezier');
|
|
40
|
+
expect(SCALE.popFrom).toBeCloseTo(0.6);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('animate — primitive helpers on null/undefined', () => {
|
|
45
|
+
it('every helper short-circuits when the element is null', () => {
|
|
46
|
+
expect(fadeIn(null)).toBeUndefined();
|
|
47
|
+
expect(fadeOut(null)).toBeUndefined();
|
|
48
|
+
expect(scaleIn(null)).toBeUndefined();
|
|
49
|
+
expect(scaleOut(null)).toBeUndefined();
|
|
50
|
+
expect(slideIn(null, 'up')).toBeUndefined();
|
|
51
|
+
expect(slideOut(null, 'up')).toBeUndefined();
|
|
52
|
+
expect(popIn(null)).toBeUndefined();
|
|
53
|
+
expect(popOut(null)).toBeUndefined();
|
|
54
|
+
expect(pulse(null)).toBeUndefined();
|
|
55
|
+
expect(shake(null)).toBeUndefined();
|
|
56
|
+
expect(springTranslateX(null, 0, 10)).toBeUndefined();
|
|
57
|
+
expect(revealIn(null)).toBeUndefined();
|
|
58
|
+
expect(directionalSlideIn(null, 1)).toBeUndefined();
|
|
59
|
+
// Press helpers: an element without setStyle also short-circuits.
|
|
60
|
+
expect(pressDown(null)).toBeUndefined();
|
|
61
|
+
expect(pressRelease(null)).toBeUndefined();
|
|
62
|
+
expect(pressDown({})).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('animate — primitive helpers on a real element', () => {
|
|
67
|
+
it('fadeIn animates opacity 0 → 1 with the default duration/easing', () => {
|
|
68
|
+
const el = makeEl();
|
|
69
|
+
fadeIn(el);
|
|
70
|
+
const [keyframes, options] = el.animate.mock.calls[0];
|
|
71
|
+
expect(keyframes).toEqual([{ opacity: 0 }, { opacity: 1 }]);
|
|
72
|
+
expect(options.duration).toBe(DURATION.normal);
|
|
73
|
+
expect(options.easing).toBe(EASING.decelerate);
|
|
74
|
+
expect(options.fill).toBe('forwards');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('fadeIn honors custom duration / easing / fill', () => {
|
|
78
|
+
const el = makeEl();
|
|
79
|
+
fadeIn(el, { duration: 999, easing: 'linear', fill: 'both' });
|
|
80
|
+
const [, options] = el.animate.mock.calls[0];
|
|
81
|
+
expect(options).toEqual({
|
|
82
|
+
duration: 999,
|
|
83
|
+
easing: 'linear',
|
|
84
|
+
fill: 'both',
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('fadeOut animates opacity 1 → 0 with the accelerate default easing', () => {
|
|
89
|
+
const el = makeEl();
|
|
90
|
+
fadeOut(el);
|
|
91
|
+
const [keyframes, options] = el.animate.mock.calls[0];
|
|
92
|
+
expect(keyframes).toEqual([{ opacity: 1 }, { opacity: 0 }]);
|
|
93
|
+
expect(options.easing).toBe(EASING.accelerate);
|
|
94
|
+
expect(options.duration).toBe(DURATION.fast);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('scaleIn uses SCALE.dialogFrom by default and honors overrides', () => {
|
|
98
|
+
const el = makeEl();
|
|
99
|
+
scaleIn(el);
|
|
100
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
101
|
+
expect(keyframes[0].transform).toContain(`scale(${SCALE.dialogFrom})`);
|
|
102
|
+
|
|
103
|
+
const el2 = makeEl();
|
|
104
|
+
scaleIn(el2, { fromScale: 0.5, fromY: 20 });
|
|
105
|
+
const [kf2] = el2.animate.mock.calls[0];
|
|
106
|
+
expect(kf2[0].transform).toContain('scale(0.5)');
|
|
107
|
+
expect(kf2[0].transform).toContain('translateY(20px)');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('scaleOut animates towards the dialog exit transform', () => {
|
|
111
|
+
const el = makeEl();
|
|
112
|
+
scaleOut(el);
|
|
113
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
114
|
+
expect(keyframes[1].transform).toContain(`scale(${SCALE.dialogTo})`);
|
|
115
|
+
|
|
116
|
+
const el2 = makeEl();
|
|
117
|
+
scaleOut(el2, { toScale: 0.7, toY: 40 });
|
|
118
|
+
const [kf2] = el2.animate.mock.calls[0];
|
|
119
|
+
expect(kf2[1].transform).toContain('scale(0.7)');
|
|
120
|
+
expect(kf2[1].transform).toContain('translateY(40px)');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it.each([
|
|
124
|
+
['up', 'translateY(100%)'],
|
|
125
|
+
['down', 'translateY(-100%)'],
|
|
126
|
+
['left', 'translateX(-100%)'],
|
|
127
|
+
['right', 'translateX(100%)'],
|
|
128
|
+
] as const)('slideIn from %s starts at %s', (direction, expected) => {
|
|
129
|
+
const el = makeEl();
|
|
130
|
+
slideIn(el, direction);
|
|
131
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
132
|
+
expect(keyframes[0].transform).toBe(expected);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it.each([
|
|
136
|
+
['up', 'translateY(-100%)'],
|
|
137
|
+
['down', 'translateY(100%)'],
|
|
138
|
+
['left', 'translateX(-100%)'],
|
|
139
|
+
['right', 'translateX(100%)'],
|
|
140
|
+
] as const)('slideOut to %s ends at %s', (direction, expected) => {
|
|
141
|
+
const el = makeEl();
|
|
142
|
+
slideOut(el, direction);
|
|
143
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
144
|
+
expect(keyframes[1].transform).toBe(expected);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('popIn defaults from SCALE.popFrom and honors fromScale', () => {
|
|
148
|
+
const el = makeEl();
|
|
149
|
+
popIn(el);
|
|
150
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
151
|
+
expect(keyframes[0].transform).toContain(`scale(${SCALE.popFrom})`);
|
|
152
|
+
|
|
153
|
+
const el2 = makeEl();
|
|
154
|
+
popIn(el2, { fromScale: 0.4 });
|
|
155
|
+
const [kf2] = el2.animate.mock.calls[0];
|
|
156
|
+
expect(kf2[0].transform).toContain('scale(0.4)');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('popOut defaults to 0.8 and honors toScale', () => {
|
|
160
|
+
const el = makeEl();
|
|
161
|
+
popOut(el);
|
|
162
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
163
|
+
expect(keyframes[1].transform).toContain('scale(0.8)');
|
|
164
|
+
|
|
165
|
+
const el2 = makeEl();
|
|
166
|
+
popOut(el2, { toScale: 0.3 });
|
|
167
|
+
const [kf2] = el2.animate.mock.calls[0];
|
|
168
|
+
expect(kf2[1].transform).toContain('scale(0.3)');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('pulse loops opacity indefinitely with a 1500ms default', () => {
|
|
172
|
+
const el = makeEl();
|
|
173
|
+
pulse(el);
|
|
174
|
+
const [keyframes, options] = el.animate.mock.calls[0];
|
|
175
|
+
expect(keyframes).toEqual([
|
|
176
|
+
{ opacity: 1 },
|
|
177
|
+
{ opacity: 0.5 },
|
|
178
|
+
{ opacity: 1 },
|
|
179
|
+
]);
|
|
180
|
+
expect(options.duration).toBe(1500);
|
|
181
|
+
expect(options.iterations).toBe(Infinity);
|
|
182
|
+
|
|
183
|
+
const el2 = makeEl();
|
|
184
|
+
pulse(el2, { duration: 400 });
|
|
185
|
+
const [, opts2] = el2.animate.mock.calls[0];
|
|
186
|
+
expect(opts2.duration).toBe(400);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('shake produces a six-keyframe X wobble with forwards fill', () => {
|
|
190
|
+
const el = makeEl();
|
|
191
|
+
shake(el);
|
|
192
|
+
const [keyframes, options] = el.animate.mock.calls[0];
|
|
193
|
+
expect(keyframes).toHaveLength(6);
|
|
194
|
+
expect(keyframes[0].transform).toBe('translateX(0)');
|
|
195
|
+
expect(options.fill).toBe('forwards');
|
|
196
|
+
|
|
197
|
+
const el2 = makeEl();
|
|
198
|
+
shake(el2, { duration: 999, easing: 'linear' });
|
|
199
|
+
const [, opts2] = el2.animate.mock.calls[0];
|
|
200
|
+
expect(opts2.duration).toBe(999);
|
|
201
|
+
expect(opts2.easing).toBe('linear');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('springTranslateX interpolates through the midpoint with a squish', () => {
|
|
205
|
+
const el = makeEl();
|
|
206
|
+
springTranslateX(el, 0, 20);
|
|
207
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
208
|
+
expect(keyframes[0].transform).toContain('translateX(0px)');
|
|
209
|
+
expect(keyframes[1].transform).toContain('translateX(10px)');
|
|
210
|
+
expect(keyframes[1].transform).toContain('scale(1.1)');
|
|
211
|
+
expect(keyframes[2].transform).toContain('translateX(20px)');
|
|
212
|
+
|
|
213
|
+
const el2 = makeEl();
|
|
214
|
+
springTranslateX(el2, 0, 20, {
|
|
215
|
+
duration: 500,
|
|
216
|
+
easing: 'ease',
|
|
217
|
+
fill: 'none',
|
|
218
|
+
});
|
|
219
|
+
const [, opts2] = el2.animate.mock.calls[0];
|
|
220
|
+
expect(opts2).toEqual({ duration: 500, easing: 'ease', fill: 'none' });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('revealIn slides in from a small Y offset', () => {
|
|
224
|
+
const el = makeEl();
|
|
225
|
+
revealIn(el);
|
|
226
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
227
|
+
expect(keyframes[0].transform).toContain('translateY(8px)');
|
|
228
|
+
|
|
229
|
+
const el2 = makeEl();
|
|
230
|
+
revealIn(el2, { fromY: 24 });
|
|
231
|
+
const [kf2] = el2.animate.mock.calls[0];
|
|
232
|
+
expect(kf2[0].transform).toContain('translateY(24px)');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('directionalSlideIn respects the direction sign', () => {
|
|
236
|
+
const el = makeEl();
|
|
237
|
+
directionalSlideIn(el, 1);
|
|
238
|
+
const [keyframes] = el.animate.mock.calls[0];
|
|
239
|
+
expect(keyframes[0].transform).toContain('translateX(24px)');
|
|
240
|
+
|
|
241
|
+
const el2 = makeEl();
|
|
242
|
+
directionalSlideIn(el2, -1);
|
|
243
|
+
const [kf2] = el2.animate.mock.calls[0];
|
|
244
|
+
expect(kf2[0].transform).toContain('translateX(-24px)');
|
|
245
|
+
|
|
246
|
+
const el3 = makeEl();
|
|
247
|
+
directionalSlideIn(el3, 1, { distance: 100 });
|
|
248
|
+
const [kf3] = el3.animate.mock.calls[0];
|
|
249
|
+
expect(kf3[0].transform).toContain('translateX(100px)');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('animate — press helpers', () => {
|
|
254
|
+
it('pressDown writes a transition + scale via setStyle', () => {
|
|
255
|
+
const el = makeEl();
|
|
256
|
+
const handle = pressDown(el);
|
|
257
|
+
expect(el.setStyle).toHaveBeenCalledWith(
|
|
258
|
+
'transition',
|
|
259
|
+
expect.stringContaining(`transform ${DURATION.instant}ms`),
|
|
260
|
+
);
|
|
261
|
+
expect(el.setStyle).toHaveBeenCalledWith(
|
|
262
|
+
'transform',
|
|
263
|
+
`scale(${SCALE.pressDown})`,
|
|
264
|
+
);
|
|
265
|
+
// The returned handle exposes a no-op cancel() for API parity.
|
|
266
|
+
expect(handle).toBeDefined();
|
|
267
|
+
expect(() => handle!.cancel()).not.toThrow();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('pressDown honors a custom scale', () => {
|
|
271
|
+
const el = makeEl();
|
|
272
|
+
pressDown(el, 0.5);
|
|
273
|
+
expect(el.setStyle).toHaveBeenCalledWith('transform', 'scale(0.5)');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('pressRelease returns to scale(1) via a spring easing transition', () => {
|
|
277
|
+
const el = makeEl();
|
|
278
|
+
pressRelease(el);
|
|
279
|
+
expect(el.setStyle).toHaveBeenCalledWith('transform', 'scale(1)');
|
|
280
|
+
expect(el.setStyle).toHaveBeenCalledWith(
|
|
281
|
+
'transition',
|
|
282
|
+
expect.stringContaining(EASING.spring),
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('animate — composite overlay helpers', () => {
|
|
288
|
+
it.each(['center', 'bottom', 'left', 'right'] as const)(
|
|
289
|
+
'overlayOpen(%s) fades the backdrop and staggers the panel entrance',
|
|
290
|
+
(type) => {
|
|
291
|
+
vi.useFakeTimers();
|
|
292
|
+
const backdrop = makeEl();
|
|
293
|
+
const panel = makeEl();
|
|
294
|
+
|
|
295
|
+
overlayOpen(backdrop, panel, type);
|
|
296
|
+
|
|
297
|
+
// Backdrop fades in immediately.
|
|
298
|
+
expect(backdrop.animate).toHaveBeenCalled();
|
|
299
|
+
// Panel is scheduled 30ms later — advance the timer to fire it.
|
|
300
|
+
vi.advanceTimersByTime(30);
|
|
301
|
+
expect(panel.animate).toHaveBeenCalled();
|
|
302
|
+
vi.useRealTimers();
|
|
303
|
+
},
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
it.each([
|
|
307
|
+
['center', 180],
|
|
308
|
+
['bottom', DURATION.normal],
|
|
309
|
+
['left', DURATION.normal],
|
|
310
|
+
['right', DURATION.normal],
|
|
311
|
+
] as const)(
|
|
312
|
+
'overlayClose(%s) returns the correct cleanup duration and animates both nodes',
|
|
313
|
+
(type, expectedDuration) => {
|
|
314
|
+
const backdrop = makeEl();
|
|
315
|
+
const panel = makeEl();
|
|
316
|
+
|
|
317
|
+
const duration = overlayClose(backdrop, panel, type);
|
|
318
|
+
|
|
319
|
+
expect(duration).toBe(expectedDuration);
|
|
320
|
+
expect(panel.animate).toHaveBeenCalled();
|
|
321
|
+
expect(backdrop.animate).toHaveBeenCalled();
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
});
|