@ng-native/testing 0.18.0 → 0.18.2

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/events.js CHANGED
@@ -65,8 +65,22 @@ function fieldOf(node) {
65
65
  throw new Error(`No TextInput or search bar at or under this ${node.viewName}.`);
66
66
  return field;
67
67
  }
68
- /** The count native would send next: one past the last one JS echoed back. */
69
- const nextCount = (field) => Number((currentOf(field) ?? field).props['mostRecentEventCount'] ?? 0) + 1;
68
+ /**
69
+ * The count each field last sent, keyed by the node's instance handle, which its clones share, so
70
+ * a field gone from the tree takes its count with it. Native keeps its own as a real field does,
71
+ * since iOS is not sent it back as a prop.
72
+ */
73
+ const sentCounts = new WeakMap();
74
+ /** The count native would send next: one past the last one it sent, or that JS echoed back. */
75
+ function nextCount(field) {
76
+ const handle = field.instanceHandle;
77
+ const sent = typeof handle === 'object' && handle !== null ? sentCounts.get(handle) : undefined;
78
+ const echoed = Number((currentOf(field) ?? field).props['mostRecentEventCount'] ?? 0);
79
+ const next = Math.max(sent ?? 0, echoed) + 1;
80
+ if (typeof handle === 'object' && handle !== null)
81
+ sentCounts.set(handle, next);
82
+ return next;
83
+ }
70
84
  function changeText(field, text) {
71
85
  if (isSearchBar(field))
72
86
  emit(field, 'topChangeText', { text });
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `fireEvent` and `userEvent`, as React Native Testing Library names them, emitting the Fabric\n * events the native side would.\n *\n * Each goes through `fabric.emit`, which is the handler the renderer registered with Fabric, so an\n * event takes the real route: the engine's responder negotiation for a touch, its bubbling for the\n * rest, then the component's listener. Nothing here calls a handler directly, and a disabled\n * pressable ignores a press here for the same reason it does on a phone.\n *\n * A touch is only sent where a finger could land, as React Native Testing Library decides it from\n * `pointerEvents`: a press, a type or a clear on a node that takes no touch does nothing at all.\n *\n * Both are async where RNTL's `fireEvent` is not. Change detection is zoneless and commits on a\n * later task, so an event has to be awaited before the tree it changed can be read.\n */\nimport { currentOf, ownerOf, settle } from './render.ts';\nimport { expoUiButtonDisabled, expoUiButtonPress, isExpoUiButton } from './expo-ui-button.ts';\nimport { flatten, isSearchBar, isTextInput, withParents } from './queries.ts';\nimport type { FakeFabricNode } from './test-utils.ts';\n\n/** A payload as a handler would see it (`{ nativeEvent }`), or the native event itself. */\nexport type EventPayload = Record<string, unknown>;\n\nconst nativeOf = (payload: EventPayload | undefined): Record<string, unknown> =>\n ((payload && 'nativeEvent' in payload ? payload['nativeEvent'] : payload) as Record<\n string,\n unknown\n >) ?? {};\n\nfunction emit(node: FakeFabricNode, type: string, nativeEvent: Record<string, unknown> = {}): void {\n ownerOf(node).fabric.emit(node, type, { target: node.reactTag, ...nativeEvent });\n}\n\n/** `press` -> `topPress`: the naming the renderer uses for `(touchEnd)` and every other event. */\nconst topLevel = (name: string): string =>\n name.startsWith('top') ? name : `top${name[0]!.toUpperCase()}${name.slice(1)}`;\n\n/** One finger, as Fabric reports it. The press machine reads `pageX`/`pageY`. */\nfunction touch(node: FakeFabricNode, phase: 'start' | 'end'): Record<string, unknown> {\n const point = {\n identifier: 0,\n target: node.reactTag,\n pageX: 0,\n pageY: 0,\n locationX: 0,\n locationY: 0,\n timestamp: Date.now(),\n };\n return { ...point, touches: phase === 'start' ? [point] : [], changedTouches: [point] };\n}\n\n/**\n * Whether a finger could reach the node, by RNTL's reading of `pointerEvents` on the node as last\n * committed: not when it is `none` or `box-none` itself, nor under a `none` or `box-only` view.\n *\n * The walk stops at a view committed with `layoutRoot`: `@expo/ui`'s view host for content\n * presented in its own window, a sheet's or a popover's, which takes its content's touches itself,\n * so what is around it, such as the sheet's `pointerEvents: 'none'` host, has no say. So does\n * SwiftUI's sheet, for the toolbar items it presents with that content.\n */\nfunction touchable(node: FakeFabricNode): boolean {\n withParents(ownerOf(node).fabric.committed);\n let blocking = 'box-none';\n for (let at: FakeFabricNode | null = currentOf(node) ?? node; at; at = at.parent ?? null) {\n if (at.props['layoutRoot'] === true || /ExpoUI_BottomSheetView$/.test(at.viewName)) return true;\n const pointerEvents = at.props['pointerEvents'];\n if (pointerEvents === 'none' || pointerEvents === blocking) return false;\n blocking = 'box-only';\n }\n return true;\n}\n\n/** The text field at or under a node, so a query for its wrapper still reaches the input. */\nfunction fieldOf(node: FakeFabricNode): FakeFabricNode {\n const current = currentOf(node) ?? node;\n const field = flatten([current]).find((n) => isTextInput(n) || isSearchBar(n));\n if (!field) throw new Error(`No TextInput or search bar at or under this ${node.viewName}.`);\n return field;\n}\n\n/** The count native would send next: one past the last one JS echoed back. */\nconst nextCount = (field: FakeFabricNode): number =>\n Number((currentOf(field) ?? field).props['mostRecentEventCount'] ?? 0) + 1;\n\nfunction changeText(field: FakeFabricNode, text: string): void {\n if (isSearchBar(field)) emit(field, 'topChangeText', { text });\n else emit(field, 'topChange', { text, eventCount: nextCount(field) });\n}\n\n/**\n * The finger lifting: the touch's end, and on `@expo/ui`'s button the press SwiftUI or Compose\n * fires for it, which a disabled one does not.\n */\nfunction release(node: FakeFabricNode): void {\n emit(node, 'topTouchEnd', touch(node, 'end'));\n const current = currentOf(node) ?? node;\n if (isExpoUiButton(current) && !expoUiButtonDisabled(current)) emit(node, expoUiButtonPress());\n}\n\nasync function press(node: FakeFabricNode): Promise<void> {\n if (!touchable(node)) return;\n emit(node, 'topTouchStart', touch(node, 'start'));\n release(node);\n await settle();\n}\n\nconst named = {\n press,\n async changeText(node: FakeFabricNode, text: string): Promise<void> {\n changeText(fieldOf(node), text);\n await settle();\n },\n async scroll(node: FakeFabricNode, payload?: EventPayload): Promise<void> {\n emit(node, 'topScroll', nativeOf(payload));\n await settle();\n },\n async focus(node: FakeFabricNode): Promise<void> {\n emit(node, 'topFocus');\n await settle();\n },\n async blur(node: FakeFabricNode): Promise<void> {\n emit(node, 'topBlur');\n await settle();\n },\n /** A `(layout)` event with the frame native would report: `{ x, y, width, height }`. */\n async layout(node: FakeFabricNode, frame: Partial<LayoutFrame> = {}): Promise<void> {\n emit(node, 'topLayout', { layout: { x: 0, y: 0, width: 0, height: 0, ...frame } });\n await settle();\n },\n};\n\n/** Where a view is in its parent, and its size, as `(layout)` reports them. */\nexport interface LayoutFrame {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** The frame in a layout payload, written bare, as `{ layout }`, or as `{ nativeEvent: { layout } }`. */\nfunction frameOf(payload: unknown): Partial<LayoutFrame> {\n const given = (payload ?? {}) as { nativeEvent?: unknown; layout?: Partial<LayoutFrame> };\n const event = (given.nativeEvent ?? given) as { layout?: Partial<LayoutFrame> };\n return event.layout ?? (event as Partial<LayoutFrame>);\n}\n\ntype FireEvent = ((node: FakeFabricNode, name: string, payload?: unknown) => Promise<void>) &\n typeof named;\n\n/**\n * `fireEvent(node, 'press')`, `fireEvent(node, 'changeText', 'Ada')`, or any other event by name -\n * `fireEvent(node, 'submitEditing', { text })` emits `topSubmitEditing`.\n */\nexport const fireEvent: FireEvent = Object.assign(\n async (node: FakeFabricNode, name: string, payload?: unknown): Promise<void> => {\n if (name === 'changeText') return named.changeText(node, String(payload));\n if (name === 'press' || name === 'scroll' || name === 'focus' || name === 'blur') {\n return named[name](node, payload as EventPayload);\n }\n // A layout by name takes the frame, or the event wrapped as `fireEvent.scroll` takes one.\n if (name === 'layout') return named.layout(node, frameOf(payload));\n emit(node, topLevel(name), nativeOf(payload as EventPayload));\n await settle();\n },\n named,\n);\n\n/** Options for `userEvent.type`, named as React Native Testing Library names them. */\nexport interface TypeOptions {\n /** Leave the field focused afterwards. By default typing ends with the field blurring. */\n skipBlur?: boolean;\n /** Press return at the end: `topSubmitEditing`, before any blur. */\n submitEditing?: boolean;\n}\n\n/**\n * Interactions as a person performs them: several events, a task apart, in the order the platform\n * sends them. Slower than `fireEvent`, and closer to what a device does.\n */\nexport interface UserEvent {\n /** A finger down and up, a task apart, through the responder. */\n press(node: FakeFabricNode): Promise<void>;\n /** A finger held for `duration` ms (500, the long-press delay, by default). */\n longPress(node: FakeFabricNode, options?: { duration?: number }): Promise<void>;\n /** Focus the field, then one change per character, each with the next `eventCount`. */\n type(node: FakeFabricNode, text: string, options?: TypeOptions): Promise<void>;\n /** Focus the field, empty it, and blur. */\n clear(node: FakeFabricNode): Promise<void>;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nasync function hold(node: FakeFabricNode, duration: number): Promise<void> {\n if (!touchable(node)) return;\n emit(node, 'topTouchStart', touch(node, 'start'));\n await settle();\n if (duration) await sleep(duration);\n release(node);\n await settle();\n}\n\nasync function endEditing(field: FakeFabricNode, text: string): Promise<void> {\n emit(field, 'topEndEditing', { text });\n emit(field, 'topBlur');\n await settle();\n}\n\nconst user: UserEvent = {\n press: (node) => hold(node, 0),\n longPress: (node, { duration = 500 } = {}) => hold(node, duration),\n async type(node, text, options = {}) {\n const field = fieldOf(node);\n if (field.props['editable'] === false || !touchable(field)) return;\n emit(field, 'topFocus');\n await settle();\n const maxLength = field.props['maxLength'];\n const limit = typeof maxLength === 'number' ? maxLength : Infinity;\n let value = String((currentOf(field) ?? field).props['text'] ?? '');\n for (const key of text) {\n // `maxLength` is enforced natively: the field never calls back with text past it, so a key\n // pressed once the limit is reached is swallowed the same way it would be on a device.\n if (value.length >= limit) continue;\n value += key;\n emit(field, 'topKeyPress', { key });\n changeText(field, value);\n await settle();\n }\n if (options.submitEditing) emit(field, 'topSubmitEditing', { text: value });\n if (!options.skipBlur) await endEditing(field, value);\n else await settle();\n },\n async clear(node) {\n const field = fieldOf(node);\n if (field.props['editable'] === false || !touchable(field)) return;\n emit(field, 'topFocus');\n changeText(field, '');\n await settle();\n await endEditing(field, '');\n },\n};\n\n/** RNTL's `userEvent`: `userEvent.setup()` for an instance, or the same calls directly. */\nexport const userEvent: UserEvent & { setup(): UserEvent } = { ...user, setup: () => user };\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `fireEvent` and `userEvent`, as React Native Testing Library names them, emitting the Fabric\n * events the native side would.\n *\n * Each goes through `fabric.emit`, which is the handler the renderer registered with Fabric, so an\n * event takes the real route: the engine's responder negotiation for a touch, its bubbling for the\n * rest, then the component's listener. Nothing here calls a handler directly, and a disabled\n * pressable ignores a press here for the same reason it does on a phone.\n *\n * A touch is only sent where a finger could land, as React Native Testing Library decides it from\n * `pointerEvents`: a press, a type or a clear on a node that takes no touch does nothing at all.\n *\n * Both are async where RNTL's `fireEvent` is not. Change detection is zoneless and commits on a\n * later task, so an event has to be awaited before the tree it changed can be read.\n */\nimport { currentOf, ownerOf, settle } from './render.ts';\nimport { expoUiButtonDisabled, expoUiButtonPress, isExpoUiButton } from './expo-ui-button.ts';\nimport { flatten, isSearchBar, isTextInput, withParents } from './queries.ts';\nimport type { FakeFabricNode } from './test-utils.ts';\n\n/** A payload as a handler would see it (`{ nativeEvent }`), or the native event itself. */\nexport type EventPayload = Record<string, unknown>;\n\nconst nativeOf = (payload: EventPayload | undefined): Record<string, unknown> =>\n ((payload && 'nativeEvent' in payload ? payload['nativeEvent'] : payload) as Record<\n string,\n unknown\n >) ?? {};\n\nfunction emit(node: FakeFabricNode, type: string, nativeEvent: Record<string, unknown> = {}): void {\n ownerOf(node).fabric.emit(node, type, { target: node.reactTag, ...nativeEvent });\n}\n\n/** `press` -> `topPress`: the naming the renderer uses for `(touchEnd)` and every other event. */\nconst topLevel = (name: string): string =>\n name.startsWith('top') ? name : `top${name[0]!.toUpperCase()}${name.slice(1)}`;\n\n/** One finger, as Fabric reports it. The press machine reads `pageX`/`pageY`. */\nfunction touch(node: FakeFabricNode, phase: 'start' | 'end'): Record<string, unknown> {\n const point = {\n identifier: 0,\n target: node.reactTag,\n pageX: 0,\n pageY: 0,\n locationX: 0,\n locationY: 0,\n timestamp: Date.now(),\n };\n return { ...point, touches: phase === 'start' ? [point] : [], changedTouches: [point] };\n}\n\n/**\n * Whether a finger could reach the node, by RNTL's reading of `pointerEvents` on the node as last\n * committed: not when it is `none` or `box-none` itself, nor under a `none` or `box-only` view.\n *\n * The walk stops at a view committed with `layoutRoot`: `@expo/ui`'s view host for content\n * presented in its own window, a sheet's or a popover's, which takes its content's touches itself,\n * so what is around it, such as the sheet's `pointerEvents: 'none'` host, has no say. So does\n * SwiftUI's sheet, for the toolbar items it presents with that content.\n */\nfunction touchable(node: FakeFabricNode): boolean {\n withParents(ownerOf(node).fabric.committed);\n let blocking = 'box-none';\n for (let at: FakeFabricNode | null = currentOf(node) ?? node; at; at = at.parent ?? null) {\n if (at.props['layoutRoot'] === true || /ExpoUI_BottomSheetView$/.test(at.viewName)) return true;\n const pointerEvents = at.props['pointerEvents'];\n if (pointerEvents === 'none' || pointerEvents === blocking) return false;\n blocking = 'box-only';\n }\n return true;\n}\n\n/** The text field at or under a node, so a query for its wrapper still reaches the input. */\nfunction fieldOf(node: FakeFabricNode): FakeFabricNode {\n const current = currentOf(node) ?? node;\n const field = flatten([current]).find((n) => isTextInput(n) || isSearchBar(n));\n if (!field) throw new Error(`No TextInput or search bar at or under this ${node.viewName}.`);\n return field;\n}\n\n/**\n * The count each field last sent, keyed by the node's instance handle, which its clones share, so\n * a field gone from the tree takes its count with it. Native keeps its own as a real field does,\n * since iOS is not sent it back as a prop.\n */\nconst sentCounts = new WeakMap<object, number>();\n\n/** The count native would send next: one past the last one it sent, or that JS echoed back. */\nfunction nextCount(field: FakeFabricNode): number {\n const handle = field.instanceHandle;\n const sent = typeof handle === 'object' && handle !== null ? sentCounts.get(handle) : undefined;\n const echoed = Number((currentOf(field) ?? field).props['mostRecentEventCount'] ?? 0);\n const next = Math.max(sent ?? 0, echoed) + 1;\n if (typeof handle === 'object' && handle !== null) sentCounts.set(handle, next);\n return next;\n}\n\nfunction changeText(field: FakeFabricNode, text: string): void {\n if (isSearchBar(field)) emit(field, 'topChangeText', { text });\n else emit(field, 'topChange', { text, eventCount: nextCount(field) });\n}\n\n/**\n * The finger lifting: the touch's end, and on `@expo/ui`'s button the press SwiftUI or Compose\n * fires for it, which a disabled one does not.\n */\nfunction release(node: FakeFabricNode): void {\n emit(node, 'topTouchEnd', touch(node, 'end'));\n const current = currentOf(node) ?? node;\n if (isExpoUiButton(current) && !expoUiButtonDisabled(current)) emit(node, expoUiButtonPress());\n}\n\nasync function press(node: FakeFabricNode): Promise<void> {\n if (!touchable(node)) return;\n emit(node, 'topTouchStart', touch(node, 'start'));\n release(node);\n await settle();\n}\n\nconst named = {\n press,\n async changeText(node: FakeFabricNode, text: string): Promise<void> {\n changeText(fieldOf(node), text);\n await settle();\n },\n async scroll(node: FakeFabricNode, payload?: EventPayload): Promise<void> {\n emit(node, 'topScroll', nativeOf(payload));\n await settle();\n },\n async focus(node: FakeFabricNode): Promise<void> {\n emit(node, 'topFocus');\n await settle();\n },\n async blur(node: FakeFabricNode): Promise<void> {\n emit(node, 'topBlur');\n await settle();\n },\n /** A `(layout)` event with the frame native would report: `{ x, y, width, height }`. */\n async layout(node: FakeFabricNode, frame: Partial<LayoutFrame> = {}): Promise<void> {\n emit(node, 'topLayout', { layout: { x: 0, y: 0, width: 0, height: 0, ...frame } });\n await settle();\n },\n};\n\n/** Where a view is in its parent, and its size, as `(layout)` reports them. */\nexport interface LayoutFrame {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** The frame in a layout payload, written bare, as `{ layout }`, or as `{ nativeEvent: { layout } }`. */\nfunction frameOf(payload: unknown): Partial<LayoutFrame> {\n const given = (payload ?? {}) as { nativeEvent?: unknown; layout?: Partial<LayoutFrame> };\n const event = (given.nativeEvent ?? given) as { layout?: Partial<LayoutFrame> };\n return event.layout ?? (event as Partial<LayoutFrame>);\n}\n\ntype FireEvent = ((node: FakeFabricNode, name: string, payload?: unknown) => Promise<void>) &\n typeof named;\n\n/**\n * `fireEvent(node, 'press')`, `fireEvent(node, 'changeText', 'Ada')`, or any other event by name -\n * `fireEvent(node, 'submitEditing', { text })` emits `topSubmitEditing`.\n */\nexport const fireEvent: FireEvent = Object.assign(\n async (node: FakeFabricNode, name: string, payload?: unknown): Promise<void> => {\n if (name === 'changeText') return named.changeText(node, String(payload));\n if (name === 'press' || name === 'scroll' || name === 'focus' || name === 'blur') {\n return named[name](node, payload as EventPayload);\n }\n // A layout by name takes the frame, or the event wrapped as `fireEvent.scroll` takes one.\n if (name === 'layout') return named.layout(node, frameOf(payload));\n emit(node, topLevel(name), nativeOf(payload as EventPayload));\n await settle();\n },\n named,\n);\n\n/** Options for `userEvent.type`, named as React Native Testing Library names them. */\nexport interface TypeOptions {\n /** Leave the field focused afterwards. By default typing ends with the field blurring. */\n skipBlur?: boolean;\n /** Press return at the end: `topSubmitEditing`, before any blur. */\n submitEditing?: boolean;\n}\n\n/**\n * Interactions as a person performs them: several events, a task apart, in the order the platform\n * sends them. Slower than `fireEvent`, and closer to what a device does.\n */\nexport interface UserEvent {\n /** A finger down and up, a task apart, through the responder. */\n press(node: FakeFabricNode): Promise<void>;\n /** A finger held for `duration` ms (500, the long-press delay, by default). */\n longPress(node: FakeFabricNode, options?: { duration?: number }): Promise<void>;\n /** Focus the field, then one change per character, each with the next `eventCount`. */\n type(node: FakeFabricNode, text: string, options?: TypeOptions): Promise<void>;\n /** Focus the field, empty it, and blur. */\n clear(node: FakeFabricNode): Promise<void>;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nasync function hold(node: FakeFabricNode, duration: number): Promise<void> {\n if (!touchable(node)) return;\n emit(node, 'topTouchStart', touch(node, 'start'));\n await settle();\n if (duration) await sleep(duration);\n release(node);\n await settle();\n}\n\nasync function endEditing(field: FakeFabricNode, text: string): Promise<void> {\n emit(field, 'topEndEditing', { text });\n emit(field, 'topBlur');\n await settle();\n}\n\nconst user: UserEvent = {\n press: (node) => hold(node, 0),\n longPress: (node, { duration = 500 } = {}) => hold(node, duration),\n async type(node, text, options = {}) {\n const field = fieldOf(node);\n if (field.props['editable'] === false || !touchable(field)) return;\n emit(field, 'topFocus');\n await settle();\n const maxLength = field.props['maxLength'];\n const limit = typeof maxLength === 'number' ? maxLength : Infinity;\n let value = String((currentOf(field) ?? field).props['text'] ?? '');\n for (const key of text) {\n // `maxLength` is enforced natively: the field never calls back with text past it, so a key\n // pressed once the limit is reached is swallowed the same way it would be on a device.\n if (value.length >= limit) continue;\n value += key;\n emit(field, 'topKeyPress', { key });\n changeText(field, value);\n await settle();\n }\n if (options.submitEditing) emit(field, 'topSubmitEditing', { text: value });\n if (!options.skipBlur) await endEditing(field, value);\n else await settle();\n },\n async clear(node) {\n const field = fieldOf(node);\n if (field.props['editable'] === false || !touchable(field)) return;\n emit(field, 'topFocus');\n changeText(field, '');\n await settle();\n await endEditing(field, '');\n },\n};\n\n/** RNTL's `userEvent`: `userEvent.setup()` for an instance, or the same calls directly. */\nexport const userEvent: UserEvent & { setup(): UserEvent } = { ...user, setup: () => user };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-native/testing",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
4
4
  "description": "Test Angular Native components in Node with no simulator: a fake Fabric, Testing Library queries and events, and Vitest and node:test integration.",
5
5
  "keywords": [
6
6
  "angular",
@@ -34,16 +34,16 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@ng-native/fabric": "0.18.0",
38
- "@ng-native/metro": "0.18.0",
39
- "@ng-native/platform": "0.18.0"
37
+ "@ng-native/fabric": "0.18.2",
38
+ "@ng-native/metro": "0.18.2",
39
+ "@ng-native/platform": "0.18.2"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@angular/compiler": "^22.0.0",
43
43
  "@angular/core": "^22.0.0",
44
44
  "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
45
45
  "vitest": "^4.0.8 || ^5.0.0",
46
- "@ng-native/components": "0.18.0"
46
+ "@ng-native/components": "0.18.2"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
49
  "@angular/compiler": {
@@ -62,9 +62,9 @@
62
62
  "@angular/core": "22.2.0",
63
63
  "@angular/forms": "22.2.0",
64
64
  "@angular/router": "22.2.0",
65
- "@ng-native/components": "0.18.0",
66
- "@ng-native/device": "0.18.0",
67
- "@ng-native/router": "0.18.0",
65
+ "@ng-native/components": "0.18.2",
66
+ "@ng-native/device": "0.18.2",
67
+ "@ng-native/router": "0.18.2",
68
68
  "vite": "^8.3.1",
69
69
  "vitest": "^5.0.2"
70
70
  },