@octane-xplat/ui 0.7.2 → 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.
- package/dist/native/CommandPalette.tsrx.js +7 -1
- package/dist/native/Grid.tsrx.js +31 -2
- package/dist/native/Pagination.tsrx.js +15 -12
- package/dist/native/Popover.tsrx.js +18 -7
- package/dist/native/Screen.tsrx.js +1 -1
- package/dist/native/Sheet.tsrx.js +1 -1
- package/dist/native/Slider.tsrx.js +23 -14
- package/dist/native/Text.tsrx.js +16 -0
- 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/octane-client-build.json +1 -1
- package/dist/native/sheet-service.js +1 -1
- package/dist/native/{tap-to-blur.mobile.js → tap-to-blur.ios.js} +20 -32
- package/dist/native/useMeasure.tsrx.js +17 -18
- package/dist/web/octane-client-build.json +1 -1
- package/package.json +1 -1
- package/src/CommandPalette.tsrx +7 -1
- package/src/Grid.tsrx +30 -1
- package/src/Pagination.tsrx +6 -4
- package/src/Popover.tsrx +20 -6
- package/src/Screen.tsrx +1 -1
- package/src/Sheet.tsrx +1 -1
- package/src/Slider.tsrx +13 -11
- package/src/Text.tsrx +34 -0
- package/src/WebView.tsrx +19 -10
- package/src/escape-props.ts +11 -1
- package/src/grid-auto-placement.ts +121 -0
- package/src/grid-placement.ts +29 -0
- package/src/sheet-service.ts +1 -1
- package/src/tap-to-blur.ios.ts +90 -0
- package/src/tap-to-blur.mobile.ts +12 -94
- package/src/theme/tokens.css +24 -8
- package/src/useMeasure.tsrx +20 -21
- package/types/generated/.tsrx-typegen-manifest.json +3 -0
- package/types/generated/Grid.d.ts +0 -1
- 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/tap-to-blur.ios.d.ts +13 -0
- package/types/generated/tap-to-blur.mobile.d.ts +1 -15
|
@@ -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 [
|
|
21
|
-
const
|
|
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((
|
|
24
|
-
|
|
25
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
view.off?.("loaded",
|
|
48
|
+
detach.current = () => {
|
|
49
|
+
view.off?.("loaded", update);
|
|
48
50
|
if (observe) {
|
|
49
51
|
view.off?.("layoutChanged", update);
|
|
50
|
-
|
|
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
|
-
}, [
|
|
55
|
+
}, [observe], _h$2);
|
|
56
|
+
useEffect(() => () => detach.current?.(), [], _h$3);
|
|
58
57
|
return {
|
|
59
58
|
bind,
|
|
60
59
|
bounds
|
package/package.json
CHANGED
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';
|
|
@@ -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={
|
|
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) =>
|
|
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)}
|
package/src/Pagination.tsrx
CHANGED
|
@@ -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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 ===
|
|
44
|
-
current?.top ===
|
|
45
|
-
current?.placement ===
|
|
49
|
+
current?.left === panelPosition.left &&
|
|
50
|
+
current?.top === panelPosition.top &&
|
|
51
|
+
current?.placement === panelPosition.placement
|
|
46
52
|
? current
|
|
47
|
-
:
|
|
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
|
|
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
|
|
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
|
-
//
|
|
26
|
-
//
|
|
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
|
-
<
|
|
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"
|
|
65
|
-
<flexboxlayout
|
|
66
|
-
|
|
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
|
-
|
|
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;
|
package/src/escape-props.ts
CHANGED
|
@@ -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: {
|
|
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) {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { GridChildPlacement } from './grid-placement'
|
|
2
|
+
|
|
3
|
+
export interface GridCell {
|
|
4
|
+
row: number
|
|
5
|
+
col: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface GridAutoPlacement {
|
|
9
|
+
cells: GridCell[]
|
|
10
|
+
rowCount: number
|
|
11
|
+
columnCount: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function gridIndex(value: unknown): number | null {
|
|
15
|
+
if (value == null || value === '') return null
|
|
16
|
+
const index = Number(value)
|
|
17
|
+
return Number.isInteger(index) && index >= 0 ? index : null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function gridSpan(value: unknown): number {
|
|
21
|
+
const span = Number(value)
|
|
22
|
+
return Number.isInteger(span) && span > 0 ? span : 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function trackCount(spec?: string): number {
|
|
26
|
+
return spec?.split(',').filter((track) => track.trim().length > 0).length ?? 0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Match CSS Grid's row-flow placement for NativeScript GridLayout children. */
|
|
30
|
+
export function autoPlaceGridChildren(
|
|
31
|
+
items: readonly GridChildPlacement[],
|
|
32
|
+
rowTracks?: string,
|
|
33
|
+
columnTracks?: string,
|
|
34
|
+
): GridAutoPlacement {
|
|
35
|
+
const placements = items.map((item) => ({
|
|
36
|
+
row: gridIndex(item.row),
|
|
37
|
+
col: gridIndex(item.col),
|
|
38
|
+
rowSpan: gridSpan(item.rowSpan),
|
|
39
|
+
colSpan: gridSpan(item.colSpan),
|
|
40
|
+
}))
|
|
41
|
+
const columnCount = Math.max(
|
|
42
|
+
1,
|
|
43
|
+
trackCount(columnTracks),
|
|
44
|
+
...placements
|
|
45
|
+
.filter((placement) => placement.col != null)
|
|
46
|
+
.map((placement) => placement.col! + placement.colSpan),
|
|
47
|
+
...placements.map((placement) => placement.colSpan),
|
|
48
|
+
)
|
|
49
|
+
const occupied = new Set<string>()
|
|
50
|
+
const isFree = (row: number, col: number, rowSpan: number, colSpan: number) => {
|
|
51
|
+
for (let y = row; y < row + rowSpan; y++) {
|
|
52
|
+
for (let x = col; x < col + colSpan; x++) {
|
|
53
|
+
if (occupied.has(`${y},${x}`)) return false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
const occupy = (row: number, col: number, rowSpan: number, colSpan: number) => {
|
|
59
|
+
for (let y = row; y < row + rowSpan; y++) {
|
|
60
|
+
for (let x = col; x < col + colSpan; x++) occupied.add(`${y},${x}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Definite placements reserve their cells before automatic items flow.
|
|
65
|
+
for (const placement of placements) {
|
|
66
|
+
if (placement.row != null && placement.col != null) {
|
|
67
|
+
occupy(placement.row, placement.col, placement.rowSpan, placement.colSpan)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const placement of placements) {
|
|
72
|
+
if (placement.row == null || placement.col != null) continue
|
|
73
|
+
let col = 0
|
|
74
|
+
while (!isFree(placement.row, col, placement.rowSpan, placement.colSpan)) col++
|
|
75
|
+
placement.col = col
|
|
76
|
+
occupy(placement.row, col, placement.rowSpan, placement.colSpan)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const placement of placements) {
|
|
80
|
+
if (placement.col == null || placement.row != null) continue
|
|
81
|
+
let row = 0
|
|
82
|
+
while (!isFree(row, placement.col, placement.rowSpan, placement.colSpan)) row++
|
|
83
|
+
placement.row = row
|
|
84
|
+
occupy(row, placement.col, placement.rowSpan, placement.colSpan)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let cursorRow = 0
|
|
88
|
+
let cursorCol = 0
|
|
89
|
+
for (const placement of placements) {
|
|
90
|
+
if (placement.row != null && placement.col != null) continue
|
|
91
|
+
while (true) {
|
|
92
|
+
if (cursorCol + placement.colSpan > columnCount) {
|
|
93
|
+
cursorRow++
|
|
94
|
+
cursorCol = 0
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
if (isFree(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)) break
|
|
98
|
+
cursorCol++
|
|
99
|
+
}
|
|
100
|
+
placement.row = cursorRow
|
|
101
|
+
placement.col = cursorCol
|
|
102
|
+
occupy(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)
|
|
103
|
+
cursorCol += placement.colSpan
|
|
104
|
+
if (cursorCol >= columnCount) {
|
|
105
|
+
cursorRow++
|
|
106
|
+
cursorCol = 0
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
cells: placements.map(({ row, col }) => ({ row: row!, col: col! })),
|
|
112
|
+
rowCount: Math.max(trackCount(rowTracks), ...placements.map((item) => item.row! + item.rowSpan)),
|
|
113
|
+
columnCount: Math.max(columnCount, ...placements.map((item) => item.col! + item.colSpan)),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function ensureGridTracks(spec: string | undefined, requiredCount: number): string {
|
|
118
|
+
const tracks = spec?.split(',').map((track) => track.trim()).filter(Boolean) ?? []
|
|
119
|
+
while (tracks.length < requiredCount) tracks.push('auto')
|
|
120
|
+
return tracks.join(',')
|
|
121
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { LayoutChildProps } from './props'
|
|
2
|
+
|
|
3
|
+
export type GridChildPlacement = Pick<LayoutChildProps, 'row' | 'col' | 'rowSpan' | 'colSpan'>
|
|
4
|
+
|
|
5
|
+
const placements = new WeakMap<object, GridChildPlacement>()
|
|
6
|
+
|
|
7
|
+
/** Keep the authored Grid placement next to its mounted NativeScript view. */
|
|
8
|
+
export function rememberGridChildPlacement(view: unknown, props: GridChildPlacement): void {
|
|
9
|
+
if ((typeof view !== 'object' || view === null) && typeof view !== 'function') {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const placement: GridChildPlacement = {}
|
|
14
|
+
for (const key of ['row', 'col', 'rowSpan', 'colSpan'] as const) {
|
|
15
|
+
if (props[key] !== undefined) {
|
|
16
|
+
placement[key] = props[key]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Object.keys(placement).length) {
|
|
21
|
+
placements.set(view as object, placement)
|
|
22
|
+
} else {
|
|
23
|
+
placements.delete(view as object)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function gridChildPlacement(view: unknown): GridChildPlacement {
|
|
28
|
+
return typeof view === 'object' && view !== null ? placements.get(view) ?? {} : {}
|
|
29
|
+
}
|
package/src/sheet-service.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { topRootLayout } from './root-layout.mobile'
|
|
|
6
6
|
import { applyThemeClasses } from './theme/theme-scheme'
|
|
7
7
|
import { attachSheetDetents } from './sheet-detents'
|
|
8
8
|
import { bindBottomInsetToKeyboard } from './keyboard-inset.mobile'
|
|
9
|
-
import { attachTapToBlur } from './tap-to-blur
|
|
9
|
+
import { attachTapToBlur } from './tap-to-blur'
|
|
10
10
|
|
|
11
11
|
interface ActiveSheet {
|
|
12
12
|
host: GridLayout
|