@blotch/dolan 22.2.4 → 22.2.6

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 (124) hide show
  1. package/dist/commands/add.test.js +2 -0
  2. package/dist/commands/add.test.js.map +1 -1
  3. package/dist/commands/doctor.js +78 -21
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/doctor.test.js +167 -26
  6. package/dist/commands/doctor.test.js.map +1 -1
  7. package/dist/commands/init.test.js +4 -7
  8. package/dist/commands/init.test.js.map +1 -1
  9. package/dist/commands/update.d.ts +3 -1
  10. package/dist/commands/update.js +78 -15
  11. package/dist/commands/update.js.map +1 -1
  12. package/dist/commands/update.test.js +257 -2
  13. package/dist/commands/update.test.js.map +1 -1
  14. package/dist/lockfile.d.ts +10 -0
  15. package/dist/lockfile.js +20 -1
  16. package/dist/lockfile.js.map +1 -1
  17. package/dist/lockfile.test.js +43 -3
  18. package/dist/lockfile.test.js.map +1 -1
  19. package/dist/registry.js +73 -11
  20. package/dist/registry.js.map +1 -1
  21. package/dist/registry.test.js +16 -3
  22. package/dist/registry.test.js.map +1 -1
  23. package/dist/test-utils.d.ts +24 -2
  24. package/dist/test-utils.js +41 -2
  25. package/dist/test-utils.js.map +1 -1
  26. package/dist/ui/boolean-inputs.test.ts +76 -0
  27. package/dist/ui/components/action-sheet/action-sheet.test.ts +370 -1
  28. package/dist/ui/components/action-sheet/action-sheet.ts +306 -28
  29. package/dist/ui/components/action-sheet/index.ts +1 -0
  30. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +552 -8
  31. package/dist/ui/components/alert-dialog/alert-dialog.ts +644 -36
  32. package/dist/ui/components/alert-dialog/index.ts +5 -0
  33. package/dist/ui/components/area-chart/area-chart.ts +3 -2
  34. package/dist/ui/components/badge/badge.ts +2 -1
  35. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +968 -3
  36. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1021 -95
  37. package/dist/ui/components/bottom-sheet/index.ts +7 -1
  38. package/dist/ui/components/button/button.test.ts +36 -1
  39. package/dist/ui/components/button/button.ts +17 -7
  40. package/dist/ui/components/button-group/button-group.test.ts +53 -9
  41. package/dist/ui/components/button-group/button-group.ts +67 -21
  42. package/dist/ui/components/card/card.test.ts +16 -0
  43. package/dist/ui/components/card/card.ts +2 -1
  44. package/dist/ui/components/chart/chart-presets.ts +6 -6
  45. package/dist/ui/components/chart/chart.test.ts +14 -0
  46. package/dist/ui/components/chart/chart.ts +10 -3
  47. package/dist/ui/components/checkbox/checkbox.ts +2 -1
  48. package/dist/ui/components/collapsible/collapsible.ts +2 -1
  49. package/dist/ui/components/dialog/dialog.ts +6 -6
  50. package/dist/ui/components/dock/dock-scroll.ts +9 -0
  51. package/dist/ui/components/dock/dock-search.test.ts +594 -0
  52. package/dist/ui/components/dock/dock.test.ts +4 -1
  53. package/dist/ui/components/dock/dock.ts +529 -51
  54. package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
  55. package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
  56. package/dist/ui/components/dynamic-color/index.ts +5 -0
  57. package/dist/ui/components/glass/glass.test.ts +39 -1
  58. package/dist/ui/components/glass/glass.ts +87 -17
  59. package/dist/ui/components/glass/index.ts +1 -0
  60. package/dist/ui/components/icon/icon-theme.test.ts +28 -0
  61. package/dist/ui/components/icon/icon-theme.ts +38 -7
  62. package/dist/ui/components/input/input.ts +2 -1
  63. package/dist/ui/components/label/label.ts +8 -2
  64. package/dist/ui/components/line-chart/line-chart.ts +3 -2
  65. package/dist/ui/components/list/list.ts +5 -4
  66. package/dist/ui/components/menu/menu-popup.test.ts +18 -14
  67. package/dist/ui/components/menu/menu-popup.ts +24 -3
  68. package/dist/ui/components/menu/menu.test.ts +99 -5
  69. package/dist/ui/components/menu/menu.ts +74 -34
  70. package/dist/ui/components/nav-drawer/nav-drawer.ts +3 -2
  71. package/dist/ui/components/navigation-bar/index.ts +1 -0
  72. package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
  73. package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
  74. package/dist/ui/components/popover/index.ts +9 -0
  75. package/dist/ui/components/popover/popover-panel.test.ts +735 -0
  76. package/dist/ui/components/popover/popover-panel.ts +603 -0
  77. package/dist/ui/components/popover/popover.test.ts +286 -0
  78. package/dist/ui/components/popover/popover.ts +207 -0
  79. package/dist/ui/components/progress/progress.ts +2 -1
  80. package/dist/ui/components/radio-group/radio-group.ts +2 -1
  81. package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
  82. package/dist/ui/components/search-field/index.ts +15 -0
  83. package/dist/ui/components/search-field/keyboard.test.ts +147 -0
  84. package/dist/ui/components/search-field/keyboard.ts +123 -0
  85. package/dist/ui/components/search-field/search-field.test.ts +549 -0
  86. package/dist/ui/components/search-field/search-field.ts +628 -0
  87. package/dist/ui/components/segmented-control/index.ts +8 -0
  88. package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
  89. package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
  90. package/dist/ui/components/select/select.ts +2 -1
  91. package/dist/ui/components/sheet/sheet.test.ts +90 -3
  92. package/dist/ui/components/sheet/sheet.ts +59 -4
  93. package/dist/ui/components/slider/index.ts +1 -0
  94. package/dist/ui/components/slider/slider.test.ts +695 -0
  95. package/dist/ui/components/slider/slider.ts +901 -0
  96. package/dist/ui/components/switch/index.ts +1 -1
  97. package/dist/ui/components/switch/switch.test.ts +464 -48
  98. package/dist/ui/components/switch/switch.ts +613 -43
  99. package/dist/ui/components/tabs/index.ts +7 -1
  100. package/dist/ui/components/tabs/tabs.test.ts +278 -2
  101. package/dist/ui/components/tabs/tabs.ts +233 -30
  102. package/dist/ui/components/textarea/textarea.ts +2 -1
  103. package/dist/ui/components/toggle/index.ts +1 -0
  104. package/dist/ui/components/toggle/toggle.test.ts +310 -4
  105. package/dist/ui/components/toggle/toggle.ts +241 -26
  106. package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
  107. package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
  108. package/dist/ui/components/toolbar/index.ts +12 -0
  109. package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
  110. package/dist/ui/components/toolbar/toolbar.ts +535 -0
  111. package/dist/utils/analyze.d.ts +10 -5
  112. package/dist/utils/analyze.js +3 -2
  113. package/dist/utils/analyze.js.map +1 -1
  114. package/dist/utils/installed.d.ts +13 -1
  115. package/dist/utils/installed.js +17 -0
  116. package/dist/utils/installed.js.map +1 -1
  117. package/dist/utils/installed.test.js +39 -1
  118. package/dist/utils/installed.test.js.map +1 -1
  119. package/dist/utils/tailwind.d.ts +25 -5
  120. package/dist/utils/tailwind.js +53 -14
  121. package/dist/utils/tailwind.js.map +1 -1
  122. package/dist/utils/tailwind.test.js +85 -22
  123. package/dist/utils/tailwind.test.js.map +1 -1
  124. 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
+ };