@octane-xplat/ui 0.7.1 → 0.7.3

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 (63) hide show
  1. package/dist/native/CommandPalette.tsrx.js +7 -1
  2. package/dist/native/Grid.tsrx.js +31 -2
  3. package/dist/native/Hoverable.mobile.tsrx.js +39 -0
  4. package/dist/native/Pagination.tsrx.js +15 -12
  5. package/dist/native/Popover.tsrx.js +18 -7
  6. package/dist/native/Screen.tsrx.js +1 -1
  7. package/dist/native/Sheet.tsrx.js +1 -1
  8. package/dist/native/Slider.tsrx.js +23 -14
  9. package/dist/native/Text.tsrx.js +16 -0
  10. package/dist/native/Tooltip.mobile.tsrx.js +39 -0
  11. package/dist/native/WebView.tsrx.js +17 -2
  12. package/dist/native/_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js +9 -0
  13. package/dist/native/escape-props.js +2 -0
  14. package/dist/native/grid-auto-placement.js +83 -0
  15. package/dist/native/grid-placement.js +20 -0
  16. package/dist/native/index.shared.js +2 -2
  17. package/dist/native/octane-client-build.json +1 -1
  18. package/dist/native/shared.js +2 -2
  19. package/dist/native/sheet-service.js +1 -1
  20. package/dist/native/{tap-to-blur.mobile.js → tap-to-blur.ios.js} +20 -32
  21. package/dist/native/ui.js +2 -2
  22. package/dist/native/useMeasure.tsrx.js +17 -18
  23. package/dist/web/index.shared.js +92 -0
  24. package/dist/web/octane-client-build.json +1 -1
  25. package/dist/web/ui.js +1 -0
  26. package/package.json +1 -1
  27. package/src/CommandPalette.tsrx +7 -1
  28. package/src/Grid.tsrx +30 -1
  29. package/src/Pagination.tsrx +6 -4
  30. package/src/Popover.tsrx +20 -6
  31. package/src/Screen.tsrx +1 -1
  32. package/src/Sheet.tsrx +1 -1
  33. package/src/Slider.tsrx +13 -11
  34. package/src/Text.tsrx +34 -0
  35. package/src/WebView.tsrx +19 -10
  36. package/src/escape-props.ts +11 -1
  37. package/src/grid-auto-placement.ts +121 -0
  38. package/src/grid-placement.ts +29 -0
  39. package/src/index.shared.ts +84 -84
  40. package/src/index.web.ts +1 -261
  41. package/src/sheet-service.ts +1 -1
  42. package/src/tap-to-blur.ios.ts +90 -0
  43. package/src/tap-to-blur.mobile.ts +12 -94
  44. package/src/theme/tokens.css +24 -8
  45. package/src/useMeasure.tsrx +20 -21
  46. package/types/generated/.tsrx-typegen-manifest.json +10 -1
  47. package/types/generated/Grid.d.ts +0 -1
  48. package/types/generated/Hoverable.mobile.d.ts +8 -0
  49. package/types/generated/Tooltip.mobile.d.ts +7 -0
  50. package/types/generated/camera-permission.web.d.ts +2 -0
  51. package/types/generated/escape-props.d.ts +4 -0
  52. package/types/generated/escape-props.web.d.ts +5 -0
  53. package/types/generated/grid-auto-placement.d.ts +13 -0
  54. package/types/generated/grid-placement.d.ts +5 -0
  55. package/types/generated/index.web.d.ts +1 -111
  56. package/types/generated/tap-to-blur.ios.d.ts +13 -0
  57. package/types/generated/tap-to-blur.mobile.d.ts +1 -15
  58. package/types/generated/text-write.web.d.ts +4 -0
  59. package/types/generated/toast.d.ts +3 -0
  60. package/types/generated/toast.web.d.ts +3 -0
  61. package/dist/native/Hoverable.tsrx.js +0 -139
  62. package/dist/native/Tooltip.tsrx.js +0 -131
  63. package/types/generated/theme/colorScheme.web.d.ts +0 -5
@@ -1,5 +1,5 @@
1
- import { Application } from "@nativescript/core";
2
- //#region src/tap-to-blur.mobile.ts
1
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js";
2
+ //#region src/tap-to-blur.ios.ts
3
3
  /** Tap-outside-to-blur: iOS keeps a focused UITextField/UITextView as first
4
4
  * responder until something else takes it, so a tap on blank chrome never
5
5
  * dismisses the keyboard on its own.
@@ -11,36 +11,15 @@ import { Application } from "@nativescript/core";
11
11
  * editable text view (otherwise the field would focus-then-blur in the same
12
12
  * tap — recognizers fire on touch-up, after the field's touch-down focus).
13
13
  * Keyboard taps live in a separate UIWindow, so they never reach it.
14
- * `cancelsTouchesInView` stays false so taps still reach buttons.
15
- *
16
- * Android: hides the soft keyboard from the currently focused view when a
17
- * tap lands on the attached container. */
14
+ * `cancelsTouchesInView` stays false so taps still reach buttons. */
18
15
  function attachTapToBlur(view) {
19
16
  const marker = "__xplatTapToBlur";
20
17
  if (!view || view[marker]) return () => {};
21
18
  view[marker] = true;
22
- if (Application.ios) {
23
- iosAttach();
24
- return () => {
25
- view[marker] = false;
26
- iosDetach();
27
- };
28
- }
29
- if (Application.android) {
30
- const handler = (_args) => {
31
- const activity = Application.android.foregroundActivity;
32
- const focused = activity?.currentFocus;
33
- if (!focused) return;
34
- activity.getSystemService(android.content.Context.INPUT_METHOD_SERVICE)?.hideSoftInputFromWindow(focused.getWindowToken(), 0);
35
- };
36
- view.on("tap", handler);
37
- return () => {
38
- view[marker] = false;
39
- view.off("tap", handler);
40
- };
41
- }
19
+ iosAttach();
42
20
  return () => {
43
21
  view[marker] = false;
22
+ iosDetach();
44
23
  };
45
24
  }
46
25
  var iosCount = 0;
@@ -53,25 +32,34 @@ function iosAttach() {
53
32
  const appWin = appWindow();
54
33
  if (!appWin) return;
55
34
  iosWindow = appWin;
56
- const Owner = class extends NSObject {
35
+ let TapToBlurRecognizerTarget = class TapToBlurRecognizerTarget extends NSObject {
36
+ static {
37
+ this.ObjCProtocols = [UIGestureRecognizerDelegate];
38
+ }
57
39
  static {
58
40
  this.ObjCExposedMethods = { tap: {
59
41
  returns: interop.types.void,
60
42
  params: [UITapGestureRecognizer]
61
43
  } };
62
44
  }
63
- tap(recognizer) {
64
- const point = recognizer.locationInView(iosWindow);
65
- let hit = iosWindow.hitTest(point, null);
45
+ gestureRecognizerShouldReceiveTouch(_recognizer, touch) {
46
+ let hit = touch.view;
66
47
  while (hit) {
67
- if (hit instanceof UITextField || hit instanceof UITextView) return;
48
+ const textField = hit.isKindOfClass(UITextField.class());
49
+ const textView = hit.isKindOfClass(UITextView.class());
50
+ if (textField || textView) return false;
68
51
  hit = hit.superview;
69
52
  }
53
+ return true;
54
+ }
55
+ tap(_recognizer) {
70
56
  iosWindow.endEditing(true);
71
57
  }
72
58
  };
73
- iosTarget = Owner.new();
59
+ TapToBlurRecognizerTarget = __decorate([NativeClass], TapToBlurRecognizerTarget);
60
+ iosTarget = TapToBlurRecognizerTarget.new();
74
61
  iosRecognizer = UITapGestureRecognizer.alloc().initWithTargetAction(iosTarget, "tap");
62
+ iosRecognizer.delegate = iosTarget;
75
63
  iosRecognizer.cancelsTouchesInView = false;
76
64
  appWin.addGestureRecognizer(iosRecognizer);
77
65
  }
package/dist/native/ui.js CHANGED
@@ -68,8 +68,8 @@ import { ScrollBox } from "./ScrollBox.tsrx.js";
68
68
  import { ScrollView } from "./ScrollView.tsrx.js";
69
69
  import { VirtualList } from "./VirtualList.tsrx.js";
70
70
  import { WebView } from "./WebView.tsrx.js";
71
- import { Hoverable } from "./Hoverable.tsrx.js";
72
- import { Tooltip } from "./Tooltip.tsrx.js";
71
+ import { Hoverable } from "./Hoverable.mobile.tsrx.js";
72
+ import { Tooltip } from "./Tooltip.mobile.tsrx.js";
73
73
  import { showToast } from "./toast-anchor.tsrx.js";
74
74
  import { useAnimation } from "./anim.tsrx.js";
75
75
  import { styled } from "./styled.tsrx.js";
@@ -1,4 +1,4 @@
1
- import { hookSlots, useCallback, useEffect, useState } from "@nativescript-community/octane";
1
+ import { hookSlots, useCallback, useEffect, useRef, useState } from "@nativescript-community/octane";
2
2
  //#region src/useMeasure.tsrx
3
3
  var _hs$ = /* @__PURE__ */ hookSlots(4);
4
4
  var _h$0 = /* @__PURE__ */ Symbol(_hs$);
@@ -17,44 +17,43 @@ function readBounds(view) {
17
17
  };
18
18
  }
19
19
  function useMeasure(options = {}) {
20
- const [view, setView] = useState(null, _h$0);
21
- const [bounds, setBounds] = useState(null, _h$1);
20
+ const [bounds, setBounds] = useState(null, _h$0);
21
+ const detach = useRef(null, _h$1);
22
22
  const observe = options.observe ?? true;
23
- const bind = useCallback((next) => {
24
- setView((current) => current === next ? current : next);
25
- }, [], _h$2);
26
- useEffect(() => {
23
+ const bind = useCallback((view) => {
24
+ detach.current?.();
25
+ detach.current = null;
27
26
  if (!view) {
28
27
  setBounds(null);
29
28
  return;
30
29
  }
31
30
  const update = () => {
32
31
  const next = readBounds(view);
33
- setBounds((prev) => prev && next && prev.x === next.x && prev.y === next.y && prev.width === next.width && prev.height === next.height ? prev : next);
32
+ setBounds((prev) => {
33
+ return prev && next && prev.x === next.x && prev.y === next.y && prev.width === next.width && prev.height === next.height ? prev : next;
34
+ });
34
35
  };
35
- const onLoaded = update;
36
- view.on?.("loaded", onLoaded);
36
+ view.on?.("loaded", update);
37
37
  update();
38
+ const scrollParents = [];
38
39
  if (observe) {
39
40
  view.on?.("layoutChanged", update);
40
41
  let parent = view.parent;
41
42
  while (parent) {
42
43
  parent.on?.("scroll", update);
44
+ scrollParents.push(parent);
43
45
  parent = parent.parent;
44
46
  }
45
47
  }
46
- return () => {
47
- view.off?.("loaded", onLoaded);
48
+ detach.current = () => {
49
+ view.off?.("loaded", update);
48
50
  if (observe) {
49
51
  view.off?.("layoutChanged", update);
50
- let parent = view.parent;
51
- while (parent) {
52
- parent.off?.("scroll", update);
53
- parent = parent.parent;
54
- }
52
+ for (const parent of scrollParents) parent.off?.("scroll", update);
55
53
  }
56
54
  };
57
- }, [view, observe], _h$3);
55
+ }, [observe], _h$2);
56
+ useEffect(() => () => detach.current?.(), [], _h$3);
58
57
  return {
59
58
  bind,
60
59
  bounds
@@ -0,0 +1,92 @@
1
+ import { View } from "./View.web.tsrx.js";
2
+ import { Row } from "./Row.web.tsrx.js";
3
+ import { Grid } from "./Grid.web.tsrx.js";
4
+ import { Stack } from "./Stack.web.tsrx.js";
5
+ import { Absolute } from "./Absolute.web.tsrx.js";
6
+ import { setTranslate } from "./translate.web.js";
7
+ import { Spacer } from "./Spacer.web.tsrx.js";
8
+ import { Text } from "./Text.web.tsrx.js";
9
+ import { RichText, RichTextSpan } from "./RichText.web.tsrx.js";
10
+ import { Pressable } from "./Pressable.web.tsrx.js";
11
+ import { ActivityIndicator } from "./ActivityIndicator.web.tsrx.js";
12
+ import { Button } from "./Button.web.tsrx.js";
13
+ import { Collapsible } from "./Collapsible.web.tsrx.js";
14
+ import { Accordion } from "./Accordion.web.tsrx.js";
15
+ import { Checkbox } from "./Checkbox.web.tsrx.js";
16
+ import { RadioGroup } from "./RadioGroup.web.tsrx.js";
17
+ import { getColorScheme, useColorScheme } from "./theme/colorScheme.web.js";
18
+ import { applyThemeClasses, getThemePreference, getThemeScheme, onThemeSchemeChange, setThemePreference, themeSchemeClasses, useThemeScheme } from "./theme/theme-scheme.js";
19
+ import { Popover } from "./Popover.web.tsrx.js";
20
+ import { registerIcon, registerIcons } from "./icons.js";
21
+ import { Icon } from "./Icon.web.tsrx.js";
22
+ import { DropdownMenu } from "./DropdownMenu.web.tsrx.js";
23
+ import { ContextMenu } from "./ContextMenu.web.tsrx.js";
24
+ import { Badge } from "./Badge.web.tsrx.js";
25
+ import { Separator } from "./Separator.web.tsrx.js";
26
+ import { Skeleton } from "./Skeleton.web.tsrx.js";
27
+ import { Avatar } from "./Avatar.web.tsrx.js";
28
+ import { AvatarGroup } from "./AvatarGroup.web.tsrx.js";
29
+ import { FormField } from "./FormField.web.tsrx.js";
30
+ import { FieldGroup } from "./FieldGroup.web.tsrx.js";
31
+ import { ListItem } from "./ListItem.web.tsrx.js";
32
+ import { TextInput } from "./TextInput.web.tsrx.js";
33
+ import { InputNumber } from "./InputNumber.web.tsrx.js";
34
+ import { PinInput } from "./PinInput.web.tsrx.js";
35
+ import { Select } from "./Select.web.tsrx.js";
36
+ import { Combobox, InputMenu, SelectMenu } from "./aliases.web.tsrx.js";
37
+ import { InputTags } from "./InputTags.web.tsrx.js";
38
+ import { InputRating } from "./InputRating.web.tsrx.js";
39
+ import { CheckboxGroup } from "./CheckboxGroup.web.tsrx.js";
40
+ import { Breadcrumb } from "./Breadcrumb.web.tsrx.js";
41
+ import { Pagination } from "./Pagination.web.tsrx.js";
42
+ import { Stepper } from "./Stepper.web.tsrx.js";
43
+ import { NavigationMenu } from "./NavigationMenu.web.tsrx.js";
44
+ import { Overlay } from "./Overlay.web.tsrx.js";
45
+ import { CommandPalette } from "./CommandPalette.web.tsrx.js";
46
+ import { Table } from "./Table.web.tsrx.js";
47
+ import { Timeline } from "./Timeline.web.tsrx.js";
48
+ import { Tree } from "./Tree.web.tsrx.js";
49
+ import { Alert } from "./Alert.web.tsrx.js";
50
+ import { Card } from "./Card.web.tsrx.js";
51
+ import { Chip } from "./Chip.web.tsrx.js";
52
+ import { Kbd } from "./Kbd.web.tsrx.js";
53
+ import { Empty } from "./Empty.web.tsrx.js";
54
+ import { Banner } from "./Banner.web.tsrx.js";
55
+ import { User } from "./User.web.tsrx.js";
56
+ import { Meter } from "./Meter.web.tsrx.js";
57
+ import { ProgressGroup } from "./ProgressGroup.web.tsrx.js";
58
+ import { Link } from "./Link.web.tsrx.js";
59
+ import { defineRoutes, deriveRouteManifest, mergeRouteManifests } from "./route-table.js";
60
+ import { addBackInterceptor, addRoutes, canGoBack, currentModalRoute, currentRoute, hrefFor, layoutsFor, popRoute, pushDeepLink, pushRoute, redirect, registerRoutes, registerScreens, routeFor, routeStacks, screenFor, useCanGoBack, useModalRoute, useRoute } from "./route.web.js";
61
+ import { NavLink } from "./NavLink.web.tsrx.js";
62
+ import { TextArea } from "./TextArea.web.tsrx.js";
63
+ import { SearchInput } from "./SearchInput.web.tsrx.js";
64
+ import { SegmentedControl } from "./SegmentedControl.web.tsrx.js";
65
+ import { ScrollView } from "./ScrollView.web.tsrx.js";
66
+ import { ScrollBox } from "./ScrollBox.web.tsrx.js";
67
+ import { VirtualList } from "./VirtualList.web.tsrx.js";
68
+ import { Image } from "./Image.web.tsrx.js";
69
+ import { WebView } from "./WebView.web.tsrx.js";
70
+ import { Hoverable } from "./Hoverable.web.tsrx.js";
71
+ import { Tooltip } from "./Tooltip.web.tsrx.js";
72
+ import { showToast } from "./toast-anchor.web.tsrx.js";
73
+ import { useAnimation } from "./anim.web.tsrx.js";
74
+ import { styled } from "./styled.web.tsrx.js";
75
+ import { openWindow } from "./windows.web.js";
76
+ import { Screen } from "./Screen.web.tsrx.js";
77
+ import { Tabs } from "./Tabs.web.tsrx.js";
78
+ import { Switch } from "./Switch.web.tsrx.js";
79
+ import { useSafeAreaInsets } from "./safeAreaInsets.web.tsrx.js";
80
+ import { SafeArea } from "./SafeArea.web.tsrx.js";
81
+ import { Drawer } from "./Drawer.web.tsrx.js";
82
+ import { useMeasure } from "./useMeasure.web.tsrx.js";
83
+ import { Slider } from "./Slider.web.tsrx.js";
84
+ import { Heading } from "./Heading.web.tsrx.js";
85
+ import { isNative } from "./platform.web.js";
86
+ import { getStack, registerStack, stackEntries } from "./stacks.web.js";
87
+ import { useBackInterceptor } from "./use-back.web.tsrx.js";
88
+ import { createStore } from "./store.js";
89
+ import { useStore } from "./use-store.web.tsrx.js";
90
+ import { Sheet } from "./Sheet.web.tsrx.js";
91
+ import { closeSheet, openSheet } from "./sheet-service.web.js";
92
+ export { Absolute, Accordion, ActivityIndicator, Alert, Avatar, AvatarGroup, Badge, Banner, Breadcrumb, Button, Card, Checkbox, CheckboxGroup, Chip, Collapsible, View as Column, Combobox, CommandPalette, ContextMenu, Drawer, DropdownMenu, Empty, FieldGroup, FormField, Grid, Heading, Hoverable, Icon, Image, InputMenu, InputNumber, InputRating, InputTags, Kbd, Link, ListItem, Meter, NavLink, NavigationMenu, Overlay, Pagination, PinInput, Popover, Pressable, ProgressGroup, RadioGroup, RichText, RichTextSpan, Row, SafeArea, Screen, ScrollBox, ScrollView, SearchInput, SegmentedControl, Select, SelectMenu, Separator, Sheet, Skeleton, Slider, Spacer, Stack, Stepper, Switch, Table, Tabs, Text, TextArea, TextInput, Timeline, Tooltip, Tree, User, View, VirtualList, WebView, addBackInterceptor, addRoutes, applyThemeClasses, canGoBack, closeSheet, createStore, currentModalRoute, currentRoute, defineRoutes, deriveRouteManifest, getColorScheme, getStack, getThemePreference, getThemeScheme, hrefFor, isNative, layoutsFor as layoutsForRoute, mergeRouteManifests, onThemeSchemeChange, openSheet, openWindow, popRoute, pushDeepLink, pushRoute, redirect, registerIcon, registerIcons, registerRoutes, registerScreens, registerStack, routeFor, routeStacks, screenFor, setThemePreference, setTranslate, showToast, stackEntries, styled, themeSchemeClasses, useAnimation, useBackInterceptor, useCanGoBack, useColorScheme, useMeasure, useModalRoute, useRoute, useSafeAreaInsets, useStore, useThemeScheme };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "buildId": "c093a881-61b5-4e75-92ac-bfe0cf48326d",
3
+ "buildId": "0ba2ee91-551d-42cf-a6c9-dedc87a8a53b",
4
4
  "mode": "production",
5
5
  "capabilities": {
6
6
  "independentHydration": false
package/dist/web/ui.js CHANGED
@@ -89,4 +89,5 @@ import { createStore } from "./store.js";
89
89
  import { useStore } from "./use-store.web.tsrx.js";
90
90
  import { Sheet } from "./Sheet.web.tsrx.js";
91
91
  import { closeSheet, openSheet } from "./sheet-service.web.js";
92
+ import "./index.shared.js";
92
93
  export { Absolute, Accordion, ActivityIndicator, Alert, Avatar, AvatarGroup, Badge, Banner, Breadcrumb, Button, Card, Checkbox, CheckboxGroup, Chip, Collapsible, View as Column, Combobox, CommandPalette, ContextMenu, Drawer, DropdownMenu, Empty, FieldGroup, FormField, Grid, Heading, Hoverable, Icon, Image, InputMenu, InputNumber, InputRating, InputTags, Kbd, Link, ListItem, Meter, NavLink, NavigationMenu, Overlay, Pagination, PinInput, Popover, Pressable, ProgressGroup, RadioGroup, RichText, RichTextSpan, Row, SafeArea, Screen, ScrollBox, ScrollView, SearchInput, SegmentedControl, Select, SelectMenu, Separator, Sheet, Skeleton, Slider, Spacer, Stack, Stepper, Switch, Table, Tabs, Text, TextArea, TextInput, Timeline, Tooltip, Tree, User, View, VirtualList, WebView, addBackInterceptor, addRoutes, applyThemeClasses, canGoBack, closeSheet, createStore, currentModalRoute, currentRoute, defineRoutes, deriveRouteManifest, getColorScheme, getStack, getThemePreference, getThemeScheme, hrefFor, isNative, layoutsFor as layoutsForRoute, mergeRouteManifests, onThemeSchemeChange, openSheet, openWindow, popRoute, pushDeepLink, pushRoute, redirect, registerIcon, registerIcons, registerRoutes, registerScreens, registerStack, routeFor, routeStacks, screenFor, setThemePreference, setTranslate, showToast, stackEntries, styled, themeSchemeClasses, useAnimation, useBackInterceptor, useCanGoBack, useColorScheme, useMeasure, useModalRoute, useRoute, useSafeAreaInsets, useStore, useThemeScheme };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octane-xplat/ui",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Cross-platform UI for Octane xplat — shared primitives, stacks, routes and theming across web (DOM), iOS and Android (NativeScript)",
5
5
  "keywords": [
6
6
  "octane",
@@ -1,5 +1,6 @@
1
1
  /** @jsxImportSource @nativescript-community/octane */
2
2
  import { useState } from 'octane';
3
+ import { Screen } from '@nativescript/core';
3
4
  import type { CommandPaletteProps, MenuItem } from "./props";
4
5
  import { cx } from "./cx";
5
6
  import { Overlay } from './Overlay.tsrx';
@@ -19,6 +20,10 @@ export function CommandPalette(props: CommandPaletteProps) @{
19
20
  const shown = query
20
21
  ? props.items.filter((i) => (i.label ?? i.key).toLowerCase().includes(query.toLowerCase()))
21
22
  : props.items;
23
+ const panelStyle = {
24
+ ...props.style,
25
+ maxWidth: props.style?.maxWidth ?? Screen.mainScreen.widthDIPs * 0.9,
26
+ };
22
27
 
23
28
  const select = (item: MenuItem) => {
24
29
  if (item.disabled) return;
@@ -29,8 +34,9 @@ export function CommandPalette(props: CommandPaletteProps) @{
29
34
  <Overlay open={props.open} shadeCover={true} onDismiss={() => setOpen(false)} className="vx-cmdk-layer">
30
35
  <flexboxlayout
31
36
  className={cx('vx-commandpalette', props.className)}
32
- style={props.style}
37
+ style={panelStyle}
33
38
  id={props.id}
39
+ flexDirection="column"
34
40
  horizontalAlignment="center"
35
41
  verticalAlignment="top"
36
42
  marginTop={80}
package/src/Grid.tsrx CHANGED
@@ -1,20 +1,49 @@
1
1
  /** @jsxImportSource @nativescript-community/octane */
2
+ import { useEffect, useRef } from 'octane';
2
3
  import type { GridProps } from './props';
3
4
  import { cx } from './cx';
4
5
  import { applyEscapeProps, nativeAccessibilityRole, nativeAccessibilityState } from './escape-props';
6
+ import { autoPlaceGridChildren, ensureGridTracks } from './grid-auto-placement';
7
+ import { gridChildPlacement } from './grid-placement';
5
8
 
6
9
  let warnedAboutGap = false;
7
10
 
8
11
  export function Grid(props: GridProps) @{
12
+ const grid = useRef<any>(null);
9
13
  const deprecatedGap = (props as GridProps & { gap?: number | string }).gap;
10
14
  if (deprecatedGap != null && !warnedAboutGap) {
11
15
  warnedAboutGap = true;
12
16
  console.warn('[Grid] gap was removed; use child margins');
13
17
  }
14
18
 
19
+ useEffect(() => {
20
+ const host = grid.current;
21
+ if (!host) return;
22
+
23
+ const views: any[] = [];
24
+ host.eachChildView((child: any) => {
25
+ views.push(child);
26
+ return true;
27
+ });
28
+ const placement = autoPlaceGridChildren(
29
+ views.map(gridChildPlacement),
30
+ props.rows,
31
+ props.columns,
32
+ );
33
+ host.rows = ensureGridTracks(props.rows, placement.rowCount);
34
+ host.columns = ensureGridTracks(props.columns, placement.columnCount);
35
+ for (let index = 0; index < views.length; index++) {
36
+ views[index].row = placement.cells[index].row;
37
+ views[index].col = placement.cells[index].col;
38
+ }
39
+ }, [props.children, props.rows, props.columns]);
40
+
15
41
  <gridlayout
16
42
  id={props.id}
17
- ref={(view: any) => applyEscapeProps(view, props)}
43
+ ref={(view: any) => {
44
+ grid.current = view;
45
+ applyEscapeProps(view, props);
46
+ }}
18
47
  rows={props.rows}
19
48
  columns={props.columns}
20
49
  className={cx('vx-grid', props.className)}
@@ -36,11 +36,13 @@ export function Pagination(props: PaginationProps) @{
36
36
  >
37
37
  <flexboxlayout className="vx-page-btn" onTap={() => go(current - 1)}><label text="‹" /></flexboxlayout>
38
38
  @for (const e of pages; key e.key) {
39
- {e.page == null
40
- ? <label className="vx-page-ellipsis" text="…" />
41
- : <flexboxlayout className={cx('vx-page-btn', e.page === current && 'vx-page-btn--on')} onTap={() => go(e.page!)}>
39
+ @if (e.page == null) {
40
+ <label className="vx-page-ellipsis" text="…" />
41
+ } @else {
42
+ <flexboxlayout className={cx('vx-page-btn', e.page === current && 'vx-page-btn--on')} onTap={() => go(e.page!)}>
42
43
  <label text={String(e.page)} />
43
- </flexboxlayout>}
44
+ </flexboxlayout>
45
+ }
44
46
  }
45
47
  <flexboxlayout className="vx-page-btn" onTap={() => go(current + 1)}><label text="›" /></flexboxlayout>
46
48
  </flexboxlayout>
package/src/Popover.tsrx CHANGED
@@ -1,6 +1,6 @@
1
1
  /** @jsxImportSource @nativescript-community/octane */
2
2
  import { useEffect, useRef, useState } from 'octane';
3
- import { GridLayout } from '@nativescript/core';
3
+ import { GridLayout, isIOS, Screen } from '@nativescript/core';
4
4
  import { createNativeScriptRoot } from '@nativescript-community/octane';
5
5
  import type { UniversalComponent } from 'octane/universal';
6
6
  import type { PopoverProps } from './props';
@@ -38,13 +38,19 @@ function PopoverLayer(props: PopoverProps & { anchorView: any }) @{
38
38
  },
39
39
  props.placement ?? 'bottom',
40
40
  );
41
-
41
+ const insets = isIOS ? panelRef.current?.parent?.getSafeAreaInsets?.() : undefined;
42
+ const scale = isIOS ? Number(Screen.mainScreen.scale) || 1 : 1;
43
+ const panelPosition = {
44
+ ...next,
45
+ left: next.left - Number(insets?.left ?? 0) / scale,
46
+ top: next.top - Number(insets?.top ?? 0) / scale,
47
+ };
42
48
  setPosition((current) =>
43
- current?.left === next.left &&
44
- current?.top === next.top &&
45
- current?.placement === next.placement
49
+ current?.left === panelPosition.left &&
50
+ current?.top === panelPosition.top &&
51
+ current?.placement === panelPosition.placement
46
52
  ? current
47
- : next,
53
+ : panelPosition,
48
54
  );
49
55
 
50
56
  if (panelSize?.width > 0 && panelSize?.height > 0) setSettled(true);
@@ -73,6 +79,14 @@ function PopoverLayer(props: PopoverProps & { anchorView: any }) @{
73
79
  };
74
80
  }, [props.open, props.anchorView, props.placement, props.children, props.style]);
75
81
 
82
+ useEffect(() => {
83
+ if (position) {
84
+ const panel = panelRef.current;
85
+ const parent = panel?.parent;
86
+ parent?.requestLayout?.();
87
+ }
88
+ }, [position]);
89
+
76
90
  useEffect(() => {
77
91
  applyNativeProps(panelRef.current, props);
78
92
  }, [props.ios, props.android, settled]);
package/src/Screen.tsrx CHANGED
@@ -3,7 +3,7 @@ import type { ScreenProps } from "./props";
3
3
  import { cx } from "./cx";
4
4
  import { applyEscapeProps } from "./escape-props";
5
5
  import { registerRootLayout, unregisterRootLayout } from "./root-layout.mobile";
6
- import { attachTapToBlur } from "./tap-to-blur.mobile";
6
+ import { attachTapToBlur } from "./tap-to-blur";
7
7
  // The screen shell is a RootLayout so RootLayout.open() can layer overlays
8
8
  // over it (decision #22). Self-registers on loaded/unloaded — overlay and
9
9
  // sheet services mount on the most recently mounted shell (topRootLayout).
package/src/Sheet.tsrx CHANGED
@@ -9,7 +9,7 @@ import { applyThemeClasses } from './theme/theme-scheme';
9
9
  import { rootLayoutFor } from './root-layout.mobile';
10
10
  import { attachSheetDetents } from './sheet-detents';
11
11
  import { bindBottomInsetToKeyboard } from './keyboard-inset.mobile';
12
- import { attachTapToBlur } from './tap-to-blur.mobile';
12
+ import { attachTapToBlur } from './tap-to-blur';
13
13
 
14
14
  function SheetBody(props: { children?: any }) @{
15
15
  <>{props.children}</>
package/src/Slider.tsrx CHANGED
@@ -22,14 +22,20 @@ export function Slider(props: SliderProps) @{
22
22
  const ratio = Math.min(1, Math.max(0, (value - min) / (max - min)));
23
23
  const width = measure.bounds?.width ?? 0;
24
24
 
25
- // Stable ref identity — an inline ref closure re-fires ref(null)/ref(el)
26
- // per render, which bounces useMeasure's setView and loops forever.
25
+ // Keep one ref identity so native measurement listeners stay attached to
26
+ // the same host across rerenders.
27
27
  const host = useRef<any>(null);
28
28
  const bindHost = useCallback((el: any) => {
29
29
  host.current = el;
30
30
  measure.bind(el);
31
31
  }, []);
32
32
 
33
+ const trackHeight = 4;
34
+ const thumbSize = 20;
35
+ const trackTop = (28 - trackHeight) / 2;
36
+ const thumbTop = (28 - thumbSize) / 2;
37
+ const thumbLeft = Math.min(Math.max(ratio * width - thumbSize / 2, 0), Math.max(width - thumbSize, 0));
38
+
33
39
  useEffect(() => {
34
40
  applyNativeProps(host.current, props);
35
41
  });
@@ -52,7 +58,7 @@ export function Slider(props: SliderProps) @{
52
58
  }
53
59
  });
54
60
 
55
- <gridlayout
61
+ <absolutelayout
56
62
  id={props.id}
57
63
  className={cx('vx-slider', props.className)}
58
64
  style={props.style}
@@ -60,12 +66,8 @@ export function Slider(props: SliderProps) @{
60
66
  onPan={onPan}
61
67
  ref={bindHost}
62
68
  >
63
- <flexboxlayout className="vx-slider-track" />
64
- <flexboxlayout className="vx-slider-fill" horizontalAlignment="left" style={{ width: ratio * width }} />
65
- <flexboxlayout
66
- className="vx-slider-thumb"
67
- horizontalAlignment="left"
68
- style={{ marginLeft: Math.min(Math.max(ratio * width - 10, 0), Math.max(width - 20, 0)) }}
69
- />
70
- </gridlayout>
69
+ <flexboxlayout className="vx-slider-track" style={{ width, translateY: trackTop }} />
70
+ <flexboxlayout className="vx-slider-fill" style={{ width: ratio * width, translateY: trackTop }} />
71
+ <flexboxlayout className="vx-slider-thumb" style={{ translateX: thumbLeft, translateY: thumbTop }} />
72
+ </absolutelayout>
71
73
  }
package/src/Text.tsrx CHANGED
@@ -1,5 +1,6 @@
1
1
  /** @jsxImportSource @nativescript-community/octane */
2
2
  import { createContext, useContext } from 'octane';
3
+ import { Application } from '@nativescript/core';
3
4
  import type { TextProps } from "./props";
4
5
  import { cx } from "./cx";
5
6
  import { applyEscapeProps, nativeAccessibilityRole, nativeAccessibilityState } from "./escape-props";
@@ -24,8 +25,41 @@ function TextScope(props: { children?: any }) {
24
25
 
25
26
  export function Text(props: TextProps) @{
26
27
  const inText = useContext(InText);
28
+ const fontWeightValue = String(props.style?.fontWeight ?? '').toLowerCase();
29
+ const namedWeights: Record<string, number> = {
30
+ normal: 400,
31
+ bold: 700,
32
+ thin: 100,
33
+ 'extra-light': 200,
34
+ light: 300,
35
+ medium: 500,
36
+ 'semi-bold': 600,
37
+ 'extra-bold': 800,
38
+ black: 900,
39
+ };
40
+ const fontWeight = /^\d+$/.test(fontWeightValue)
41
+ ? Number(fontWeightValue)
42
+ : namedWeights[fontWeightValue];
43
+ const lineHeight = props.style?.lineHeight;
44
+ // UIFontDescriptor resolves variable fonts to the regular axis default on
45
+ // iOS. Set wght explicitly so CSS fontWeight selects the matching instance.
27
46
  const textStyle = {
28
47
  ...props.style,
48
+ ...((Application.ios || Application.android) &&
49
+ typeof lineHeight === 'number' &&
50
+ Number.isFinite(lineHeight) &&
51
+ lineHeight > 0 &&
52
+ props.style?.height == null &&
53
+ props.style?.minHeight == null
54
+ ? { minHeight: lineHeight }
55
+ : {}),
56
+ ...(Application.ios &&
57
+ props.style?.fontVariationSettings == null &&
58
+ fontWeight !== undefined &&
59
+ fontWeight >= 100 &&
60
+ fontWeight <= 900
61
+ ? { fontVariationSettings: `'wght' ${fontWeight}` }
62
+ : {}),
29
63
  // Clamped text must be allowed to shrink — the shared shrink:0 default
30
64
  // would let the label overflow its row instead of truncating.
31
65
  ...(props.ellipsize || props.numberOfLines ? { flexShrink: 1 } : {}),
package/src/WebView.tsrx CHANGED
@@ -24,6 +24,22 @@ function reportContentLayout(view: any, props: WebViewProps) {
24
24
  }
25
25
  }
26
26
 
27
+ function applyNativeScrollEnabled(view: any, enabled: boolean | undefined) {
28
+ if (isIOS) {
29
+ const scrollView = view?.ios?.scrollView
30
+ if (scrollView) scrollView.scrollEnabled = enabled !== false
31
+ } else if (isAndroid) {
32
+ const nativeView = view?.android
33
+ if (!nativeView?.setOnTouchListener) return
34
+ nativeView.setOnTouchListener(enabled === false
35
+ ? new android.view.View.OnTouchListener({
36
+ onTouch: (_view: any, event: any) =>
37
+ event.getActionMasked() === android.view.MotionEvent.ACTION_MOVE,
38
+ })
39
+ : null)
40
+ }
41
+ }
42
+
27
43
  /** Embedded web document — NS <webview> (WKWebView / android.webkit.WebView).
28
44
  * `html` flows through the `src` property: values that aren't http/file
29
45
  * URLs take its `_loadData` path. NS reports failures as `loadFinished`
@@ -39,16 +55,7 @@ export function WebView(props: WebViewProps) @{
39
55
  useEffect(() => {
40
56
  const v = el.current;
41
57
  if (!v) return;
42
- if (isIOS) {
43
- v.ios.scrollView.scrollEnabled = props.scrollEnabled !== false;
44
- } else if (isAndroid) {
45
- v.android.setOnTouchListener(props.scrollEnabled === false
46
- ? new android.view.View.OnTouchListener({
47
- onTouch: (_view: any, event: any) =>
48
- event.getActionMasked() === android.view.MotionEvent.ACTION_MOVE,
49
- })
50
- : null);
51
- }
58
+ applyNativeScrollEnabled(v, props.scrollEnabled)
52
59
  if (props.matchContents && loaded.current) reportContentLayout(v, props);
53
60
  }, [props.scrollEnabled, props.matchContents]);
54
61
 
@@ -70,7 +77,9 @@ export function WebView(props: WebViewProps) @{
70
77
  className={cx('vx-webview', props.className)}
71
78
  style={props.style}
72
79
  src={props.html !== undefined ? props.html : props.src}
80
+ onLoaded={() => applyNativeScrollEnabled(el.current, props.scrollEnabled)}
73
81
  onLoadFinished={(e: any) => {
82
+ applyNativeScrollEnabled(e.object ?? el.current, props.scrollEnabled)
74
83
  if (e.error) props.onError?.({ url: e.url, error: e.error });
75
84
  else {
76
85
  loaded.current = true;
@@ -1,15 +1,25 @@
1
1
  import { isAndroid, isIOS } from '@nativescript/core'
2
2
  import type { NativeModifier, Role } from './props'
3
3
  import { applyNativeModifiers } from './apply-native-modifiers.native'
4
+ import { rememberGridChildPlacement } from './grid-placement'
4
5
 
5
6
  /** Apply only the active device's escape bag after the primitive's own props. */
6
7
  export function applyEscapeProps(
7
8
  view: any,
8
- props: { ios?: any; android?: any; modifiers?: readonly NativeModifier[] },
9
+ props: {
10
+ ios?: any
11
+ android?: any
12
+ modifiers?: readonly NativeModifier[]
13
+ row?: number
14
+ col?: number
15
+ rowSpan?: number
16
+ colSpan?: number
17
+ },
9
18
  ): void {
10
19
  if (!view) {
11
20
  return
12
21
  }
22
+ rememberGridChildPlacement(view, props)
13
23
 
14
24
  const bag = isIOS ? props.ios : isAndroid ? props.android : undefined
15
25
  if (bag) {