@seed-design/lynx-react 0.3.0 → 0.4.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.
Files changed (84) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +20 -0
  3. package/lib/components/Field/Field.d.ts +47 -0
  4. package/lib/components/Field/Field.d.ts.map +1 -0
  5. package/lib/components/Field/Field.jsx +121 -0
  6. package/lib/components/Field/Field.namespace.d.ts +2 -0
  7. package/lib/components/Field/Field.namespace.d.ts.map +1 -0
  8. package/lib/components/Field/Field.namespace.js +16 -0
  9. package/lib/components/Field/context.d.ts +19 -0
  10. package/lib/components/Field/context.d.ts.map +1 -0
  11. package/lib/components/Field/context.js +10 -0
  12. package/lib/components/Field/index.d.ts +3 -0
  13. package/lib/components/Field/index.d.ts.map +1 -0
  14. package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts +42 -0
  15. package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.d.ts.map +1 -0
  16. package/lib/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.jsx +140 -0
  17. package/lib/components/KeyboardAvoidingScrollView/context.d.ts +17 -0
  18. package/lib/components/KeyboardAvoidingScrollView/context.d.ts.map +1 -0
  19. package/lib/components/KeyboardAvoidingScrollView/context.js +12 -0
  20. package/lib/components/KeyboardAvoidingScrollView/engine.d.ts +50 -0
  21. package/lib/components/KeyboardAvoidingScrollView/engine.d.ts.map +1 -0
  22. package/lib/components/KeyboardAvoidingScrollView/engine.js +259 -0
  23. package/lib/components/KeyboardAvoidingScrollView/geometry.d.ts +50 -0
  24. package/lib/components/KeyboardAvoidingScrollView/geometry.d.ts.map +1 -0
  25. package/lib/components/KeyboardAvoidingScrollView/geometry.js +90 -0
  26. package/lib/components/KeyboardAvoidingScrollView/index.d.ts +2 -0
  27. package/lib/components/KeyboardAvoidingScrollView/index.d.ts.map +1 -0
  28. package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.d.ts +11 -0
  29. package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.d.ts.map +1 -0
  30. package/lib/components/KeyboardAvoidingScrollView/keyboard-event-source.js +43 -0
  31. package/lib/components/KeyboardAvoidingScrollView/native-driver.d.ts +34 -0
  32. package/lib/components/KeyboardAvoidingScrollView/native-driver.d.ts.map +1 -0
  33. package/lib/components/KeyboardAvoidingScrollView/native-driver.js +133 -0
  34. package/lib/components/TextField/TextField.d.ts +113 -0
  35. package/lib/components/TextField/TextField.d.ts.map +1 -0
  36. package/lib/components/TextField/TextField.jsx +418 -0
  37. package/lib/components/TextField/TextField.namespace.d.ts +2 -0
  38. package/lib/components/TextField/TextField.namespace.d.ts.map +1 -0
  39. package/lib/components/TextField/TextField.namespace.js +14 -0
  40. package/lib/components/TextField/context.d.ts +22 -0
  41. package/lib/components/TextField/context.d.ts.map +1 -0
  42. package/lib/components/TextField/context.js +7 -0
  43. package/lib/components/TextField/index.d.ts +4 -0
  44. package/lib/components/TextField/index.d.ts.map +1 -0
  45. package/lib/components/TextField/useTextFieldWithGraphemes.d.ts +24 -0
  46. package/lib/components/TextField/useTextFieldWithGraphemes.d.ts.map +1 -0
  47. package/lib/components/TextField/useTextFieldWithGraphemes.js +47 -0
  48. package/lib/components/index.d.ts +3 -0
  49. package/lib/components/index.d.ts.map +1 -1
  50. package/lib/index.js +7 -1
  51. package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/_grapheme_data.js +8 -0
  52. package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/_incb_data.js +14 -0
  53. package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/core.js +68 -0
  54. package/lib/node_modules/.bun/unicode-segmenter@0.14.5/node_modules/unicode-segmenter/grapheme.js +175 -0
  55. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
  56. package/lib/utils/create-slot-recipe-context.jsx +7 -0
  57. package/package.json +10 -5
  58. package/src/components/Field/Field.namespace.ts +19 -0
  59. package/src/components/Field/Field.test.tsx +81 -0
  60. package/src/components/Field/Field.tsx +252 -0
  61. package/src/components/Field/context.ts +26 -0
  62. package/src/components/Field/index.ts +21 -0
  63. package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.test.tsx +267 -0
  64. package/src/components/KeyboardAvoidingScrollView/KeyboardAvoidingScrollView.tsx +299 -0
  65. package/src/components/KeyboardAvoidingScrollView/context.ts +24 -0
  66. package/src/components/KeyboardAvoidingScrollView/engine.test.ts +451 -0
  67. package/src/components/KeyboardAvoidingScrollView/engine.ts +417 -0
  68. package/src/components/KeyboardAvoidingScrollView/geometry.test.ts +315 -0
  69. package/src/components/KeyboardAvoidingScrollView/geometry.ts +194 -0
  70. package/src/components/KeyboardAvoidingScrollView/index.ts +5 -0
  71. package/src/components/KeyboardAvoidingScrollView/keyboard-event-source.test.ts +122 -0
  72. package/src/components/KeyboardAvoidingScrollView/keyboard-event-source.ts +75 -0
  73. package/src/components/KeyboardAvoidingScrollView/native-driver.test.ts +223 -0
  74. package/src/components/KeyboardAvoidingScrollView/native-driver.ts +248 -0
  75. package/src/components/TextField/TextField.namespace.ts +16 -0
  76. package/src/components/TextField/TextField.test.tsx +924 -0
  77. package/src/components/TextField/TextField.tsx +912 -0
  78. package/src/components/TextField/context.ts +33 -0
  79. package/src/components/TextField/index.ts +23 -0
  80. package/src/components/TextField/useTextFieldWithGraphemes.test.tsx +80 -0
  81. package/src/components/TextField/useTextFieldWithGraphemes.ts +78 -0
  82. package/src/components/index.ts +3 -0
  83. package/src/utils/create-slot-recipe-context.test.tsx +51 -0
  84. package/src/utils/create-slot-recipe-context.tsx +23 -0
@@ -0,0 +1,17 @@
1
+ import { Context } from '@lynx-js/react';
2
+ import { NodesRef } from '@lynx-js/types';
3
+ import { KeyboardAvoidanceRegistration as EngineRegistration } from './engine';
4
+ export type KeyboardAvoidanceRegistration = EngineRegistration<NodesRef>;
5
+ export interface KeyboardAvoidanceActions {
6
+ focus(registration: KeyboardAvoidanceRegistration): void;
7
+ blur(owner: object): void;
8
+ layoutChanged(owner: object): void;
9
+ unregister(owner: object): void;
10
+ }
11
+ export declare const KeyboardAvoidanceActionsContext: Context<KeyboardAvoidanceActions | null>;
12
+ /**
13
+ * Lynx TextField가 선택적으로 소비하는 내부 연결점이다.
14
+ * Provider 밖에서는 `null`을 반환해 TextField 자체 동작을 방해하지 않는다.
15
+ */
16
+ export declare function useKeyboardAvoidanceActions(): KeyboardAvoidanceActions | null;
17
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,KAAK,EAAE,6BAA6B,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEpF,MAAM,MAAM,6BAA6B,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AAEzE,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,YAAY,EAAE,6BAA6B,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,eAAO,MAAM,+BAA+B,EAAE,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAC/B,CAAC;AAEvD;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,wBAAwB,GAAG,IAAI,CAE7E"}
@@ -0,0 +1,12 @@
1
+ import { createContext, useContext } from "@lynx-js/react";
2
+ //#region src/components/KeyboardAvoidingScrollView/context.ts
3
+ var KeyboardAvoidanceActionsContext = createContext(null);
4
+ /**
5
+ * Lynx TextField가 선택적으로 소비하는 내부 연결점이다.
6
+ * Provider 밖에서는 `null`을 반환해 TextField 자체 동작을 방해하지 않는다.
7
+ */
8
+ function useKeyboardAvoidanceActions() {
9
+ return useContext(KeyboardAvoidanceActionsContext);
10
+ }
11
+ //#endregion
12
+ export { KeyboardAvoidanceActionsContext, useKeyboardAvoidanceActions };
@@ -0,0 +1,50 @@
1
+ import { VerticalRect } from './geometry';
2
+ import { KeyboardOcclusion, RawKeyboardState, ScrollMetrics } from './native-driver';
3
+ export interface RefLike<Node> {
4
+ current: Node | null;
5
+ }
6
+ export interface KeyboardAvoidanceRegistration<Node> {
7
+ owner: object;
8
+ nativeRef: RefLike<Node>;
9
+ controlRef?: RefLike<Node>;
10
+ fieldRef?: RefLike<Node>;
11
+ anchorRef?: RefLike<Node>;
12
+ enabled?: boolean;
13
+ }
14
+ export interface KeyboardAvoidingNativeDriver<Node> {
15
+ measure(node: Node): Promise<VerticalRect | null>;
16
+ resolveKeyboardOcclusion(state: RawKeyboardState): Promise<KeyboardOcclusion | null>;
17
+ setSpacerHeight(node: Node, height: number): void;
18
+ waitForLayout(): Promise<void>;
19
+ getScrollMetrics(node: Node, viewportHeight: number): Promise<ScrollMetrics | null>;
20
+ scrollTo(node: Node, offset: number, smooth: boolean): void;
21
+ }
22
+ type ScheduledCallback = () => void | Promise<void>;
23
+ type CancelScheduledCallback = () => void;
24
+ export interface KeyboardAvoidingScheduler {
25
+ scheduleFrame(callback: ScheduledCallback): CancelScheduledCallback;
26
+ scheduleTimer(callback: ScheduledCallback, delayMs: number): CancelScheduledCallback;
27
+ }
28
+ export interface KeyboardAvoidingEngineOptions<Node> {
29
+ driver: KeyboardAvoidingNativeDriver<Node>;
30
+ scheduler: KeyboardAvoidingScheduler;
31
+ getScrollNode(): Node | null;
32
+ getSpacerNode(): Node | null;
33
+ getKeyboardGap(): number;
34
+ getToolbarHeight(): number;
35
+ getSmooth(): boolean;
36
+ }
37
+ export interface KeyboardAvoidingEngine<Node> {
38
+ focus(registration: KeyboardAvoidanceRegistration<Node>): void;
39
+ blur(owner: object): void;
40
+ layoutChanged(owner: object): void;
41
+ unregister(owner: object): void;
42
+ keyboardChanged(state: RawKeyboardState): void;
43
+ viewportChanged(): void;
44
+ userScrollStarted(): void;
45
+ userScrollEnded(): void;
46
+ dispose(): void;
47
+ }
48
+ export declare function createKeyboardAvoidingEngine<Node>(options: KeyboardAvoidingEngineOptions<Node>): KeyboardAvoidingEngine<Node>;
49
+ export {};
50
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAI1F,MAAM,WAAW,OAAO,CAAC,IAAI;IAC3B,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,6BAA6B,CAAC,IAAI;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B,CAAC,IAAI;IAChD,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAClD,wBAAwB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACrF,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACpF,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7D;AAED,KAAK,iBAAiB,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AACpD,KAAK,uBAAuB,GAAG,MAAM,IAAI,CAAC;AAE1C,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,QAAQ,EAAE,iBAAiB,GAAG,uBAAuB,CAAC;IACpE,aAAa,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,uBAAuB,CAAC;CACtF;AAED,MAAM,WAAW,6BAA6B,CAAC,IAAI;IACjD,MAAM,EAAE,4BAA4B,CAAC,IAAI,CAAC,CAAC;IAC3C,SAAS,EAAE,yBAAyB,CAAC;IACrC,aAAa,IAAI,IAAI,GAAG,IAAI,CAAC;IAC7B,aAAa,IAAI,IAAI,GAAG,IAAI,CAAC;IAC7B,cAAc,IAAI,MAAM,CAAC;IACzB,gBAAgB,IAAI,MAAM,CAAC;IAC3B,SAAS,IAAI,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB,CAAC,IAAI;IAC1C,KAAK,CAAC,YAAY,EAAE,6BAA6B,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/D,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/C,eAAe,IAAI,IAAI,CAAC;IACxB,iBAAiB,IAAI,IAAI,CAAC;IAC1B,eAAe,IAAI,IAAI,CAAC;IACxB,OAAO,IAAI,IAAI,CAAC;CACjB;AA4VD,wBAAgB,4BAA4B,CAAC,IAAI,EAC/C,OAAO,EAAE,6BAA6B,CAAC,IAAI,CAAC,GAC3C,sBAAsB,CAAC,IAAI,CAAC,CAE9B"}
@@ -0,0 +1,259 @@
1
+ import { calculateSafeArea, calculateSignedScrollDelta, clampScrollOffset, createFieldBodyRect, hasMeaningfulGeometryChange, selectLargestFittingTarget } from "./geometry.js";
2
+ //#region src/components/KeyboardAvoidingScrollView/engine.ts
3
+ var BLUR_HANDOFF_DELAY_MS = 30;
4
+ function normalizeKeyboardState(state) {
5
+ const height = Number.isFinite(state.height) ? Math.max(0, state.height) : 0;
6
+ const visible = state.visible && height > 0;
7
+ return {
8
+ visible,
9
+ height: visible ? height : 0
10
+ };
11
+ }
12
+ function targetHeight(rect) {
13
+ return Math.max(0, rect.bottom - rect.top);
14
+ }
15
+ var KeyboardAvoidingEngineImpl = class {
16
+ #options;
17
+ #pendingReasons = /* @__PURE__ */ new Set();
18
+ #ownerStates = /* @__PURE__ */ new Map();
19
+ #activeRegistration = null;
20
+ #keyboard = {
21
+ visible: false,
22
+ height: 0
23
+ };
24
+ #revision = 0;
25
+ #session = 0;
26
+ #appliedSpacerHeight = 0;
27
+ #needsCloseClamp = false;
28
+ #cancelFrame = null;
29
+ #cancelBlur = null;
30
+ #blurOwner = null;
31
+ #userScrolling = false;
32
+ #disposed = false;
33
+ constructor(options) {
34
+ this.#options = options;
35
+ }
36
+ focus(registration) {
37
+ if (this.#disposed) return;
38
+ this.#cancelPendingBlur();
39
+ if (registration.enabled === false) {
40
+ if (this.#activeRegistration === null) return;
41
+ this.#activeRegistration = null;
42
+ this.#invalidate("focus");
43
+ return;
44
+ }
45
+ this.#activeRegistration = registration;
46
+ this.#invalidate("focus");
47
+ }
48
+ blur(owner) {
49
+ if (this.#disposed || this.#activeRegistration?.owner !== owner) return;
50
+ this.#cancelPendingBlur();
51
+ this.#blurOwner = owner;
52
+ this.#cancelBlur = this.#options.scheduler.scheduleTimer(() => {
53
+ this.#cancelBlur = null;
54
+ const blurOwner = this.#blurOwner;
55
+ this.#blurOwner = null;
56
+ if (this.#activeRegistration?.owner !== blurOwner) return;
57
+ this.#activeRegistration = null;
58
+ this.#invalidate("focus");
59
+ }, BLUR_HANDOFF_DELAY_MS);
60
+ }
61
+ layoutChanged(owner) {
62
+ if (this.#disposed || this.#activeRegistration?.owner !== owner) return;
63
+ this.#invalidate("layout");
64
+ }
65
+ unregister(owner) {
66
+ if (this.#disposed) return;
67
+ this.#ownerStates.delete(owner);
68
+ if (this.#blurOwner === owner) this.#cancelPendingBlur();
69
+ if (this.#activeRegistration?.owner !== owner) return;
70
+ this.#activeRegistration = null;
71
+ this.#invalidate("focus");
72
+ }
73
+ keyboardChanged(state) {
74
+ if (this.#disposed) return;
75
+ const next = normalizeKeyboardState(state);
76
+ if (next.visible === this.#keyboard.visible && !hasMeaningfulGeometryChange(this.#keyboard.height, next.height)) return;
77
+ if (next.visible !== this.#keyboard.visible) {
78
+ this.#session += 1;
79
+ this.#ownerStates.clear();
80
+ }
81
+ this.#keyboard = next;
82
+ this.#invalidate("keyboard");
83
+ }
84
+ viewportChanged() {
85
+ if (this.#disposed) return;
86
+ this.#invalidate("viewport");
87
+ }
88
+ userScrollStarted() {
89
+ if (this.#disposed || this.#userScrolling) return;
90
+ this.#userScrolling = true;
91
+ this.#revision += 1;
92
+ this.#pendingReasons.add("scroll");
93
+ this.#cancelFrame?.();
94
+ this.#cancelFrame = null;
95
+ }
96
+ userScrollEnded() {
97
+ if (this.#disposed || !this.#userScrolling) return;
98
+ this.#userScrolling = false;
99
+ this.#invalidate("scroll");
100
+ }
101
+ dispose() {
102
+ if (this.#disposed) return;
103
+ this.#disposed = true;
104
+ this.#revision += 1;
105
+ this.#cancelFrame?.();
106
+ this.#cancelFrame = null;
107
+ this.#cancelPendingBlur();
108
+ this.#pendingReasons.clear();
109
+ this.#ownerStates.clear();
110
+ this.#activeRegistration = null;
111
+ }
112
+ #cancelPendingBlur() {
113
+ this.#cancelBlur?.();
114
+ this.#cancelBlur = null;
115
+ this.#blurOwner = null;
116
+ }
117
+ #invalidate(reason) {
118
+ this.#revision += 1;
119
+ this.#pendingReasons.add(reason);
120
+ if (this.#userScrolling || this.#cancelFrame !== null) return;
121
+ this.#cancelFrame = this.#options.scheduler.scheduleFrame(async () => {
122
+ this.#cancelFrame = null;
123
+ if (this.#disposed || this.#userScrolling) return;
124
+ const reasons = new Set(this.#pendingReasons);
125
+ this.#pendingReasons.clear();
126
+ const transaction = {
127
+ revision: this.#revision,
128
+ session: this.#session,
129
+ registration: this.#activeRegistration,
130
+ keyboard: this.#keyboard,
131
+ layoutOnly: reasons.size === 1 && reasons.has("layout")
132
+ };
133
+ await this.#evaluate(transaction);
134
+ });
135
+ }
136
+ #isCurrent(transaction) {
137
+ return !this.#disposed && transaction.revision === this.#revision && transaction.session === this.#session && transaction.registration === this.#activeRegistration && !this.#userScrolling;
138
+ }
139
+ async #evaluate(transaction) {
140
+ try {
141
+ if (!transaction.keyboard.visible || transaction.registration === null) {
142
+ await this.#close(transaction);
143
+ return;
144
+ }
145
+ await this.#open(transaction, transaction.registration);
146
+ } catch {}
147
+ }
148
+ async #close(transaction) {
149
+ if (this.#appliedSpacerHeight === 0 && !this.#needsCloseClamp) return;
150
+ const scrollNode = this.#options.getScrollNode();
151
+ if (this.#appliedSpacerHeight !== 0) {
152
+ const spacerNode = this.#options.getSpacerNode();
153
+ if (spacerNode === null) return;
154
+ this.#options.driver.setSpacerHeight(spacerNode, 0);
155
+ this.#appliedSpacerHeight = 0;
156
+ this.#needsCloseClamp = true;
157
+ }
158
+ if (scrollNode === null) return;
159
+ await this.#options.driver.waitForLayout();
160
+ if (!this.#isCurrent(transaction)) return;
161
+ const viewport = await this.#options.driver.measure(scrollNode);
162
+ if (!this.#isCurrent(transaction) || viewport === null) return;
163
+ const viewportHeight = Math.max(0, viewport.bottom - viewport.top);
164
+ const metrics = await this.#options.driver.getScrollMetrics(scrollNode, viewportHeight);
165
+ if (!this.#isCurrent(transaction) || metrics === null) return;
166
+ const clampedOffset = clampScrollOffset(metrics.offsetY, metrics.maxOffsetY);
167
+ if (clampedOffset === metrics.offsetY) {
168
+ this.#needsCloseClamp = false;
169
+ return;
170
+ }
171
+ this.#options.driver.scrollTo(scrollNode, clampedOffset, this.#options.getSmooth());
172
+ this.#needsCloseClamp = false;
173
+ }
174
+ async #open(transaction, registration) {
175
+ const scrollNode = this.#options.getScrollNode();
176
+ const spacerNode = this.#options.getSpacerNode();
177
+ if (scrollNode === null || spacerNode === null) return;
178
+ const [viewport, occlusion] = await Promise.all([this.#options.driver.measure(scrollNode), this.#options.driver.resolveKeyboardOcclusion(transaction.keyboard)]);
179
+ if (!this.#isCurrent(transaction) || viewport === null || occlusion === null) return;
180
+ const safeArea = calculateSafeArea({
181
+ viewport,
182
+ keyboardOcclusionTop: occlusion.topInScreenPx,
183
+ toolbarHeight: this.#options.getToolbarHeight(),
184
+ keyboardGap: this.#options.getKeyboardGap()
185
+ });
186
+ this.#needsCloseClamp = false;
187
+ if (hasMeaningfulGeometryChange(this.#appliedSpacerHeight, safeArea.spacerHeight)) {
188
+ this.#options.driver.setSpacerHeight(spacerNode, safeArea.spacerHeight);
189
+ this.#appliedSpacerHeight = safeArea.spacerHeight;
190
+ }
191
+ await this.#options.driver.waitForLayout();
192
+ if (!this.#isCurrent(transaction)) return;
193
+ const state = this.#getOwnerState(registration.owner, transaction.session);
194
+ const measure = (ref) => {
195
+ const node = ref?.current;
196
+ if (node === null || node === void 0) return Promise.resolve(null);
197
+ return Promise.resolve().then(() => this.#options.driver.measure(node)).catch(() => null);
198
+ };
199
+ const [field, control, native, anchor] = await Promise.all([
200
+ state.fieldDowngraded ? Promise.resolve(null) : measure(registration.fieldRef),
201
+ measure(registration.controlRef),
202
+ measure(registration.nativeRef),
203
+ measure(registration.anchorRef)
204
+ ]);
205
+ if (!this.#isCurrent(transaction)) return;
206
+ const fieldBody = createFieldBodyRect(field, control);
207
+ const selected = selectLargestFittingTarget({
208
+ field,
209
+ fieldBody,
210
+ control,
211
+ native,
212
+ anchor
213
+ }, safeArea);
214
+ const nextState = {
215
+ ...state,
216
+ fieldDowngraded: state.fieldDowngraded || fieldBody === null && field !== null && selected !== null && selected.kind !== "field",
217
+ lastTargetKind: selected?.kind ?? null,
218
+ lastTargetHeight: selected ? targetHeight(selected.rect) : null
219
+ };
220
+ if (selected === null) {
221
+ this.#ownerStates.set(registration.owner, nextState);
222
+ return;
223
+ }
224
+ if (transaction.layoutOnly && (state.lastTargetKind === selected.kind && state.lastTargetHeight !== null && targetHeight(selected.rect) < state.lastTargetHeight - 1 || state.lastTargetKind === "fieldBottom" && selected.kind !== "fieldBottom")) {
225
+ this.#ownerStates.set(registration.owner, nextState);
226
+ return;
227
+ }
228
+ const delta = calculateSignedScrollDelta(selected.rect, safeArea);
229
+ if (delta === 0) {
230
+ this.#ownerStates.set(registration.owner, nextState);
231
+ return;
232
+ }
233
+ const viewportHeight = Math.max(0, viewport.bottom - viewport.top);
234
+ const metrics = await this.#options.driver.getScrollMetrics(scrollNode, viewportHeight);
235
+ if (!this.#isCurrent(transaction) || metrics === null) return;
236
+ const nextOffset = clampScrollOffset(metrics.offsetY + delta, metrics.maxOffsetY);
237
+ if (!hasMeaningfulGeometryChange(metrics.offsetY, nextOffset)) {
238
+ this.#ownerStates.set(registration.owner, nextState);
239
+ return;
240
+ }
241
+ this.#options.driver.scrollTo(scrollNode, nextOffset, this.#options.getSmooth());
242
+ this.#ownerStates.set(registration.owner, nextState);
243
+ }
244
+ #getOwnerState(owner, session) {
245
+ const current = this.#ownerStates.get(owner);
246
+ if (current?.session === session) return current;
247
+ return {
248
+ session,
249
+ fieldDowngraded: false,
250
+ lastTargetKind: null,
251
+ lastTargetHeight: null
252
+ };
253
+ }
254
+ };
255
+ function createKeyboardAvoidingEngine(options) {
256
+ return new KeyboardAvoidingEngineImpl(options);
257
+ }
258
+ //#endregion
259
+ export { createKeyboardAvoidingEngine };
@@ -0,0 +1,50 @@
1
+ export declare const KEYBOARD_AVOIDANCE_EPSILON_PX = 1;
2
+ export interface VerticalRect {
3
+ top: number;
4
+ bottom: number;
5
+ }
6
+ export interface SafeArea {
7
+ safeTop: number;
8
+ safeBottom: number;
9
+ spacerHeight: number;
10
+ }
11
+ export interface CalculateSafeAreaOptions {
12
+ viewport: VerticalRect;
13
+ keyboardOcclusionTop: number;
14
+ toolbarHeight: number;
15
+ keyboardGap: number;
16
+ }
17
+ export type AvoidanceTargetKind = "field" | "fieldBody" | "control" | "native" | "anchor" | "fieldBottom";
18
+ export interface AvoidanceTarget {
19
+ kind: AvoidanceTargetKind;
20
+ rect: VerticalRect;
21
+ }
22
+ export interface AvoidanceTargetCandidates {
23
+ field?: VerticalRect | null;
24
+ fieldBody?: VerticalRect | null;
25
+ control?: VerticalRect | null;
26
+ native?: VerticalRect | null;
27
+ anchor?: VerticalRect | null;
28
+ }
29
+ /**
30
+ * screen 좌표계에서 키보드와 툴바가 가리지 않는 세로 영역을 계산한다.
31
+ */
32
+ export declare function calculateSafeArea({ viewport, keyboardOcclusionTop, toolbarHeight, keyboardGap, }: CalculateSafeAreaOptions): SafeArea;
33
+ /**
34
+ * Field의 header를 제외하고 control부터 footer까지 포함하는 영역을 만든다.
35
+ */
36
+ export declare function createFieldBodyRect(field: VerticalRect | null, control: VerticalRect | null, epsilon?: number): VerticalRect | null;
37
+ /**
38
+ * 화면에 온전히 들어오는 가장 큰 의미 단위를 선택한다.
39
+ * 우선순위는 Field, Field body, Field bottom, control, native control, 정밀 anchor 순이다.
40
+ * Field body가 너무 크면 autoresize Field의 footer를 추적할 수 있도록
41
+ * control보다 먼저 Field body의 하단점을 사용한다.
42
+ */
43
+ export declare function selectLargestFittingTarget(candidates: AvoidanceTargetCandidates, safeArea: Pick<SafeArea, "safeTop" | "safeBottom">, epsilon?: number): AvoidanceTarget | null;
44
+ /**
45
+ * 양수는 아래로, 음수는 위로 이동해야 하는 scroll offset 변화량이다.
46
+ */
47
+ export declare function calculateSignedScrollDelta(target: VerticalRect, safeArea: Pick<SafeArea, "safeTop" | "safeBottom">, epsilon?: number): number;
48
+ export declare function clampScrollOffset(offset: number, maxOffset: number): number;
49
+ export declare function hasMeaningfulGeometryChange(previous: number, next: number, epsilon?: number): boolean;
50
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/geometry.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,YAAY,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAC3B,OAAO,GACP,WAAW,GACX,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,aAAa,CAAC;AAElB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CAC9B;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,EAAE,wBAAwB,GAAG,QAAQ,CAcrC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,GAAG,IAAI,EAC1B,OAAO,EAAE,YAAY,GAAG,IAAI,EAC5B,OAAO,SAAgC,GACtC,YAAY,GAAG,IAAI,CAgBrB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,yBAAyB,EACrC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,GAAG,YAAY,CAAC,EAClD,OAAO,SAAgC,GACtC,eAAe,GAAG,IAAI,CAwDxB;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,GAAG,YAAY,CAAC,EAClD,OAAO,SAAgC,GACtC,MAAM,CAYR;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,SAAgC,GACtC,OAAO,CAET"}
@@ -0,0 +1,90 @@
1
+ function normalizeNonNegativeFinite(value) {
2
+ return Number.isFinite(value) ? Math.max(0, value) : 0;
3
+ }
4
+ /**
5
+ * screen 좌표계에서 키보드와 툴바가 가리지 않는 세로 영역을 계산한다.
6
+ */
7
+ function calculateSafeArea({ viewport, keyboardOcclusionTop, toolbarHeight, keyboardGap }) {
8
+ const normalizedToolbarHeight = normalizeNonNegativeFinite(toolbarHeight);
9
+ const normalizedKeyboardGap = normalizeNonNegativeFinite(keyboardGap);
10
+ const normalizedViewportBottom = Math.max(viewport.top, viewport.bottom);
11
+ const safeBottom = Math.min(normalizedViewportBottom, Math.max(viewport.top, keyboardOcclusionTop - normalizedToolbarHeight - normalizedKeyboardGap));
12
+ return {
13
+ safeTop: viewport.top,
14
+ safeBottom,
15
+ spacerHeight: normalizedViewportBottom - safeBottom
16
+ };
17
+ }
18
+ /**
19
+ * Field의 header를 제외하고 control부터 footer까지 포함하는 영역을 만든다.
20
+ */
21
+ function createFieldBodyRect(field, control, epsilon = 1) {
22
+ if (field === null || control === null) return null;
23
+ const normalizedEpsilon = Math.max(0, epsilon);
24
+ const fieldHeight = field.bottom - field.top;
25
+ const controlHeight = control.bottom - control.top;
26
+ if (fieldHeight < 0 || controlHeight < 0 || control.top < field.top - normalizedEpsilon || control.bottom > field.bottom + normalizedEpsilon) return null;
27
+ return {
28
+ top: control.top,
29
+ bottom: Math.max(field.bottom, control.bottom)
30
+ };
31
+ }
32
+ /**
33
+ * 화면에 온전히 들어오는 가장 큰 의미 단위를 선택한다.
34
+ * 우선순위는 Field, Field body, Field bottom, control, native control, 정밀 anchor 순이다.
35
+ * Field body가 너무 크면 autoresize Field의 footer를 추적할 수 있도록
36
+ * control보다 먼저 Field body의 하단점을 사용한다.
37
+ */
38
+ function selectLargestFittingTarget(candidates, safeArea, epsilon = 1) {
39
+ const availableHeight = Math.max(0, safeArea.safeBottom - safeArea.safeTop);
40
+ const normalizedEpsilon = Math.max(0, epsilon);
41
+ const fieldCandidates = [["field", candidates.field], ["fieldBody", candidates.fieldBody]];
42
+ const fallbackCandidates = [
43
+ ["control", candidates.control],
44
+ ["native", candidates.native],
45
+ ["anchor", candidates.anchor]
46
+ ];
47
+ for (const [kind, rect] of fieldCandidates) {
48
+ if (!rect) continue;
49
+ const targetHeight = rect.bottom - rect.top;
50
+ if (targetHeight >= 0 && targetHeight <= availableHeight + normalizedEpsilon) return {
51
+ kind,
52
+ rect
53
+ };
54
+ }
55
+ const fieldBody = candidates.fieldBody;
56
+ const fieldBodyHeight = fieldBody ? fieldBody.bottom - fieldBody.top : -1;
57
+ if (fieldBody && Number.isFinite(fieldBody.top) && Number.isFinite(fieldBody.bottom) && fieldBodyHeight > availableHeight + normalizedEpsilon) return {
58
+ kind: "fieldBottom",
59
+ rect: {
60
+ top: fieldBody.bottom,
61
+ bottom: fieldBody.bottom
62
+ }
63
+ };
64
+ for (const [kind, rect] of fallbackCandidates) {
65
+ if (!rect) continue;
66
+ const targetHeight = rect.bottom - rect.top;
67
+ if (targetHeight >= 0 && targetHeight <= availableHeight + normalizedEpsilon) return {
68
+ kind,
69
+ rect
70
+ };
71
+ }
72
+ return null;
73
+ }
74
+ /**
75
+ * 양수는 아래로, 음수는 위로 이동해야 하는 scroll offset 변화량이다.
76
+ */
77
+ function calculateSignedScrollDelta(target, safeArea, epsilon = 1) {
78
+ const normalizedEpsilon = Math.max(0, epsilon);
79
+ if (target.top < safeArea.safeTop - normalizedEpsilon) return target.top - safeArea.safeTop;
80
+ if (target.bottom > safeArea.safeBottom + normalizedEpsilon) return target.bottom - safeArea.safeBottom;
81
+ return 0;
82
+ }
83
+ function clampScrollOffset(offset, maxOffset) {
84
+ return Math.min(Math.max(0, offset), Math.max(0, maxOffset));
85
+ }
86
+ function hasMeaningfulGeometryChange(previous, next, epsilon = 1) {
87
+ return Math.abs(next - previous) > Math.max(0, epsilon);
88
+ }
89
+ //#endregion
90
+ export { calculateSafeArea, calculateSignedScrollDelta, clampScrollOffset, createFieldBodyRect, hasMeaningfulGeometryChange, selectLargestFittingTarget };
@@ -0,0 +1,2 @@
1
+ export { KeyboardAvoidingScrollView, type KeyboardAvoidingScrollBehavior, type KeyboardAvoidingScrollViewProps, } from './KeyboardAvoidingScrollView';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,GACrC,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { GlobalEventEmitter } from '@lynx-js/types';
2
+ import { RawKeyboardState } from './native-driver';
3
+ export type KeyboardEventListener = (state: RawKeyboardState) => void;
4
+ export interface KeyboardEventSource {
5
+ subscribe(listener: KeyboardEventListener): () => void;
6
+ }
7
+ type GetGlobalEventEmitter = () => GlobalEventEmitter;
8
+ export declare function createKeyboardEventSource(getEmitter: GetGlobalEventEmitter): KeyboardEventSource;
9
+ export declare const lynxKeyboardEventSource: KeyboardEventSource;
10
+ export {};
11
+ //# sourceMappingURL=keyboard-event-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard-event-source.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/keyboard-event-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAAC;CACxD;AAED,KAAK,qBAAqB,GAAG,MAAM,kBAAkB,CAAC;AAEtD,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,qBAAqB,GAAG,mBAAmB,CAwDhG;AAED,eAAO,MAAM,uBAAuB,qBAIlC,CAAC"}
@@ -0,0 +1,43 @@
1
+ //#region src/components/KeyboardAvoidingScrollView/keyboard-event-source.ts
2
+ function createKeyboardEventSource(getEmitter) {
3
+ const subscriptions = /* @__PURE__ */ new Set();
4
+ let emitter = null;
5
+ let currentState = {
6
+ visible: false,
7
+ height: 0
8
+ };
9
+ const handleNativeKeyboardEvent = (...args) => {
10
+ "background only";
11
+ const status = args[0];
12
+ const height = args[1];
13
+ currentState = {
14
+ visible: status === "on",
15
+ height: status === "on" && typeof height === "number" && Number.isFinite(height) ? Math.max(0, height) : 0
16
+ };
17
+ for (const subscription of subscriptions) subscription.listener(currentState);
18
+ };
19
+ return { subscribe(listener) {
20
+ "background only";
21
+ const subscription = { listener };
22
+ subscriptions.add(subscription);
23
+ if (emitter === null) try {
24
+ const nextEmitter = getEmitter();
25
+ nextEmitter.addListener("keyboardstatuschanged", handleNativeKeyboardEvent);
26
+ emitter = nextEmitter;
27
+ } catch {}
28
+ listener(currentState);
29
+ let subscribed = true;
30
+ return () => {
31
+ "background only";
32
+ if (!subscribed) return;
33
+ subscribed = false;
34
+ subscriptions.delete(subscription);
35
+ };
36
+ } };
37
+ }
38
+ var lynxKeyboardEventSource = createKeyboardEventSource(() => {
39
+ "background only";
40
+ return lynx.getJSModule("GlobalEventEmitter");
41
+ });
42
+ //#endregion
43
+ export { lynxKeyboardEventSource };
@@ -0,0 +1,34 @@
1
+ import { NodesRef, PlatformType } from '@lynx-js/types';
2
+ import { VerticalRect } from './geometry';
3
+ export type KeyboardAvoidingPlatform = Extract<PlatformType, "Android" | "iOS">;
4
+ export interface RawKeyboardState {
5
+ visible: boolean;
6
+ height: number;
7
+ }
8
+ export interface KeyboardOcclusion {
9
+ visible: boolean;
10
+ topInScreenPx: number;
11
+ }
12
+ export interface ScrollMetrics {
13
+ offsetY: number;
14
+ maxOffsetY: number;
15
+ }
16
+ export interface RawScrollInfo {
17
+ scrollY?: number;
18
+ scrollRange?: number;
19
+ maxScrollOffset?: number;
20
+ scrollTop?: number;
21
+ scrollHeight?: number;
22
+ }
23
+ export interface KeyboardAvoidingNativeDriver {
24
+ measure(node: NodesRef): Promise<VerticalRect | null>;
25
+ resolveKeyboardOcclusion(state: RawKeyboardState): Promise<KeyboardOcclusion | null>;
26
+ setSpacerHeight(node: NodesRef, height: number): void;
27
+ waitForLayout(): Promise<void>;
28
+ getScrollMetrics(node: NodesRef, viewportHeight: number): Promise<ScrollMetrics | null>;
29
+ scrollTo(node: NodesRef, offset: number, smooth: boolean): void;
30
+ }
31
+ export declare function normalizeScrollMetrics(raw: RawScrollInfo, platform: KeyboardAvoidingPlatform, viewportHeight: number): ScrollMetrics | null;
32
+ export declare function resolveKeyboardOcclusionTop(state: RawKeyboardState, platform: KeyboardAvoidingPlatform, screenHeight: number, rootRect: VerticalRect | null): number | null;
33
+ export declare const lynxKeyboardAvoidingNativeDriver: KeyboardAvoidingNativeDriver;
34
+ //# sourceMappingURL=native-driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-driver.d.ts","sourceRoot":"","sources":["../../../src/components/KeyboardAvoidingScrollView/native-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,MAAM,wBAAwB,GAAG,OAAO,CAAC,YAAY,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;AAEhF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAQD,MAAM,WAAW,4BAA4B;IAC3C,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACtD,wBAAwB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACrF,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACxF,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACjE;AAsBD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,wBAAwB,EAClC,cAAc,EAAE,MAAM,GACrB,aAAa,GAAG,IAAI,CA2BtB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,wBAAwB,EAClC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,YAAY,GAAG,IAAI,GAC5B,MAAM,GAAG,IAAI,CAQf;AA8ED,eAAO,MAAM,gCAAgC,EAAE,4BA2D9C,CAAC"}