@blotch/dolan 22.2.4 → 22.2.5
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.test.js +2 -0
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +78 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +167 -26
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.test.js +4 -7
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/update.d.ts +3 -1
- package/dist/commands/update.js +78 -15
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +257 -2
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +10 -0
- package/dist/lockfile.js +20 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.js +43 -3
- package/dist/lockfile.test.js.map +1 -1
- package/dist/registry.js +73 -11
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +16 -3
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +24 -2
- package/dist/test-utils.js +41 -2
- package/dist/test-utils.js.map +1 -1
- package/dist/ui/boolean-inputs.test.ts +76 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +370 -1
- package/dist/ui/components/action-sheet/action-sheet.ts +306 -28
- package/dist/ui/components/action-sheet/index.ts +1 -0
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +552 -8
- package/dist/ui/components/alert-dialog/alert-dialog.ts +644 -36
- package/dist/ui/components/alert-dialog/index.ts +5 -0
- package/dist/ui/components/area-chart/area-chart.ts +3 -2
- package/dist/ui/components/badge/badge.ts +2 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +968 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1021 -95
- package/dist/ui/components/bottom-sheet/index.ts +7 -1
- package/dist/ui/components/button/button.test.ts +36 -1
- package/dist/ui/components/button/button.ts +17 -7
- package/dist/ui/components/button-group/button-group.test.ts +53 -9
- package/dist/ui/components/button-group/button-group.ts +67 -21
- package/dist/ui/components/card/card.test.ts +16 -0
- package/dist/ui/components/card/card.ts +2 -1
- package/dist/ui/components/chart/chart-presets.ts +6 -6
- package/dist/ui/components/chart/chart.test.ts +14 -0
- package/dist/ui/components/chart/chart.ts +10 -3
- package/dist/ui/components/checkbox/checkbox.ts +2 -1
- package/dist/ui/components/collapsible/collapsible.ts +2 -1
- package/dist/ui/components/dialog/dialog.ts +6 -6
- package/dist/ui/components/dock/dock-scroll.ts +9 -0
- package/dist/ui/components/dock/dock-search.test.ts +594 -0
- package/dist/ui/components/dock/dock.test.ts +4 -1
- package/dist/ui/components/dock/dock.ts +529 -51
- package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
- package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
- package/dist/ui/components/dynamic-color/index.ts +5 -0
- package/dist/ui/components/glass/glass.test.ts +39 -1
- package/dist/ui/components/glass/glass.ts +87 -17
- package/dist/ui/components/glass/index.ts +1 -0
- package/dist/ui/components/icon/icon-theme.test.ts +28 -0
- package/dist/ui/components/icon/icon-theme.ts +38 -7
- package/dist/ui/components/input/input.ts +2 -1
- package/dist/ui/components/label/label.ts +8 -2
- package/dist/ui/components/line-chart/line-chart.ts +3 -2
- package/dist/ui/components/list/list.ts +5 -4
- package/dist/ui/components/menu/menu-popup.test.ts +18 -14
- package/dist/ui/components/menu/menu-popup.ts +24 -3
- package/dist/ui/components/menu/menu.test.ts +99 -5
- package/dist/ui/components/menu/menu.ts +74 -34
- package/dist/ui/components/nav-drawer/nav-drawer.ts +3 -2
- package/dist/ui/components/navigation-bar/index.ts +1 -0
- package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
- package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
- package/dist/ui/components/popover/index.ts +9 -0
- package/dist/ui/components/popover/popover-panel.test.ts +735 -0
- package/dist/ui/components/popover/popover-panel.ts +603 -0
- package/dist/ui/components/popover/popover.test.ts +286 -0
- package/dist/ui/components/popover/popover.ts +207 -0
- package/dist/ui/components/progress/progress.ts +2 -1
- package/dist/ui/components/radio-group/radio-group.ts +2 -1
- package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
- package/dist/ui/components/search-field/index.ts +15 -0
- package/dist/ui/components/search-field/keyboard.test.ts +147 -0
- package/dist/ui/components/search-field/keyboard.ts +123 -0
- package/dist/ui/components/search-field/search-field.test.ts +549 -0
- package/dist/ui/components/search-field/search-field.ts +628 -0
- package/dist/ui/components/segmented-control/index.ts +8 -0
- package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
- package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
- package/dist/ui/components/select/select.ts +2 -1
- package/dist/ui/components/sheet/sheet.test.ts +90 -3
- package/dist/ui/components/sheet/sheet.ts +59 -4
- package/dist/ui/components/slider/index.ts +1 -0
- package/dist/ui/components/slider/slider.test.ts +695 -0
- package/dist/ui/components/slider/slider.ts +901 -0
- package/dist/ui/components/switch/index.ts +1 -1
- package/dist/ui/components/switch/switch.test.ts +464 -48
- package/dist/ui/components/switch/switch.ts +613 -43
- package/dist/ui/components/tabs/index.ts +7 -1
- package/dist/ui/components/tabs/tabs.test.ts +278 -2
- package/dist/ui/components/tabs/tabs.ts +233 -30
- package/dist/ui/components/textarea/textarea.ts +2 -1
- package/dist/ui/components/toggle/index.ts +1 -0
- package/dist/ui/components/toggle/toggle.test.ts +310 -4
- package/dist/ui/components/toggle/toggle.ts +241 -26
- package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
- package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
- package/dist/ui/components/toolbar/index.ts +12 -0
- package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
- package/dist/ui/components/toolbar/toolbar.ts +535 -0
- package/dist/utils/analyze.d.ts +10 -5
- package/dist/utils/analyze.js +3 -2
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/installed.d.ts +13 -1
- package/dist/utils/installed.js +17 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +39 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -5
- package/dist/utils/tailwind.js +53 -14
- package/dist/utils/tailwind.js.map +1 -1
- package/dist/utils/tailwind.test.js +85 -22
- package/dist/utils/tailwind.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Injector, type Renderer2 } from '@angular/core';
|
|
2
|
+
import { LynxSystemInfo } from '@blotch/angular-lynx';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
type KeyboardScreen,
|
|
6
|
+
keyboardHeightOf,
|
|
7
|
+
keyboardLift,
|
|
8
|
+
keyboardScreen,
|
|
9
|
+
measureForKeyboard,
|
|
10
|
+
} from './keyboard';
|
|
11
|
+
|
|
12
|
+
const IPHONE: KeyboardScreen = {
|
|
13
|
+
platform: 'iOS',
|
|
14
|
+
pixelRatio: 3,
|
|
15
|
+
screenHeight: 874,
|
|
16
|
+
};
|
|
17
|
+
const PIXEL: KeyboardScreen = {
|
|
18
|
+
platform: 'Android',
|
|
19
|
+
pixelRatio: 2.625,
|
|
20
|
+
screenHeight: 914,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The Lynx function that runs an element's UI methods, which the testing
|
|
25
|
+
* library defines on the global object.
|
|
26
|
+
*/
|
|
27
|
+
type InvokeUIMethod = {
|
|
28
|
+
__InvokeUIMethod: (
|
|
29
|
+
element: unknown,
|
|
30
|
+
method: string,
|
|
31
|
+
params: Record<string, unknown>,
|
|
32
|
+
callback: (result: { code: number; data?: unknown }) => void,
|
|
33
|
+
) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('keyboardHeightOf', () => {
|
|
41
|
+
it('takes iOS’s points as they are', () => {
|
|
42
|
+
expect(keyboardHeightOf({ detail: { height: 336 } }, IPHONE)).toBe(336);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('turns Android’s physical pixels into px', () => {
|
|
46
|
+
expect(keyboardHeightOf({ detail: { height: 819 } }, PIXEL)).toBe(312);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('keyboardLift', () => {
|
|
51
|
+
it('measures iOS’s keyboard up from the bottom of the screen', () => {
|
|
52
|
+
// The keyboard's top is at 874 - 336 = 538; the field's bottom clears it
|
|
53
|
+
// by the gap.
|
|
54
|
+
expect(keyboardLift({ bottom: 820 }, { bottom: 840 }, 336, IPHONE, 8)).toBe(
|
|
55
|
+
290,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('measures Android’s up from the bottom of the Lynx view', () => {
|
|
60
|
+
// The keyboard's top is at 890 - 312 = 578.
|
|
61
|
+
expect(keyboardLift({ bottom: 855 }, { bottom: 890 }, 312, PIXEL, 8)).toBe(
|
|
62
|
+
285,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('is 0 when the keyboard doesn’t reach the element, or is down', () => {
|
|
67
|
+
expect(keyboardLift({ bottom: 300 }, { bottom: 874 }, 336, IPHONE, 8)).toBe(
|
|
68
|
+
0,
|
|
69
|
+
);
|
|
70
|
+
expect(keyboardLift({ bottom: 820 }, { bottom: 874 }, 0, IPHONE, 8)).toBe(
|
|
71
|
+
0,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('keyboardScreen', () => {
|
|
77
|
+
it('is LynxSystemInfo where there is one', () => {
|
|
78
|
+
const info = { platform: 'iOS', pixelRatio: 3, screenHeight: 874 };
|
|
79
|
+
const injector = Injector.create({
|
|
80
|
+
providers: [{ provide: LynxSystemInfo, useValue: info }],
|
|
81
|
+
});
|
|
82
|
+
expect(keyboardScreen(injector)).toBe(info);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('is null where LynxSystemInfo has no SystemInfo to read, and throws', () => {
|
|
86
|
+
const injector = Injector.create({
|
|
87
|
+
providers: [
|
|
88
|
+
{
|
|
89
|
+
provide: LynxSystemInfo,
|
|
90
|
+
useFactory: () => {
|
|
91
|
+
throw new Error('SystemInfo is not available in this environment');
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
expect(keyboardScreen(injector)).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('measureForKeyboard', () => {
|
|
101
|
+
/** A page holding a column holding the field, as Lynx elements. */
|
|
102
|
+
const page = { element: 'page', tagName: 'page' };
|
|
103
|
+
const column = { element: 'column' };
|
|
104
|
+
const field = { element: 'field' };
|
|
105
|
+
// The page has a parent of its own in the testing library, which the
|
|
106
|
+
// walk stops short of.
|
|
107
|
+
const parents = new Map<unknown, unknown>([
|
|
108
|
+
[field, column],
|
|
109
|
+
[column, page],
|
|
110
|
+
[page, { element: 'document' }],
|
|
111
|
+
]);
|
|
112
|
+
const renderer = {
|
|
113
|
+
parentNode: (node: unknown) => parents.get(node) ?? null,
|
|
114
|
+
} as unknown as Renderer2;
|
|
115
|
+
|
|
116
|
+
it('measures the element and the page it’s in, on screen', async () => {
|
|
117
|
+
const invoke = vi
|
|
118
|
+
.spyOn(globalThis as unknown as InvokeUIMethod, '__InvokeUIMethod')
|
|
119
|
+
.mockImplementation((element, _method, _params, callback) =>
|
|
120
|
+
callback({
|
|
121
|
+
code: 0,
|
|
122
|
+
data: { bottom: element === 'page' ? 874 : 820 },
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await expect(measureForKeyboard(renderer, field)).resolves.toEqual({
|
|
127
|
+
element: expect.objectContaining({ bottom: 820 }),
|
|
128
|
+
page: expect.objectContaining({ bottom: 874 }),
|
|
129
|
+
});
|
|
130
|
+
expect(invoke).toHaveBeenCalledWith(
|
|
131
|
+
'field',
|
|
132
|
+
'boundingClientRect',
|
|
133
|
+
{ relativeTo: 'screen' },
|
|
134
|
+
expect.any(Function),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('is null where an element can’t be measured', async () => {
|
|
139
|
+
vi.spyOn(
|
|
140
|
+
globalThis as unknown as InvokeUIMethod,
|
|
141
|
+
'__InvokeUIMethod',
|
|
142
|
+
).mockImplementation((_element, _method, _params, callback) =>
|
|
143
|
+
callback({ code: 1 }),
|
|
144
|
+
);
|
|
145
|
+
await expect(measureForKeyboard(renderer, field)).resolves.toBeNull();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type Injector, type Renderer2 } from '@angular/core';
|
|
2
|
+
import { LynxSystemInfo, MainThreadElement } from '@blotch/angular-lynx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* How far above the keyboard a field that rises clear of it sits, in px: the
|
|
6
|
+
* gap the dock keeps from the home indicator.
|
|
7
|
+
*/
|
|
8
|
+
export const KEYBOARD_GAP = 8;
|
|
9
|
+
|
|
10
|
+
/** What a field's `<input>` sends with its `keyboardheightchange` event. */
|
|
11
|
+
export type KeyboardHeightEvent = { detail: { height: number } };
|
|
12
|
+
|
|
13
|
+
/** The platform, as far as the keyboard's geometry needs it. */
|
|
14
|
+
export type KeyboardScreen = {
|
|
15
|
+
readonly platform: string;
|
|
16
|
+
readonly pixelRatio: number;
|
|
17
|
+
/** The screen's height in px. */
|
|
18
|
+
readonly screenHeight: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The platform from LynxSystemInfo, or null where there's no SystemInfo to
|
|
23
|
+
* read: LynxSystemInfo throws in unit tests without one. Looked up when the
|
|
24
|
+
* keyboard shows, which is when it's needed.
|
|
25
|
+
*/
|
|
26
|
+
export const keyboardScreen = (injector: Injector): KeyboardScreen | null => {
|
|
27
|
+
try {
|
|
28
|
+
return injector.get(LynxSystemInfo);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** A box on screen, as far as the keyboard's geometry needs it, in px. */
|
|
35
|
+
export type KeyboardRect = { readonly bottom: number };
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The keyboard's height in px, from an `<input>`'s `keyboardheightchange`
|
|
39
|
+
* event, which arrives on the main thread while the input is focused: as the
|
|
40
|
+
* keyboard shows, when it changes size (the emoji keyboard, the predictive
|
|
41
|
+
* bar), and with 0 as it hides.
|
|
42
|
+
*
|
|
43
|
+
* Lynx measures it differently on each platform. iOS sends points, measured
|
|
44
|
+
* up from the bottom of the window. Android sends physical pixels, measured
|
|
45
|
+
* up from the bottom of the Lynx view: an app whose window shrinks above the
|
|
46
|
+
* keyboard, as the Android app `ng build --platform android` makes does,
|
|
47
|
+
* gets 0 once it has.
|
|
48
|
+
*/
|
|
49
|
+
export const keyboardHeightOf = (
|
|
50
|
+
event: KeyboardHeightEvent,
|
|
51
|
+
screen: KeyboardScreen,
|
|
52
|
+
): number =>
|
|
53
|
+
screen.platform === 'Android'
|
|
54
|
+
? event.detail.height / screen.pixelRatio
|
|
55
|
+
: event.detail.height;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* How far an element has to rise for its bottom edge to clear the keyboard
|
|
59
|
+
* by `gap`, in px: 0 when the keyboard doesn't reach it. `element` and
|
|
60
|
+
* `page` are their boxes on screen, `page` the Lynx page's, whose bottom is
|
|
61
|
+
* the Lynx view's: iOS measures the keyboard from the window's bottom edge,
|
|
62
|
+
* and Android from the Lynx view's (see keyboardHeightOf()).
|
|
63
|
+
*/
|
|
64
|
+
export const keyboardLift = (
|
|
65
|
+
element: KeyboardRect,
|
|
66
|
+
page: KeyboardRect,
|
|
67
|
+
keyboard: number,
|
|
68
|
+
screen: KeyboardScreen,
|
|
69
|
+
gap: number,
|
|
70
|
+
): number => {
|
|
71
|
+
if (keyboard <= 0) return 0;
|
|
72
|
+
const keyboardTop =
|
|
73
|
+
screen.platform === 'iOS'
|
|
74
|
+
? screen.screenHeight - keyboard
|
|
75
|
+
: page.bottom - keyboard;
|
|
76
|
+
return Math.max(0, Math.round(element.bottom + gap - keyboardTop));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** AngularLynx's wrapper of a Lynx element, around the element itself. */
|
|
80
|
+
type LynxElementRef = { readonly element: unknown; readonly tagName?: string };
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* An element's box on screen, or null where it can't be measured (the web
|
|
84
|
+
* preview measures from the `<lynx-view>`, which has no keyboard to clear).
|
|
85
|
+
* Lynx gives a script an element's position only through its
|
|
86
|
+
* `boundingClientRect` UI method, which answers later.
|
|
87
|
+
*/
|
|
88
|
+
const measureOnScreen = async (
|
|
89
|
+
element: LynxElementRef,
|
|
90
|
+
): Promise<KeyboardRect | null> => {
|
|
91
|
+
try {
|
|
92
|
+
return await new MainThreadElement(
|
|
93
|
+
element.element as ConstructorParameters<typeof MainThreadElement>[0],
|
|
94
|
+
).getBoundingClientRect({ relativeTo: 'screen' });
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Measures `element` and the page it's in, the `<page>` above it, for
|
|
102
|
+
* keyboardLift(). Null when either can't be measured.
|
|
103
|
+
*/
|
|
104
|
+
export const measureForKeyboard = async (
|
|
105
|
+
renderer: Renderer2,
|
|
106
|
+
element: LynxElementRef,
|
|
107
|
+
): Promise<{ element: KeyboardRect; page: KeyboardRect } | null> => {
|
|
108
|
+
let page = element;
|
|
109
|
+
for (
|
|
110
|
+
let parent = renderer.parentNode(page) as LynxElementRef | null;
|
|
111
|
+
parent && page.tagName !== 'page';
|
|
112
|
+
parent = renderer.parentNode(parent) as LynxElementRef | null
|
|
113
|
+
) {
|
|
114
|
+
page = parent;
|
|
115
|
+
}
|
|
116
|
+
const [elementRect, pageRect] = await Promise.all([
|
|
117
|
+
measureOnScreen(element),
|
|
118
|
+
measureOnScreen(page),
|
|
119
|
+
]);
|
|
120
|
+
return elementRect && pageRect
|
|
121
|
+
? { element: elementRect, page: pageRect }
|
|
122
|
+
: null;
|
|
123
|
+
};
|