@octane-xplat/ui 0.7.2 → 0.9.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.
- package/README.md +4 -3
- package/dist/native/Button.tsrx.js +1 -1
- package/dist/native/CommandPalette.tsrx.js +7 -1
- package/dist/native/Grid.tsrx.js +31 -2
- package/dist/native/KeyboardAvoiding.mobile.tsrx.js +2 -2
- package/dist/native/Markdown.tsrx.js +233 -0
- package/dist/native/Overlay.tsrx.js +15 -26
- package/dist/native/Pagination.tsrx.js +15 -12
- package/dist/native/PinInput.tsrx.js +25 -7
- package/dist/native/Popover.tsrx.js +33 -23
- package/dist/native/Pressable.tsrx.js +4 -1
- package/dist/native/Screen.tsrx.js +1 -1
- package/dist/native/SearchInput.tsrx.js +6 -3
- package/dist/native/Sheet.tsrx.js +23 -38
- package/dist/native/Slider.tsrx.js +23 -14
- package/dist/native/Text.tsrx.js +16 -0
- package/dist/native/TextArea.tsrx.js +6 -3
- package/dist/native/TextInput.tsrx.js +6 -3
- package/dist/native/VirtualList.tsrx.js +305 -121
- package/dist/native/WebView.tsrx.js +17 -2
- package/dist/native/_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js +9 -0
- package/dist/native/escape-props.js +2 -0
- package/dist/native/grid-auto-placement.js +83 -0
- package/dist/native/grid-placement.js +20 -0
- package/dist/native/index.shared.js +7 -5
- package/dist/native/keyboard-inset.mobile.js +5 -5
- package/dist/native/navigation-request.js +26 -0
- package/dist/native/octane-client-build.json +1 -1
- package/dist/native/overlay-lifetime.mobile.js +65 -0
- package/dist/native/pan.tsrx.js +16 -2
- package/dist/native/route-table.js +90 -10
- package/dist/native/route.js +26 -16
- package/dist/native/shared.js +7 -5
- package/dist/native/sheet-service.js +2 -2
- package/dist/native/svg.mobile.js +1 -1
- package/dist/native/{tap-to-blur.mobile.js → tap-to-blur.ios.js} +20 -32
- package/dist/native/text-focus.js +10 -0
- package/dist/native/text-write.js +18 -11
- package/dist/native/toast-anchor.tsrx.js +1 -1
- package/dist/native/ui.js +7 -6
- package/dist/native/useMeasure.tsrx.js +17 -18
- package/dist/native/vendor/ui-svg/index.android.js +2 -2
- package/dist/native/vendor/ui-svg/index.ios.js +2 -2
- package/dist/native/vendor/ui-svg/{index.common.js → src/index.common.js} +1 -1
- package/dist/native/virtual-list-cell-pool.js +63 -0
- package/dist/web/Button.web.tsrx.js +1 -1
- package/dist/web/KeyboardAvoiding.web.tsrx.js +78 -0
- package/dist/web/Markdown.tsrx.js +98 -0
- package/dist/web/Overlay.web.tsrx.js +81 -32
- package/dist/web/PinInput.web.tsrx.js +24 -10
- package/dist/web/Pressable.web.tsrx.js +169 -53
- package/dist/web/SearchInput.web.tsrx.js +2 -2
- package/dist/web/Sheet.web.tsrx.js +20 -7
- package/dist/web/TextArea.web.tsrx.js +22 -17
- package/dist/web/TextInput.web.tsrx.js +2 -2
- package/dist/web/VirtualList.web.tsrx.js +220 -109
- package/dist/web/WebView.web.tsrx.js +3 -3
- package/dist/web/{index.shared.js → index.shared.web.js} +7 -5
- package/dist/web/modal-focus.web.js +95 -0
- package/dist/web/navigation-request.js +26 -0
- package/dist/web/octane-client-build.json +1 -1
- package/dist/web/pan.web.tsrx.js +27 -10
- package/dist/web/route-table.js +90 -10
- package/dist/web/route.web.js +88 -22
- package/dist/web/sheet-service.web.js +8 -0
- package/dist/web/theme/colorScheme.web.js +31 -4
- package/dist/web/ui.js +8 -6
- package/dist/web/virtual-list-cell-pool.js +63 -0
- package/package.json +21 -19
- package/platforms/android/native-api-usage.json +7 -7
- package/src/CommandPalette.tsrx +8 -1
- package/src/Grid.tsrx +32 -1
- package/src/Icon.macos.tsrx +1 -0
- package/src/KeyboardAvoiding.macos.tsrx +7 -1
- package/src/KeyboardAvoiding.mobile.tsrx +2 -2
- package/src/KeyboardAvoiding.tsrx +15 -0
- package/src/KeyboardAvoiding.web.tsrx +15 -0
- package/src/Markdown.tsrx +121 -0
- package/src/Meter.tsrx +1 -0
- package/src/Overlay.tsrx +15 -30
- package/src/Overlay.web.tsrx +12 -3
- package/src/Pagination.tsrx +6 -4
- package/src/PinInput.macos.tsrx +20 -6
- package/src/PinInput.mobile.test.ts +107 -0
- package/src/PinInput.tsrx +20 -6
- package/src/PinInput.web.test.tsrx +42 -0
- package/src/PinInput.web.tsrx +20 -6
- package/src/Popover.tsrx +36 -22
- package/src/Pressable.tsrx +1 -1
- package/src/Pressable.web.test.tsrx +36 -0
- package/src/Pressable.web.tsrx +37 -6
- package/src/RouteHost.macos.tsrx +1 -0
- package/src/Screen.tsrx +1 -1
- package/src/SearchInput.tsrx +4 -3
- package/src/SearchInput.web.tsrx +1 -1
- package/src/Sheet.tsrx +21 -42
- package/src/Sheet.web.tsrx +9 -1
- package/src/Slider.tsrx +13 -11
- package/src/Text.tsrx +36 -0
- package/src/TextArea.tsrx +6 -5
- package/src/TextArea.web.test.tsrx +39 -0
- package/src/TextArea.web.tsrx +7 -5
- package/src/TextInput.tsrx +6 -6
- package/src/TextInput.web.tsrx +1 -1
- package/src/VirtualList.macos.tsrx +93 -6
- package/src/VirtualList.mobile.test.tsrx +232 -0
- package/src/VirtualList.tsrx +285 -81
- package/src/VirtualList.web.tsrx +131 -25
- package/src/WebView.tsrx +20 -10
- package/src/WebView.web.tsrx +3 -0
- package/src/android/BottomNavigationView.android.tsrx +1 -0
- package/src/android/MaterialDialog.android.tsrx +1 -0
- package/src/anim.macos.tsrx +1 -0
- package/src/apply-native-modifiers.native.ts +4 -1
- package/src/child-array.ts +12 -7
- package/src/colorScheme.macos.ts +1 -0
- package/src/controls.macos.tsrx +4 -1
- package/src/data-driver.mobile.test.ts +120 -0
- package/src/data-lifecycle.mobile.test.ts +251 -0
- package/src/escape-props.ts +13 -1
- package/src/grid-auto-placement.ts +163 -0
- package/src/grid-placement.ts +29 -0
- package/src/hosted-unsupported.macos.tsrx +1 -0
- package/src/index.macos.ts +23 -3
- package/src/index.mobile.ts +0 -1
- package/src/index.shared.ts +22 -1
- package/src/index.shared.web.ts +291 -0
- package/src/index.web.ts +1 -1
- package/src/input-readiness.web.test.tsrx +82 -0
- package/src/ios/UITabBar.ios.tsrx +1 -0
- package/src/keyboard-inset.mobile.test.ts +62 -0
- package/src/keyboard-inset.mobile.ts +63 -43
- package/src/layout-shells.macos.tsrx +1 -5
- package/src/macos-extras.macos.tsrx +2 -0
- package/src/modal-focus.web.test.tsrx +63 -0
- package/src/modal-focus.web.ts +162 -0
- package/src/navigation-request.ts +37 -0
- package/src/overlay-lifecycle.mobile.test.ts +316 -0
- package/src/overlay-lifetime.mobile.ts +107 -0
- package/src/pan.mobile.test.tsrx +116 -0
- package/src/pan.tsrx +20 -2
- package/src/pan.web.tsrx +35 -7
- package/src/platformProps.ts +1 -0
- package/src/popover.mobile.test.ts +9 -1
- package/src/pressable-accessibility.mobile.test.ts +30 -0
- package/src/props.ts +163 -3
- package/src/refresh.ts +6 -1
- package/src/refresh.web.test.ts +77 -77
- package/src/route-android.test.ts +162 -0
- package/src/route-history.web.test.ts +80 -0
- package/src/route-navigation.web.test.ts +173 -0
- package/src/route-table.test.ts +188 -9
- package/src/route-table.ts +138 -10
- package/src/route.macos.ts +56 -13
- package/src/route.ts +54 -24
- package/src/route.web.ts +141 -24
- package/src/sheet-detents.ts +5 -3
- package/src/sheet-service.macos.ts +5 -1
- package/src/sheet-service.ts +1 -1
- package/src/sheet-service.web.ts +8 -0
- package/src/sheet-snap.test.ts +23 -23
- package/src/sheet-snap.ts +6 -1
- package/src/svg.mobile.ts +9 -3
- package/src/tap-to-blur.ios.ts +106 -0
- package/src/tap-to-blur.mobile.ts +21 -95
- package/src/text-focus.ts +12 -0
- package/src/text-input.mobile.test.ts +89 -0
- package/src/text-write.ts +21 -11
- package/src/theme/colorScheme.linux.ts +2 -4
- package/src/theme/colorScheme.web.ts +51 -4
- package/src/theme/theme-scheme.macos.ts +8 -2
- package/src/theme/tokens.css +1740 -1718
- package/src/translate.macos.ts +4 -1
- package/src/useMeasure.tsrx +20 -21
- package/src/virtual-list-cell-pool.test.ts +53 -0
- package/src/virtual-list-cell-pool.ts +81 -0
- package/src/virtual-list-layout.test.ts +11 -8
- package/src/virtual-list-layout.ts +4 -8
- package/src/windows.linux.ts +7 -2
- package/src/windows.macos.ts +1 -0
- package/types/generated/.tsrx-typegen-manifest.json +16 -3
- package/types/generated/Grid.d.ts +0 -1
- package/types/generated/KeyboardAvoiding.d.ts +3 -0
- package/types/generated/KeyboardAvoiding.web.d.ts +2 -0
- package/types/generated/Markdown.d.ts +59 -0
- package/types/generated/TextArea.d.ts +2 -2
- package/types/generated/TextArea.web.d.ts +1 -1
- package/types/generated/TextInput.d.ts +2 -3
- package/types/generated/VirtualList.d.ts +3 -3
- package/types/generated/escape-props.d.ts +4 -0
- package/types/generated/grid-auto-placement.d.ts +13 -0
- package/types/generated/grid-placement.d.ts +5 -0
- package/types/generated/index.mobile.d.ts +0 -1
- package/types/generated/index.shared.d.ts +6 -3
- package/types/generated/index.shared.web.d.ts +114 -0
- package/types/generated/index.web.d.ts +1 -1
- package/types/generated/modal-focus.web.d.ts +3 -0
- package/types/generated/navigation-request.d.ts +10 -0
- package/types/generated/overlay-lifetime.mobile.d.ts +10 -0
- package/types/generated/props.d.ts +111 -3
- package/types/generated/route-table.d.ts +14 -3
- package/types/generated/svg.mobile.d.ts +2 -2
- package/types/generated/tap-to-blur.ios.d.ts +13 -0
- package/types/generated/tap-to-blur.mobile.d.ts +1 -15
- package/types/generated/text-focus.d.ts +3 -0
- package/types/generated/text-write.d.ts +3 -1
- package/types/generated/theme/colorScheme.web.d.ts +5 -0
- package/types/generated/virtual-list-cell-pool.d.ts +18 -0
- package/types/index.macos.d.ts +15 -3
- /package/src/vendor/ui-svg/{index.android.ts → src/index.android.ts} +0 -0
- /package/src/vendor/ui-svg/{index.common.ts → src/index.common.ts} +0 -0
- /package/src/vendor/ui-svg/{index.ios.ts → src/index.ios.ts} +0 -0
- /package/src/vendor/ui-svg/{index.ts → src/index.ts} +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/SVGKit.d.ts +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/android.d.ts +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/ios.d.ts +0 -0
- /package/types/generated/vendor/ui-svg/{index.common.d.ts → src/index.common.d.ts} +0 -0
- /package/types/generated/vendor/ui-svg/{index.d.ts → src/index.d.ts} +0 -0
- /package/types/generated/vendor/ui-svg/{index.ios.d.ts → src/index.ios.d.ts} +0 -0
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { hookSlots, useSyncExternalStore } from "octane";
|
|
2
2
|
//#region src/theme/colorScheme.web.ts
|
|
3
|
-
|
|
3
|
+
var hostTransport = () => typeof window === "undefined" ? null : window.__xplatHostTransport ?? null;
|
|
4
|
+
var hostedScheme = typeof window === "undefined" ? "light" : window.__xplatHostSnapshot?.colorScheme ?? window.__xplatColorScheme ?? "light";
|
|
5
|
+
function getSystemScheme() {
|
|
4
6
|
return typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
5
7
|
}
|
|
8
|
+
function getColorScheme() {
|
|
9
|
+
return hostTransport() ? hostedScheme : getSystemScheme();
|
|
10
|
+
}
|
|
6
11
|
function subscribeSystemScheme(cb) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
const transport = hostTransport();
|
|
13
|
+
if (!transport) {
|
|
14
|
+
const mq = matchMedia("(prefers-color-scheme: dark)");
|
|
15
|
+
mq.addEventListener("change", cb);
|
|
16
|
+
return () => mq.removeEventListener("change", cb);
|
|
17
|
+
}
|
|
18
|
+
const onTransportMessage = (message) => {
|
|
19
|
+
try {
|
|
20
|
+
const packet = JSON.parse(message);
|
|
21
|
+
if (packet.type === "event" && packet.name === "appearance.change") {
|
|
22
|
+
hostedScheme = packet.payload === "dark" ? "dark" : "light";
|
|
23
|
+
cb();
|
|
24
|
+
}
|
|
25
|
+
} catch {}
|
|
26
|
+
};
|
|
27
|
+
const onCompatEvent = (event) => {
|
|
28
|
+
hostedScheme = event.detail === "dark" ? "dark" : "light";
|
|
29
|
+
cb();
|
|
30
|
+
};
|
|
31
|
+
const offTransport = transport.listen(onTransportMessage);
|
|
32
|
+
window.addEventListener("xplat:appearance.change", onCompatEvent);
|
|
33
|
+
return () => {
|
|
34
|
+
offTransport();
|
|
35
|
+
window.removeEventListener("xplat:appearance.change", onCompatEvent);
|
|
36
|
+
};
|
|
10
37
|
}
|
|
11
38
|
function useColorScheme() {
|
|
12
39
|
return useSyncExternalStore(subscribeSystemScheme, getColorScheme, _h$0);
|
package/dist/web/ui.js
CHANGED
|
@@ -6,9 +6,12 @@ import { Absolute } from "./Absolute.web.tsrx.js";
|
|
|
6
6
|
import { setTranslate } from "./translate.web.js";
|
|
7
7
|
import { Spacer } from "./Spacer.web.tsrx.js";
|
|
8
8
|
import { Text } from "./Text.web.tsrx.js";
|
|
9
|
+
import { Screen } from "./Screen.web.tsrx.js";
|
|
10
|
+
import { ActivityIndicator } from "./ActivityIndicator.web.tsrx.js";
|
|
11
|
+
import { ScrollView } from "./ScrollView.web.tsrx.js";
|
|
12
|
+
import { Markdown, MarkdownScreen } from "./Markdown.tsrx.js";
|
|
9
13
|
import { RichText, RichTextSpan } from "./RichText.web.tsrx.js";
|
|
10
14
|
import { Pressable } from "./Pressable.web.tsrx.js";
|
|
11
|
-
import { ActivityIndicator } from "./ActivityIndicator.web.tsrx.js";
|
|
12
15
|
import { Button } from "./Button.web.tsrx.js";
|
|
13
16
|
import { Collapsible } from "./Collapsible.web.tsrx.js";
|
|
14
17
|
import { Accordion } from "./Accordion.web.tsrx.js";
|
|
@@ -56,13 +59,12 @@ import { User } from "./User.web.tsrx.js";
|
|
|
56
59
|
import { Meter } from "./Meter.web.tsrx.js";
|
|
57
60
|
import { ProgressGroup } from "./ProgressGroup.web.tsrx.js";
|
|
58
61
|
import { Link } from "./Link.web.tsrx.js";
|
|
59
|
-
import { defineRoutes, deriveRouteManifest, mergeRouteManifests } from "./route-table.js";
|
|
62
|
+
import { defineRoutes, deriveRouteManifest, manifestToJson, mergeRouteManifests } from "./route-table.js";
|
|
60
63
|
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
64
|
import { NavLink } from "./NavLink.web.tsrx.js";
|
|
62
65
|
import { TextArea } from "./TextArea.web.tsrx.js";
|
|
63
66
|
import { SearchInput } from "./SearchInput.web.tsrx.js";
|
|
64
67
|
import { SegmentedControl } from "./SegmentedControl.web.tsrx.js";
|
|
65
|
-
import { ScrollView } from "./ScrollView.web.tsrx.js";
|
|
66
68
|
import { ScrollBox } from "./ScrollBox.web.tsrx.js";
|
|
67
69
|
import { VirtualList } from "./VirtualList.web.tsrx.js";
|
|
68
70
|
import { Image } from "./Image.web.tsrx.js";
|
|
@@ -73,11 +75,11 @@ import { showToast } from "./toast-anchor.web.tsrx.js";
|
|
|
73
75
|
import { useAnimation } from "./anim.web.tsrx.js";
|
|
74
76
|
import { styled } from "./styled.web.tsrx.js";
|
|
75
77
|
import { openWindow } from "./windows.web.js";
|
|
76
|
-
import { Screen } from "./Screen.web.tsrx.js";
|
|
77
78
|
import { Tabs } from "./Tabs.web.tsrx.js";
|
|
78
79
|
import { Switch } from "./Switch.web.tsrx.js";
|
|
79
80
|
import { useSafeAreaInsets } from "./safeAreaInsets.web.tsrx.js";
|
|
80
81
|
import { SafeArea } from "./SafeArea.web.tsrx.js";
|
|
82
|
+
import { KeyboardAvoiding } from "./KeyboardAvoiding.web.tsrx.js";
|
|
81
83
|
import { Drawer } from "./Drawer.web.tsrx.js";
|
|
82
84
|
import { useMeasure } from "./useMeasure.web.tsrx.js";
|
|
83
85
|
import { Slider } from "./Slider.web.tsrx.js";
|
|
@@ -89,5 +91,5 @@ import { createStore } from "./store.js";
|
|
|
89
91
|
import { useStore } from "./use-store.web.tsrx.js";
|
|
90
92
|
import { Sheet } from "./Sheet.web.tsrx.js";
|
|
91
93
|
import { closeSheet, openSheet } from "./sheet-service.web.js";
|
|
92
|
-
import "./index.shared.js";
|
|
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 };
|
|
94
|
+
import "./index.shared.web.js";
|
|
95
|
+
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, KeyboardAvoiding, Link, ListItem, Markdown, MarkdownScreen, 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, manifestToJson, 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 };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/virtual-list-cell-pool.ts
|
|
2
|
+
/** Reuse physical hosts without retaining an off-window logical row subtree. */
|
|
3
|
+
var VirtualListCellPool = class {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.cells = [];
|
|
6
|
+
this.byId = /* @__PURE__ */ new Map();
|
|
7
|
+
this.nextId = 0;
|
|
8
|
+
}
|
|
9
|
+
update(entries) {
|
|
10
|
+
if (entries.length === 0) {
|
|
11
|
+
this.clear();
|
|
12
|
+
return this.cells;
|
|
13
|
+
}
|
|
14
|
+
const wanted = new Set(entries.map((entry) => entry.rowKey));
|
|
15
|
+
let cells = this.cells.map((cell) => cell.entry && !wanted.has(cell.entry.rowKey) ? {
|
|
16
|
+
...cell,
|
|
17
|
+
entry: null,
|
|
18
|
+
rows: [],
|
|
19
|
+
generation: cell.generation + 1
|
|
20
|
+
} : cell);
|
|
21
|
+
const retained = new Map(cells.filter((cell) => cell.entry).map((cell) => [cell.entry.rowKey, cell.id]));
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const retainedId = retained.get(entry.rowKey);
|
|
24
|
+
let index = retainedId === void 0 ? cells.findIndex((cell) => !cell.entry && cell.typeKey === entry.typeKey) : cells.findIndex((cell) => cell.id === retainedId);
|
|
25
|
+
if (index === -1) {
|
|
26
|
+
index = cells.length;
|
|
27
|
+
cells.push({
|
|
28
|
+
id: this.nextId++,
|
|
29
|
+
typeKey: entry.typeKey,
|
|
30
|
+
generation: 0,
|
|
31
|
+
entry: null,
|
|
32
|
+
rows: []
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const cell = cells[index];
|
|
36
|
+
if (cell.entry !== entry) cells[index] = {
|
|
37
|
+
...cell,
|
|
38
|
+
entry,
|
|
39
|
+
rows: [entry],
|
|
40
|
+
generation: cell.generation + 1
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
let spare = 0;
|
|
44
|
+
cells = cells.filter((cell) => cell.entry || spare++ < 8);
|
|
45
|
+
cells.sort((a, b) => (a.entry?.index ?? Infinity) - (b.entry?.index ?? Infinity) || a.id - b.id);
|
|
46
|
+
this.cells = cells;
|
|
47
|
+
this.byId = new Map(cells.map((cell) => [cell.id, cell]));
|
|
48
|
+
return cells;
|
|
49
|
+
}
|
|
50
|
+
get(id) {
|
|
51
|
+
return this.byId.get(id);
|
|
52
|
+
}
|
|
53
|
+
isCurrent(id, rowKey, generation) {
|
|
54
|
+
const cell = this.byId.get(id);
|
|
55
|
+
return cell?.entry?.rowKey === rowKey && cell.generation === generation;
|
|
56
|
+
}
|
|
57
|
+
clear() {
|
|
58
|
+
this.cells = [];
|
|
59
|
+
this.byId.clear();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { VirtualListCellPool };
|
package/package.json
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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
|
-
"
|
|
7
|
-
"xplat",
|
|
6
|
+
"android",
|
|
8
7
|
"cross-platform",
|
|
9
|
-
"ui",
|
|
10
|
-
"ui-components",
|
|
11
|
-
"primitives",
|
|
12
8
|
"design-system",
|
|
13
|
-
"nativescript",
|
|
14
9
|
"ios",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
10
|
+
"nativescript",
|
|
11
|
+
"octane",
|
|
12
|
+
"primitives",
|
|
13
|
+
"ui",
|
|
14
|
+
"ui-components",
|
|
15
|
+
"web",
|
|
16
|
+
"xplat"
|
|
17
17
|
],
|
|
18
|
-
"license": "MIT",
|
|
19
18
|
"homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/ui",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/octane-xplat/octane-xplat",
|
|
23
|
+
"directory": "packages/ui"
|
|
24
|
+
},
|
|
20
25
|
"files": [
|
|
21
26
|
"dist",
|
|
22
27
|
"types",
|
|
@@ -60,7 +65,7 @@
|
|
|
60
65
|
"./theme/tokens.css": "./src/theme/tokens.css"
|
|
61
66
|
},
|
|
62
67
|
"devDependencies": {
|
|
63
|
-
"@nativescript-community/octane": "0.2.
|
|
68
|
+
"@nativescript-community/octane": "0.2.4",
|
|
64
69
|
"@nativescript-community/ui-drawer": "0.1.32",
|
|
65
70
|
"@nativescript/core": "9.1.2",
|
|
66
71
|
"@nativescript/types": "9.0.0",
|
|
@@ -68,7 +73,7 @@
|
|
|
68
73
|
"@tsrx/typescript-plugin": "0.4.11",
|
|
69
74
|
"jsdom": "30.1.1",
|
|
70
75
|
"octane": "0.6.3",
|
|
71
|
-
"tsrx-typegen": "0.1
|
|
76
|
+
"tsrx-typegen": "0.2.1",
|
|
72
77
|
"typescript": "5.9.3",
|
|
73
78
|
"vite": "8.3.0",
|
|
74
79
|
"vitest": "5.0.1"
|
|
@@ -100,14 +105,11 @@
|
|
|
100
105
|
"android": "9.1.0"
|
|
101
106
|
}
|
|
102
107
|
},
|
|
103
|
-
"repository": {
|
|
104
|
-
"type": "git",
|
|
105
|
-
"url": "git+https://github.com/octane-xplat/octane-xplat.git",
|
|
106
|
-
"directory": "packages/ui"
|
|
107
|
-
},
|
|
108
108
|
"scripts": {
|
|
109
|
-
"build": "vite build && vite build --mode native &&
|
|
109
|
+
"build": "node ../../scripts/sync-ui-barrels.mjs --check && vite build && vite build --mode native && pnpm typegen && node ../../scripts/check-native-dist.mjs",
|
|
110
110
|
"test:packed": "node tests/packed-consumer.mjs",
|
|
111
|
-
"test:native": "vitest run --config vitest.native.config.mts"
|
|
111
|
+
"test:native": "vitest run --config vitest.native.config.mts",
|
|
112
|
+
"typegen": "tsrx-typegen --target ui",
|
|
113
|
+
"pack:check": "tsrx-typegen --pack-check"
|
|
112
114
|
}
|
|
113
115
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
2
|
+
"uses": [
|
|
3
|
+
"com.caverock.androidsvg:SVG",
|
|
4
|
+
"com.caverock.androidsvg:SVGExternalFileResolver",
|
|
5
|
+
"com.caverock.androidsvg:PreserveAspectRatio",
|
|
6
|
+
"com.caverock.androidsvg:RenderOptions"
|
|
7
|
+
]
|
|
8
|
+
}
|
package/src/CommandPalette.tsrx
CHANGED
|
@@ -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';
|
|
@@ -20,6 +21,11 @@ export function CommandPalette(props: CommandPaletteProps) @{
|
|
|
20
21
|
? props.items.filter((i) => (i.label ?? i.key).toLowerCase().includes(query.toLowerCase()))
|
|
21
22
|
: props.items;
|
|
22
23
|
|
|
24
|
+
const panelStyle = {
|
|
25
|
+
...props.style,
|
|
26
|
+
maxWidth: props.style?.maxWidth ?? Screen.mainScreen.widthDIPs * 0.9,
|
|
27
|
+
};
|
|
28
|
+
|
|
23
29
|
const select = (item: MenuItem) => {
|
|
24
30
|
if (item.disabled) return;
|
|
25
31
|
setOpen(false);
|
|
@@ -29,8 +35,9 @@ export function CommandPalette(props: CommandPaletteProps) @{
|
|
|
29
35
|
<Overlay open={props.open} shadeCover={true} onDismiss={() => setOpen(false)} className="vx-cmdk-layer">
|
|
30
36
|
<flexboxlayout
|
|
31
37
|
className={cx('vx-commandpalette', props.className)}
|
|
32
|
-
style={
|
|
38
|
+
style={panelStyle}
|
|
33
39
|
id={props.id}
|
|
40
|
+
flexDirection="column"
|
|
34
41
|
horizontalAlignment="center"
|
|
35
42
|
verticalAlignment="top"
|
|
36
43
|
marginTop={80}
|
package/src/Grid.tsrx
CHANGED
|
@@ -1,20 +1,51 @@
|
|
|
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
|
+
|
|
29
|
+
const placement = autoPlaceGridChildren(
|
|
30
|
+
views.map(gridChildPlacement),
|
|
31
|
+
props.rows,
|
|
32
|
+
props.columns,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
host.rows = ensureGridTracks(props.rows, placement.rowCount);
|
|
36
|
+
host.columns = ensureGridTracks(props.columns, placement.columnCount);
|
|
37
|
+
for (let index = 0; index < views.length; index++) {
|
|
38
|
+
views[index].row = placement.cells[index].row;
|
|
39
|
+
views[index].col = placement.cells[index].col;
|
|
40
|
+
}
|
|
41
|
+
}, [props.children, props.rows, props.columns]);
|
|
42
|
+
|
|
15
43
|
<gridlayout
|
|
16
44
|
id={props.id}
|
|
17
|
-
ref={(view: any) =>
|
|
45
|
+
ref={(view: any) => {
|
|
46
|
+
grid.current = view;
|
|
47
|
+
applyEscapeProps(view, props);
|
|
48
|
+
}}
|
|
18
49
|
rows={props.rows}
|
|
19
50
|
columns={props.columns}
|
|
20
51
|
className={cx('vx-grid', props.className)}
|
package/src/Icon.macos.tsrx
CHANGED
|
@@ -8,6 +8,7 @@ import { getIcon } from './icons'
|
|
|
8
8
|
export function Icon(props: IconProps) @{
|
|
9
9
|
const size = props.size ?? 24
|
|
10
10
|
const glyph = getIcon(props.name)
|
|
11
|
+
|
|
11
12
|
<label
|
|
12
13
|
className={props.className}
|
|
13
14
|
style={{ width: size, height: size, fontSize: size * 0.75, ...props.style }}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/** @jsxImportSource @xplat/macos/renderer */
|
|
2
2
|
import type { KeyboardAvoidingProps } from './props'
|
|
3
|
+
import { cx } from './cx'
|
|
3
4
|
|
|
4
5
|
export function KeyboardAvoiding(props: KeyboardAvoidingProps) @{
|
|
5
|
-
<flexboxlayout
|
|
6
|
+
<flexboxlayout
|
|
7
|
+
id={props.id}
|
|
8
|
+
flexDirection="column"
|
|
9
|
+
className={cx('vx-keyboard-avoiding', props.className)}
|
|
10
|
+
style={props.style}
|
|
11
|
+
>
|
|
6
12
|
{props.children}
|
|
7
13
|
</flexboxlayout>
|
|
8
14
|
}
|
|
@@ -32,10 +32,10 @@ function useKeyboardInset() {
|
|
|
32
32
|
|
|
33
33
|
function useAndroidResize() {
|
|
34
34
|
useEffect(() => {
|
|
35
|
-
const nativeWindow = Application.android?.foregroundActivity?.
|
|
35
|
+
const nativeWindow = Application.android?.foregroundActivity?.getWindow();
|
|
36
36
|
if (!nativeWindow) return;
|
|
37
37
|
const params = android.view.WindowManager.LayoutParams;
|
|
38
|
-
const previous = nativeWindow.
|
|
38
|
+
const previous = nativeWindow.getAttributes().softInputMode;
|
|
39
39
|
const adjusted = (previous & ~params.SOFT_INPUT_MASK_ADJUST) | params.SOFT_INPUT_ADJUST_RESIZE;
|
|
40
40
|
nativeWindow.setSoftInputMode(adjusted);
|
|
41
41
|
return () => nativeWindow.setSoftInputMode(previous);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import type { KeyboardAvoidingProps } from './props'
|
|
3
|
+
import { cx } from './cx'
|
|
4
|
+
|
|
5
|
+
// Targets without mobile keyboard insets retain the shared column layout.
|
|
6
|
+
export function KeyboardAvoiding(props: KeyboardAvoidingProps) @{
|
|
7
|
+
<flexboxlayout
|
|
8
|
+
id={props.id}
|
|
9
|
+
flexDirection="column"
|
|
10
|
+
className={cx('vx-keyboard-avoiding', props.className)}
|
|
11
|
+
style={props.style}
|
|
12
|
+
>
|
|
13
|
+
{props.children}
|
|
14
|
+
</flexboxlayout>
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @jsxImportSource octane */
|
|
2
|
+
import type { KeyboardAvoidingProps } from './props'
|
|
3
|
+
import { cx } from './cx'
|
|
4
|
+
|
|
5
|
+
// Web keeps the wrapper's layout and styling, without claiming keyboard avoidance.
|
|
6
|
+
export function KeyboardAvoiding(props: KeyboardAvoidingProps) @{
|
|
7
|
+
<div
|
|
8
|
+
id={props.id}
|
|
9
|
+
className={cx('vx-keyboard-avoiding', props.className)}
|
|
10
|
+
style={props.style}
|
|
11
|
+
{...props.web}
|
|
12
|
+
>
|
|
13
|
+
{props.children}
|
|
14
|
+
</div>
|
|
15
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { Screen } from './Screen';
|
|
3
|
+
import { ScrollView } from './ScrollView';
|
|
4
|
+
import { Text } from './Text';
|
|
5
|
+
import { View } from './View';
|
|
6
|
+
|
|
7
|
+
/** Markdown AST — the serialized shape `xplat routes` bakes for `.md`
|
|
8
|
+
* routes (parse happens at codegen, not runtime). `MdNode` covers block
|
|
9
|
+
* level; `MdInline` covers text runs inside paragraphs/lists/quotes. */
|
|
10
|
+
export type MdInline =
|
|
11
|
+
| { t: 'text'; text: string }
|
|
12
|
+
| { t: 'code'; text: string }
|
|
13
|
+
| { t: 'bold'; children: MdInline[] }
|
|
14
|
+
| { t: 'em'; children: MdInline[] }
|
|
15
|
+
| { t: 'link'; text: string; href: string };
|
|
16
|
+
|
|
17
|
+
export type MdNode =
|
|
18
|
+
| { t: 'h'; depth: 1 | 2 | 3 | 4 | 5 | 6; children: MdInline[] }
|
|
19
|
+
| { t: 'p'; children: MdInline[] }
|
|
20
|
+
| { t: 'code'; lang?: string; text: string }
|
|
21
|
+
| { t: 'list'; ordered: boolean; items: MdInline[][] }
|
|
22
|
+
| { t: 'quote'; children: MdInline[] }
|
|
23
|
+
| { t: 'hr' };
|
|
24
|
+
|
|
25
|
+
export interface MdDoc {
|
|
26
|
+
kind: 'octane-xplat/md';
|
|
27
|
+
v: 1;
|
|
28
|
+
children: MdNode[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const headingClass = ['text-2xl font-bold', 'text-xl font-semibold', 'text-lg font-semibold'];
|
|
32
|
+
|
|
33
|
+
function Inlines(props: { nodes: MdInline[] }) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const n of props.nodes ?? []) {
|
|
36
|
+
switch (n.t) {
|
|
37
|
+
case 'code':
|
|
38
|
+
out.push(
|
|
39
|
+
<Text style={{ fontFamily: 'ui-monospace' }}>{n.text}</Text>,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
break;
|
|
43
|
+
case 'bold':
|
|
44
|
+
out.push(<Text className="font-bold"><Inlines nodes={n.children} /></Text>);
|
|
45
|
+
|
|
46
|
+
break;
|
|
47
|
+
case 'em':
|
|
48
|
+
out.push(<Text style={{ fontStyle: 'italic' }}><Inlines nodes={n.children} /></Text>);
|
|
49
|
+
|
|
50
|
+
break;
|
|
51
|
+
case 'link':
|
|
52
|
+
out.push(<Text style={{ textDecorationLine: 'underline' }}>{n.text}</Text>);
|
|
53
|
+
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
out.push(n.text);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Renders a baked markdown doc with the shared vocabulary — Text
|
|
64
|
+
* blocks on every platform, no parser at runtime. `data` is the AST the
|
|
65
|
+
* codegen baked into routes.gen.data (or a doc built programmatically). */
|
|
66
|
+
export function Markdown(props: { data?: MdDoc }) @{
|
|
67
|
+
<View className="gap-3">
|
|
68
|
+
{(props.data?.children ?? []).map((node) => {
|
|
69
|
+
switch (node.t) {
|
|
70
|
+
case 'h':
|
|
71
|
+
return (
|
|
72
|
+
<Text className={headingClass[node.depth - 1] ?? 'font-semibold'}>
|
|
73
|
+
<Inlines nodes={node.children} />
|
|
74
|
+
</Text>
|
|
75
|
+
);
|
|
76
|
+
case 'p':
|
|
77
|
+
return (
|
|
78
|
+
<Text>
|
|
79
|
+
<Inlines nodes={node.children} />
|
|
80
|
+
</Text>
|
|
81
|
+
);
|
|
82
|
+
case 'code':
|
|
83
|
+
return (
|
|
84
|
+
<Text className="text-sm" style={{ fontFamily: 'ui-monospace' }}>
|
|
85
|
+
{node.text}
|
|
86
|
+
</Text>
|
|
87
|
+
);
|
|
88
|
+
case 'list':
|
|
89
|
+
return node.items.map((item, i) => (
|
|
90
|
+
<Text>
|
|
91
|
+
{(node.ordered ? `${i + 1}. ` : '• ') + ''}
|
|
92
|
+
<Inlines nodes={item} />
|
|
93
|
+
</Text>
|
|
94
|
+
));
|
|
95
|
+
case 'quote':
|
|
96
|
+
return (
|
|
97
|
+
<Text className="text-muted" style={{ fontStyle: 'italic' }}>
|
|
98
|
+
<Inlines nodes={node.children} />
|
|
99
|
+
</Text>
|
|
100
|
+
);
|
|
101
|
+
case 'hr':
|
|
102
|
+
return (
|
|
103
|
+
<View style={{ height: 1, backgroundColor: 'var(--color-border)' }} />
|
|
104
|
+
);
|
|
105
|
+
default:
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
})}
|
|
109
|
+
</View>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Route screen for `.md` files — the codegen emits specs with this as
|
|
113
|
+
* `screen`, so a markdown file in the route dir is a route end-to-end.
|
|
114
|
+
* Receives baked data like any route (`props.data`). */
|
|
115
|
+
export function MarkdownScreen(props: { data?: MdDoc; error?: unknown }) @{
|
|
116
|
+
<Screen className="vx-app">
|
|
117
|
+
<ScrollView style={{ padding: 16 }}>
|
|
118
|
+
<Markdown data={props.data} />
|
|
119
|
+
</ScrollView>
|
|
120
|
+
</Screen>
|
|
121
|
+
}
|
package/src/Meter.tsrx
CHANGED
|
@@ -17,6 +17,7 @@ function ringSvg(size: number, strokeWidth: number, ratio: number, color: string
|
|
|
17
17
|
const arc = ratio > 0 && r > 0
|
|
18
18
|
? `<circle cx="${c}" cy="${c}" r="${r}" fill="none" stroke="${color}" stroke-width="${strokeWidth}" stroke-linecap="round" stroke-dasharray="${ratio * circumference} ${circumference}" transform="rotate(-90 ${c} ${c})"/>`
|
|
19
19
|
: '';
|
|
20
|
+
|
|
20
21
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}">${track}${arc}</svg>`;
|
|
21
22
|
}
|
|
22
23
|
|
package/src/Overlay.tsrx
CHANGED
|
@@ -7,12 +7,13 @@ import type { OverlayProps } from './props';
|
|
|
7
7
|
import { cx } from './cx';
|
|
8
8
|
import { applyThemeClasses } from './theme/theme-scheme';
|
|
9
9
|
import { rootLayoutFor } from './root-layout.mobile';
|
|
10
|
+
import { createOverlayLifetime } from './overlay-lifetime.mobile';
|
|
10
11
|
|
|
11
12
|
function OverlayBody(props: { children?: any }) @{
|
|
12
13
|
<>{props.children}</>
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
type OpenOverlay = { host: GridLayout; root: any;
|
|
16
|
+
type OpenOverlay = { host: GridLayout; root: any; lifetime: ReturnType<typeof createOverlayLifetime>; unbindTheme?: () => void };
|
|
16
17
|
|
|
17
18
|
/** Renders children in a separate RootLayout root above the current screen. */
|
|
18
19
|
export function Overlay(props: OverlayProps) @{
|
|
@@ -26,22 +27,13 @@ export function Overlay(props: OverlayProps) @{
|
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
const current = shown.current;
|
|
28
29
|
if (!props.open) {
|
|
29
|
-
|
|
30
|
-
current.dismissOnClose = false;
|
|
31
|
-
shown.current = null;
|
|
32
|
-
const rl = rootLayout();
|
|
33
|
-
if (rl && (rl as any)?.hasChild?.(current.host)) {
|
|
34
|
-
rl.close(current.host).then(
|
|
35
|
-
() => current.root.unmount?.(),
|
|
36
|
-
(error) => { console.error('[Overlay] close failed', error); current.root.unmount?.(); },
|
|
37
|
-
);
|
|
38
|
-
} else current.root.unmount?.();
|
|
39
|
-
|
|
30
|
+
current?.lifetime.close();
|
|
40
31
|
return;
|
|
41
32
|
}
|
|
42
33
|
|
|
43
34
|
if (current) {
|
|
44
35
|
current.root.render(OverlayBody as unknown as UniversalComponent, { children: props.children });
|
|
36
|
+
current.unbindTheme?.();
|
|
45
37
|
current.unbindTheme = applyThemeClasses(current.host, cx('vx-overlay-host', props.className));
|
|
46
38
|
if (props.style) Object.assign(current.host.style, props.style);
|
|
47
39
|
Object.assign(current.host, isIOS ? props.ios : isAndroid ? props.android : undefined);
|
|
@@ -57,28 +49,21 @@ export function Overlay(props: OverlayProps) @{
|
|
|
57
49
|
if (props.style) Object.assign(host.style, props.style);
|
|
58
50
|
Object.assign(host, isIOS ? props.ios : isAndroid ? props.android : undefined);
|
|
59
51
|
const root = createNativeScriptRoot(host);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
host.on('closed', () => {
|
|
64
|
-
if (shown.current === opened) shown.current = null;
|
|
65
|
-
opened.unbindTheme?.();
|
|
66
|
-
root.unmount?.();
|
|
67
|
-
if (opened.dismissOnClose) dismiss.current?.();
|
|
52
|
+
const lifetime = createOverlayLifetime(rl, host, root, 'Overlay', (dismissed) => {
|
|
53
|
+
if (shown.current?.host === host) shown.current = null;
|
|
54
|
+
if (dismissed) dismiss.current?.();
|
|
68
55
|
});
|
|
69
56
|
|
|
70
|
-
const
|
|
57
|
+
const opened = { host, root, lifetime, unbindTheme };
|
|
58
|
+
lifetime.own(() => opened.unbindTheme?.());
|
|
59
|
+
shown.current = opened;
|
|
60
|
+
root.render(OverlayBody as unknown as UniversalComponent, { children: props.children });
|
|
61
|
+
lifetime.open(props.shadeCover ? {
|
|
71
62
|
shadeCover: { opacity: 0.45, color: '#000000', tapToClose: true },
|
|
72
|
-
} : {})
|
|
73
|
-
if (shown.current === opened) shown.current = null;
|
|
74
|
-
console.error('[Overlay] open failed', error);
|
|
75
|
-
root.unmount?.();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if ((rl as any).hasChild?.(host)) {
|
|
79
|
-
rl.close(host).then(open, (error) => console.error('[Overlay] stale host close failed', error));
|
|
80
|
-
} else open();
|
|
63
|
+
} : {});
|
|
81
64
|
}, [props.open, props.children, props.shadeCover, props.className, props.style, props.ios, props.android]);
|
|
82
65
|
|
|
66
|
+
useEffect(() => () => shown.current?.lifetime.close(), []);
|
|
67
|
+
|
|
83
68
|
<stacklayout ref={(v: any) => { hostView.current = v; }} style={{ width: 0, height: 0 }} />
|
|
84
69
|
}
|
package/src/Overlay.web.tsrx
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import { createPortal } from 'octane';
|
|
1
|
+
import { createPortal, useEffect, useRef } from 'octane';
|
|
2
2
|
import type { OverlayProps } from './props';
|
|
3
3
|
import { useThemeScheme } from './theme/theme-scheme';
|
|
4
4
|
import { cx } from './cx';
|
|
5
|
+
import { isolateModalFocus } from './modal-focus.web';
|
|
5
6
|
|
|
6
7
|
/** Renders an in-window overlay through the DOM renderer's body portal. */
|
|
7
8
|
export function Overlay(props: OverlayProps) @{
|
|
8
9
|
const dark = useThemeScheme() === 'dark';
|
|
10
|
+
const layer = useRef<HTMLDivElement | null>(null);
|
|
11
|
+
const panel = useRef<HTMLDivElement | null>(null);
|
|
12
|
+
const dismiss = useRef(props.onDismiss);
|
|
13
|
+
dismiss.current = props.onDismiss;
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!props.open || !props.shadeCover || !layer.current || !panel.current) return;
|
|
16
|
+
return isolateModalFocus(layer.current, panel.current, () => dismiss.current?.());
|
|
17
|
+
}, [props.open, props.shadeCover]);
|
|
9
18
|
|
|
10
19
|
@if (props.open) {
|
|
11
20
|
<>{createPortal(
|
|
12
|
-
<div className={cx('vx-overlay', dark && 'dark', props.className)} style={{ ...props.style, position: 'fixed', inset: 0 }} {...props.web}>
|
|
21
|
+
<div ref={layer} className={cx('vx-overlay', dark && 'dark', props.className)} style={{ ...props.style, position: 'fixed', inset: 0 }} {...props.web}>
|
|
13
22
|
@if (props.shadeCover) {
|
|
14
23
|
<div className="vx-overlay-shade" onClick={props.onDismiss} />
|
|
15
24
|
}
|
|
16
|
-
<div className="vx-overlay-content" onClick={(event: any) => event.stopPropagation()}>
|
|
25
|
+
<div ref={panel} role={props.shadeCover ? 'dialog' : undefined} aria-modal={props.shadeCover ? true : undefined} className="vx-overlay-content" onClick={(event: any) => event.stopPropagation()}>
|
|
17
26
|
{props.children}
|
|
18
27
|
</div>
|
|
19
28
|
</div>,
|