@octane-xplat/smooth-corners 0.10.0 → 0.11.1
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/octane-client-build.json +1 -1
- package/dist/native/packages/smooth-corners/src/SmoothCorners.tsrx.js +9 -4
- package/dist/native/packages/smooth-corners/src/layout-child.js +61 -0
- package/dist/web/octane-client-build.json +1 -1
- package/dist/web/packages/smooth-corners/src/SmoothCorners.web.tsrx.js +8 -2
- package/dist/web/packages/smooth-corners/src/layout-child.web.js +94 -0
- package/package.json +3 -3
- package/src/SmoothCorners.macos.tsrx +8 -3
- package/src/SmoothCorners.tsrx +7 -3
- package/src/SmoothCorners.web.tsrx +11 -3
- package/src/layout-child.ts +137 -0
- package/src/layout-child.web.ts +171 -0
- package/src/props.ts +42 -1
- package/types/props.d.ts +40 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { applySmoothClip, applySmoothShadow } from "./apply.ios.js";
|
|
2
2
|
import { applySmoothClip as applySmoothClip$1, applySmoothShadow as applySmoothShadow$1 } from "./apply.android.js";
|
|
3
|
-
import {
|
|
3
|
+
import { outerLayoutProps, paintedStyle } from "./layout-child.js";
|
|
4
|
+
import { defineUniversalComponent, hookSlots, universalPlan, universalProps, universalValue, useCallback, useEffect, useRef } from "@nativescript-community/octane";
|
|
4
5
|
import { isIOS } from "@nativescript/core";
|
|
5
6
|
//#region src/SmoothCorners.tsrx
|
|
6
7
|
var _hs$ = /* @__PURE__ */ hookSlots(6);
|
|
@@ -13,7 +14,7 @@ var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
|
|
|
13
14
|
var __octaneUniversalPlan0 = universalPlan("nativescript", {
|
|
14
15
|
"kind": "host",
|
|
15
16
|
"type": "gridlayout",
|
|
16
|
-
"
|
|
17
|
+
"propsSlot": 0,
|
|
17
18
|
"children": [{
|
|
18
19
|
"kind": "host",
|
|
19
20
|
"type": "gridlayout",
|
|
@@ -60,11 +61,15 @@ var SmoothCorners = defineUniversalComponent("nativescript", function SmoothCorn
|
|
|
60
61
|
const bindOuter = useRef(bind("outer"), _h$4);
|
|
61
62
|
useEffect(() => apply(), [apply], _h$5);
|
|
62
63
|
return universalValue(__octaneUniversalPlan0, [
|
|
63
|
-
|
|
64
|
+
universalProps([[
|
|
65
|
+
"set",
|
|
66
|
+
"ref",
|
|
67
|
+
bindOuter.current
|
|
68
|
+
], ["spread", outerLayoutProps(props)]]),
|
|
64
69
|
bindInner.current,
|
|
65
70
|
props.id,
|
|
66
71
|
props.className,
|
|
67
|
-
props.style,
|
|
72
|
+
paintedStyle(props.style),
|
|
68
73
|
props.children
|
|
69
74
|
]);
|
|
70
75
|
}, { "module": "@nativescript-community/octane" });
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/layout-child.ts
|
|
2
|
+
var OUTER_STYLE_KEYS = [
|
|
3
|
+
"width",
|
|
4
|
+
"height",
|
|
5
|
+
"margin",
|
|
6
|
+
"marginTop",
|
|
7
|
+
"marginRight",
|
|
8
|
+
"marginBottom",
|
|
9
|
+
"marginLeft"
|
|
10
|
+
];
|
|
11
|
+
var MARGIN_KEYS = [
|
|
12
|
+
"margin",
|
|
13
|
+
"marginTop",
|
|
14
|
+
"marginRight",
|
|
15
|
+
"marginBottom",
|
|
16
|
+
"marginLeft"
|
|
17
|
+
];
|
|
18
|
+
/** The inner box's `style` — everything except margins, which belong to the
|
|
19
|
+
* element's border box and move out to the wrapper. Width/height stay: the
|
|
20
|
+
* AppKit inner stack cannot stretch along its parent's main axis, so it
|
|
21
|
+
* must keep its own size. */
|
|
22
|
+
function paintedStyle(style) {
|
|
23
|
+
if (!style || typeof style !== "object") return style;
|
|
24
|
+
const next = { ...style };
|
|
25
|
+
for (const key of MARGIN_KEYS) delete next[key];
|
|
26
|
+
return next;
|
|
27
|
+
}
|
|
28
|
+
var CHILD_PROPS_ALWAYS = [
|
|
29
|
+
"row",
|
|
30
|
+
"col",
|
|
31
|
+
"rowSpan",
|
|
32
|
+
"colSpan",
|
|
33
|
+
"left",
|
|
34
|
+
"top",
|
|
35
|
+
"horizontalAlignment",
|
|
36
|
+
"verticalAlignment",
|
|
37
|
+
"flexGrow",
|
|
38
|
+
"flexShrink",
|
|
39
|
+
"alignSelf",
|
|
40
|
+
"order"
|
|
41
|
+
];
|
|
42
|
+
var CHILD_PROPS_IF_SET = [
|
|
43
|
+
"dock",
|
|
44
|
+
"right",
|
|
45
|
+
"bottom"
|
|
46
|
+
];
|
|
47
|
+
/** Parent-layout metadata — must land on the OUTER wrapper, the node the
|
|
48
|
+
* parent layout reads as its child. Values forward under the host's own
|
|
49
|
+
* attribute names. */
|
|
50
|
+
function outerLayoutProps(props) {
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key of CHILD_PROPS_ALWAYS) result[key] = props[key];
|
|
53
|
+
for (const key of CHILD_PROPS_IF_SET) if (props[key] !== void 0) result[key] = props[key];
|
|
54
|
+
const source = props.style && typeof props.style === "object" ? props.style : {};
|
|
55
|
+
const style = {};
|
|
56
|
+
for (const key of OUTER_STYLE_KEYS) if (source[key] !== void 0) style[key] = source[key];
|
|
57
|
+
result.style = Object.keys(style).length > 0 ? style : void 0;
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { outerLayoutProps, paintedStyle };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { innerLayoutProps, outerLayoutProps, paintedStyle } from "./layout-child.web.js";
|
|
1
2
|
import { smoothPathD } from "./path.js";
|
|
2
3
|
import { createElement, createScopedElement, createScopedValue, hookSlots, positionalChildren, useLayoutEffect, useRef, useState } from "octane";
|
|
3
4
|
//#region src/SmoothCorners.web.tsrx
|
|
@@ -24,13 +25,18 @@ var SmoothCorners = function SmoothCorners(props, __s, __extra) {
|
|
|
24
25
|
return () => ro.disconnect();
|
|
25
26
|
}, [], _hs$ + 2);
|
|
26
27
|
const d = smoothPathD(size.w, size.h, props.corners);
|
|
28
|
+
const shadow = props.shadow;
|
|
29
|
+
const inner = innerLayoutProps(props).style;
|
|
30
|
+
const outer = outerLayoutProps(props).style;
|
|
27
31
|
const box = createScopedValue(() => createScopedElement("div", {
|
|
28
32
|
ref,
|
|
29
33
|
id: props.id,
|
|
30
34
|
className: props.className,
|
|
31
35
|
style: {
|
|
32
36
|
position: "relative",
|
|
33
|
-
...props.style,
|
|
37
|
+
...paintedStyle(props.style),
|
|
38
|
+
...inner,
|
|
39
|
+
...shadow ? {} : outer,
|
|
34
40
|
clipPath: size.w > 0 ? `path("${d}")` : void 0
|
|
35
41
|
}
|
|
36
42
|
}, () => positionalChildren([props.children, props.border && size.w > 0 && createScopedValue(() => createScopedElement("svg", {
|
|
@@ -50,11 +56,11 @@ var SmoothCorners = function SmoothCorners(props, __s, __extra) {
|
|
|
50
56
|
stroke: props.border.color,
|
|
51
57
|
strokeWidth: props.border.width * 2
|
|
52
58
|
}))))])));
|
|
53
|
-
const shadow = props.shadow;
|
|
54
59
|
if (!shadow) return box ?? null;
|
|
55
60
|
const filter = `drop-shadow(${shadow.offsetX ?? 0}px ${shadow.offsetY ?? 0}px ${shadow.blur ?? 0}px ${shadow.color ?? "rgba(0,0,0,0.35)"})`;
|
|
56
61
|
return createScopedValue(() => createScopedElement("div", { style: {
|
|
57
62
|
display: "grid",
|
|
63
|
+
...outer,
|
|
58
64
|
filter
|
|
59
65
|
} }, () => box));
|
|
60
66
|
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region src/layout-child.web.ts
|
|
2
|
+
var FLEX_JUSTIFY = {
|
|
3
|
+
start: "flex-start",
|
|
4
|
+
end: "flex-end"
|
|
5
|
+
};
|
|
6
|
+
var FLEX_ALIGN = {
|
|
7
|
+
start: "flex-start",
|
|
8
|
+
end: "flex-end"
|
|
9
|
+
};
|
|
10
|
+
var JUSTIFY_SELF = {
|
|
11
|
+
left: "start",
|
|
12
|
+
center: "center",
|
|
13
|
+
middle: "center",
|
|
14
|
+
right: "end",
|
|
15
|
+
stretch: "stretch"
|
|
16
|
+
};
|
|
17
|
+
var ALIGN_SELF = {
|
|
18
|
+
top: "start",
|
|
19
|
+
center: "center",
|
|
20
|
+
middle: "center",
|
|
21
|
+
bottom: "end",
|
|
22
|
+
stretch: "stretch"
|
|
23
|
+
};
|
|
24
|
+
var OUTER_STYLE_KEYS = [
|
|
25
|
+
"width",
|
|
26
|
+
"height",
|
|
27
|
+
"margin",
|
|
28
|
+
"marginTop",
|
|
29
|
+
"marginRight",
|
|
30
|
+
"marginBottom",
|
|
31
|
+
"marginLeft"
|
|
32
|
+
];
|
|
33
|
+
var MARGIN_KEYS = [
|
|
34
|
+
"margin",
|
|
35
|
+
"marginTop",
|
|
36
|
+
"marginRight",
|
|
37
|
+
"marginBottom",
|
|
38
|
+
"marginLeft"
|
|
39
|
+
];
|
|
40
|
+
/** The inner box's `style` — everything except margins, which belong to the
|
|
41
|
+
* element's border box and move out to the wrapper. */
|
|
42
|
+
function paintedStyle(style) {
|
|
43
|
+
if (!style || typeof style !== "object") return style;
|
|
44
|
+
const next = { ...style };
|
|
45
|
+
for (const key of MARGIN_KEYS) delete next[key];
|
|
46
|
+
return next;
|
|
47
|
+
}
|
|
48
|
+
/** Parent-layout metadata folded into CSS on the outermost element — the
|
|
49
|
+
* shadow wrapper when present, the clipped box otherwise. Native's `dock`
|
|
50
|
+
* has no CSS equivalent and is ignored on web. Grid indices are zero-based
|
|
51
|
+
* in the shared API, matching NativeScript. */
|
|
52
|
+
function outerLayoutProps(props) {
|
|
53
|
+
const style = {};
|
|
54
|
+
if (props.row !== void 0) style.gridRow = `${props.row + 1} / span ${props.rowSpan ?? 1}`;
|
|
55
|
+
else if (props.rowSpan !== void 0) style.gridRow = `span ${props.rowSpan}`;
|
|
56
|
+
if (props.col !== void 0) style.gridColumn = `${props.col + 1} / span ${props.colSpan ?? 1}`;
|
|
57
|
+
else if (props.colSpan !== void 0) style.gridColumn = `span ${props.colSpan}`;
|
|
58
|
+
for (const key of [
|
|
59
|
+
"left",
|
|
60
|
+
"top",
|
|
61
|
+
"right",
|
|
62
|
+
"bottom"
|
|
63
|
+
]) if (props[key] !== void 0) style[key] = props[key];
|
|
64
|
+
if (props.flexGrow !== void 0) style.flexGrow = props.flexGrow;
|
|
65
|
+
if (props.flexShrink !== void 0) style.flexShrink = props.flexShrink;
|
|
66
|
+
if (props.alignSelf !== void 0) style.alignSelf = props.alignSelf;
|
|
67
|
+
if (props.order !== void 0) style.order = props.order;
|
|
68
|
+
if (props.horizontalAlignment !== void 0) style.justifySelf = JUSTIFY_SELF[props.horizontalAlignment] ?? props.horizontalAlignment;
|
|
69
|
+
if (props.verticalAlignment !== void 0) style.alignSelf = ALIGN_SELF[props.verticalAlignment] ?? props.verticalAlignment;
|
|
70
|
+
if (props.style && typeof props.style === "object") {
|
|
71
|
+
for (const key of OUTER_STYLE_KEYS) if (props.style[key] !== void 0) style[key] = props.style[key];
|
|
72
|
+
}
|
|
73
|
+
return { style };
|
|
74
|
+
}
|
|
75
|
+
/** Flex-container props folded into the inner box's style. Emits
|
|
76
|
+
* `display: flex` only when a flex prop is present so a bare SmoothCorners
|
|
77
|
+
* keeps normal block flow; the default direction is column to match the
|
|
78
|
+
* renderers' stack default. */
|
|
79
|
+
function innerLayoutProps(props) {
|
|
80
|
+
if (!(props.flexDirection !== void 0 || props.justifyContent !== void 0 || props.alignItems !== void 0 || props.flexWrap !== void 0 || props.gap !== void 0 || props.rowGap !== void 0 || props.columnGap !== void 0)) return { style: {} };
|
|
81
|
+
const style = {
|
|
82
|
+
display: "flex",
|
|
83
|
+
flexDirection: props.flexDirection ?? "column"
|
|
84
|
+
};
|
|
85
|
+
if (props.justifyContent !== void 0) style.justifyContent = FLEX_JUSTIFY[props.justifyContent] ?? props.justifyContent;
|
|
86
|
+
if (props.alignItems !== void 0) style.alignItems = FLEX_ALIGN[props.alignItems] ?? props.alignItems;
|
|
87
|
+
if (props.flexWrap !== void 0) style.flexWrap = props.flexWrap === true ? "wrap" : props.flexWrap === false ? "nowrap" : props.flexWrap;
|
|
88
|
+
if (props.gap !== void 0) style.gap = props.gap;
|
|
89
|
+
if (props.rowGap !== void 0) style.rowGap = props.rowGap;
|
|
90
|
+
if (props.columnGap !== void 0) style.columnGap = props.columnGap;
|
|
91
|
+
return { style };
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { innerLayoutProps, outerLayoutProps, paintedStyle };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/smooth-corners",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Exact-match smooth corners for Octane xplat — one generated curve (Rosenfeld continuous, Figma squircle, superellipse, clothoid) clipped natively on web, iOS, Android, and macOS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"clip-path",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@nativescript-community/octane": "0.2.4",
|
|
45
45
|
"@nativescript/core": "9.1.2",
|
|
46
|
-
"@octane-xplat/macos-renderer": "0.
|
|
46
|
+
"@octane-xplat/macos-renderer": "0.11.1",
|
|
47
47
|
"@octanejs/vite-plugin": "0.1.61",
|
|
48
48
|
"@tsrx/typescript-plugin": "0.4.11",
|
|
49
49
|
"octane": "0.6.3",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"build": "vite build && vite build --mode native && pnpm typegen && node ../../scripts/check-native-dist.mjs",
|
|
69
69
|
"typegen": "tsc -p tsconfig.types.json",
|
|
70
70
|
"pack:check": "tsrx-typegen --pack-check",
|
|
71
|
-
"test": "node --test tests
|
|
71
|
+
"test": "node --test tests/*.test.mjs",
|
|
72
72
|
"typecheck": "tsrx-tsc --noEmit -p tsconfig.check.json && tsrx-tsc --noEmit -p tsconfig.check.web.json && tsrx-tsc --noEmit -p tsconfig.check.macos.json"
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** @jsxImportSource @octane-xplat/macos-renderer */
|
|
2
2
|
import { useCallback, useRef } from 'octane';
|
|
3
|
+
import { innerLayoutProps, outerLayoutProps, paintedStyle } from './layout-child';
|
|
3
4
|
import { flipCommandsY, generateCommands } from './path';
|
|
4
5
|
import type { PathCommand } from './path';
|
|
5
6
|
import type { SmoothCornersProps } from './props';
|
|
@@ -145,7 +146,10 @@ function applyOuter(view: any, w: number, h: number, props: SmoothCornersProps):
|
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
/** SmoothCorners on the AppKit renderer — NSBezierPath → CAShapeLayer mask
|
|
148
|
-
* on the inner box; the outer view carries the drop shadow.
|
|
149
|
+
* on the inner box; the outer view carries the drop shadow. Parent layouts
|
|
150
|
+
* read positioning metadata (left/top/row/col/…) off the outer wrapper's
|
|
151
|
+
* props; flex-container props (flexDirection/gap/…) land on the inner
|
|
152
|
+
* stack that owns the children. */
|
|
149
153
|
export function SmoothCorners(props: SmoothCornersProps) @{
|
|
150
154
|
const state = useRef<{ props: SmoothCornersProps }>({ props });
|
|
151
155
|
state.current.props = props;
|
|
@@ -163,12 +167,13 @@ export function SmoothCorners(props: SmoothCornersProps) @{
|
|
|
163
167
|
);
|
|
164
168
|
|
|
165
169
|
return (
|
|
166
|
-
<flexboxlayout onLayoutChanged={outerLayout}>
|
|
170
|
+
<flexboxlayout {...outerLayoutProps(props)} onLayoutChanged={outerLayout}>
|
|
167
171
|
<flexboxlayout
|
|
172
|
+
{...innerLayoutProps(props)}
|
|
168
173
|
onLayoutChanged={innerLayout}
|
|
169
174
|
id={props.id}
|
|
170
175
|
className={props.className}
|
|
171
|
-
style={props.style}
|
|
176
|
+
style={paintedStyle(props.style)}
|
|
172
177
|
>
|
|
173
178
|
{props.children}
|
|
174
179
|
</flexboxlayout>
|
package/src/SmoothCorners.tsrx
CHANGED
|
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef } from 'octane';
|
|
|
3
3
|
import { isIOS } from '@nativescript/core';
|
|
4
4
|
import { applySmoothClip as applyIosClip, applySmoothShadow as applyIosShadow } from './apply.ios';
|
|
5
5
|
import { applySmoothClip as applyAndroidClip, applySmoothShadow as applyAndroidShadow } from './apply.android';
|
|
6
|
+
import { outerLayoutProps, paintedStyle } from './layout-child';
|
|
6
7
|
import type { SmoothCornersProps } from './props';
|
|
7
8
|
|
|
8
9
|
const applyClip = isIOS ? applyIosClip : applyAndroidClip;
|
|
@@ -12,7 +13,10 @@ const applyShadow = isIOS ? applyIosShadow : applyAndroidShadow;
|
|
|
12
13
|
* Two nested gridlayouts: the inner carries the clip (layer mask on iOS,
|
|
13
14
|
* drawable+outline on Android); the outer carries the iOS drop shadow
|
|
14
15
|
* (a masked layer can't show its own). Radius comes from `corners`, not
|
|
15
|
-
* `border-radius`.
|
|
16
|
+
* `border-radius`. Parent-layout metadata lands on the outer grid — the
|
|
17
|
+
* node the parent reads as its child. The flex-container props are
|
|
18
|
+
* web/macOS-only here: the inner host is a gridlayout, which has no flex
|
|
19
|
+
* vocabulary. */
|
|
16
20
|
export function SmoothCorners(props: SmoothCornersProps) @{
|
|
17
21
|
const state = useRef<{ inner: any; outer: any; props: SmoothCornersProps }>({
|
|
18
22
|
inner: null,
|
|
@@ -53,12 +57,12 @@ export function SmoothCorners(props: SmoothCornersProps) @{
|
|
|
53
57
|
useEffect(() => apply());
|
|
54
58
|
|
|
55
59
|
return (
|
|
56
|
-
<gridlayout ref={bindOuter.current}>
|
|
60
|
+
<gridlayout ref={bindOuter.current} {...outerLayoutProps(props)}>
|
|
57
61
|
<gridlayout
|
|
58
62
|
ref={bindInner.current}
|
|
59
63
|
id={props.id}
|
|
60
64
|
className={props.className}
|
|
61
|
-
style={props.style}
|
|
65
|
+
style={paintedStyle(props.style)}
|
|
62
66
|
>
|
|
63
67
|
{props.children}
|
|
64
68
|
</gridlayout>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useLayoutEffect, useRef, useState } from 'octane';
|
|
2
|
+
import { innerLayoutProps, outerLayoutProps, paintedStyle } from './layout-child.web';
|
|
2
3
|
import { smoothPathD } from './path';
|
|
3
4
|
import type { SmoothCornersProps } from './props';
|
|
4
5
|
|
|
@@ -26,6 +27,12 @@ export function SmoothCorners(props: SmoothCornersProps) @{
|
|
|
26
27
|
}, []);
|
|
27
28
|
|
|
28
29
|
const d = smoothPathD(size.w, size.h, props.corners);
|
|
30
|
+
const shadow = props.shadow;
|
|
31
|
+
// Flex props style the clipped box; parent-layout metadata styles the
|
|
32
|
+
// outermost element — the shadow wrapper when present, the box itself
|
|
33
|
+
// otherwise.
|
|
34
|
+
const inner = innerLayoutProps(props).style;
|
|
35
|
+
const outer = outerLayoutProps(props).style;
|
|
29
36
|
|
|
30
37
|
const box = (
|
|
31
38
|
<div
|
|
@@ -34,7 +41,9 @@ export function SmoothCorners(props: SmoothCornersProps) @{
|
|
|
34
41
|
className={props.className}
|
|
35
42
|
style={{
|
|
36
43
|
position: 'relative',
|
|
37
|
-
...props.style,
|
|
44
|
+
...paintedStyle(props.style),
|
|
45
|
+
...inner,
|
|
46
|
+
...(shadow ? {} : outer),
|
|
38
47
|
clipPath: size.w > 0 ? `path("${d}")` : undefined,
|
|
39
48
|
}}
|
|
40
49
|
>
|
|
@@ -64,8 +73,7 @@ export function SmoothCorners(props: SmoothCornersProps) @{
|
|
|
64
73
|
</div>
|
|
65
74
|
);
|
|
66
75
|
|
|
67
|
-
const shadow = props.shadow;
|
|
68
76
|
if (!shadow) return box;
|
|
69
77
|
const filter = `drop-shadow(${shadow.offsetX ?? 0}px ${shadow.offsetY ?? 0}px ${shadow.blur ?? 0}px ${shadow.color ?? 'rgba(0,0,0,0.35)'})`;
|
|
70
|
-
return <div style={{ display: 'grid', filter }}>{box}</div>;
|
|
78
|
+
return <div style={{ display: 'grid', ...outer, filter }}>{box}</div>;
|
|
71
79
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { FlexContainerProps, LayoutChildProps, SmoothCornersProps } from './props'
|
|
2
|
+
|
|
3
|
+
// start/end shorthand → flex-start/flex-end (NativeScript's value names; the
|
|
4
|
+
// AppKit renderer accepts both forms).
|
|
5
|
+
const FLEX_JUSTIFY: Record<string, string> = { start: 'flex-start', end: 'flex-end' }
|
|
6
|
+
const FLEX_ALIGN: Record<string, string> = { start: 'flex-start', end: 'flex-end' }
|
|
7
|
+
|
|
8
|
+
// The element's box geometry — parent layouts read the child's own style for
|
|
9
|
+
// size and margins, so these keys must land on the outer wrapper. The rest
|
|
10
|
+
// of `style` stays on the inner box (it is the clipped, painted surface).
|
|
11
|
+
const OUTER_STYLE_KEYS = [
|
|
12
|
+
'width',
|
|
13
|
+
'height',
|
|
14
|
+
'margin',
|
|
15
|
+
'marginTop',
|
|
16
|
+
'marginRight',
|
|
17
|
+
'marginBottom',
|
|
18
|
+
'marginLeft',
|
|
19
|
+
] as const
|
|
20
|
+
|
|
21
|
+
const MARGIN_KEYS = [
|
|
22
|
+
'margin',
|
|
23
|
+
'marginTop',
|
|
24
|
+
'marginRight',
|
|
25
|
+
'marginBottom',
|
|
26
|
+
'marginLeft',
|
|
27
|
+
] as const
|
|
28
|
+
|
|
29
|
+
/** The inner box's `style` — everything except margins, which belong to the
|
|
30
|
+
* element's border box and move out to the wrapper. Width/height stay: the
|
|
31
|
+
* AppKit inner stack cannot stretch along its parent's main axis, so it
|
|
32
|
+
* must keep its own size. */
|
|
33
|
+
export function paintedStyle(style: any) {
|
|
34
|
+
if (!style || typeof style !== 'object') {
|
|
35
|
+
return style
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const next = { ...style }
|
|
39
|
+
for (const key of MARGIN_KEYS) {
|
|
40
|
+
delete next[key]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return next
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Whitelisted by the AppKit flexboxlayout prop handler — safe to emit even
|
|
47
|
+
// as `undefined`, which is how removals propagate: the hosts merge update
|
|
48
|
+
// bags over the node's recorded props, so a vanished key would keep its
|
|
49
|
+
// stale value and parent layouts would never see the removal.
|
|
50
|
+
const CHILD_PROPS_ALWAYS = [
|
|
51
|
+
'row',
|
|
52
|
+
'col',
|
|
53
|
+
'rowSpan',
|
|
54
|
+
'colSpan',
|
|
55
|
+
'left',
|
|
56
|
+
'top',
|
|
57
|
+
'horizontalAlignment',
|
|
58
|
+
'verticalAlignment',
|
|
59
|
+
'flexGrow',
|
|
60
|
+
'flexShrink',
|
|
61
|
+
'alignSelf',
|
|
62
|
+
'order',
|
|
63
|
+
] as const
|
|
64
|
+
|
|
65
|
+
// Not whitelisted on the AppKit host (the prop-warning gap, GH#9/#10) —
|
|
66
|
+
// emitted only when set so a bare SmoothCorners does not warn every mount.
|
|
67
|
+
const CHILD_PROPS_IF_SET = ['dock', 'right', 'bottom'] as const
|
|
68
|
+
|
|
69
|
+
/** Parent-layout metadata — must land on the OUTER wrapper, the node the
|
|
70
|
+
* parent layout reads as its child. Values forward under the host's own
|
|
71
|
+
* attribute names. */
|
|
72
|
+
export function outerLayoutProps(props: LayoutChildProps & { style?: any }) {
|
|
73
|
+
const result: Record<string, any> = {}
|
|
74
|
+
for (const key of CHILD_PROPS_ALWAYS) {
|
|
75
|
+
result[key] = props[key]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const key of CHILD_PROPS_IF_SET) {
|
|
79
|
+
if (props[key] !== undefined) {
|
|
80
|
+
result[key] = props[key]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const source = props.style && typeof props.style === 'object' ? props.style : {}
|
|
85
|
+
const style: Record<string, any> = {}
|
|
86
|
+
for (const key of OUTER_STYLE_KEYS) {
|
|
87
|
+
if (source[key] !== undefined) {
|
|
88
|
+
style[key] = source[key]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Explicit undefined clears a previously mirrored geometry bag on update.
|
|
93
|
+
result.style = Object.keys(style).length > 0 ? style : undefined
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Flex-container props — land on the INNER host that owns the children.
|
|
98
|
+
* Used by the macOS leaf (inner flexboxlayout); the native leaf's inner
|
|
99
|
+
* gridlayout has no flex vocabulary and does not consume these. */
|
|
100
|
+
export function innerLayoutProps(
|
|
101
|
+
props: FlexContainerProps & Pick<SmoothCornersProps, 'flexDirection'>,
|
|
102
|
+
) {
|
|
103
|
+
const result: Record<string, any> = {}
|
|
104
|
+
// Explicit undefined so removals propagate through the merge-style update.
|
|
105
|
+
result.flexDirection = props.flexDirection
|
|
106
|
+
result.justifyContent =
|
|
107
|
+
props.justifyContent === undefined
|
|
108
|
+
? undefined
|
|
109
|
+
: (FLEX_JUSTIFY[props.justifyContent] ?? props.justifyContent)
|
|
110
|
+
|
|
111
|
+
result.alignItems =
|
|
112
|
+
props.alignItems === undefined
|
|
113
|
+
? undefined
|
|
114
|
+
: (FLEX_ALIGN[props.alignItems] ?? props.alignItems)
|
|
115
|
+
|
|
116
|
+
result.flexWrap =
|
|
117
|
+
props.flexWrap === undefined
|
|
118
|
+
? undefined
|
|
119
|
+
: props.flexWrap === true
|
|
120
|
+
? 'wrap'
|
|
121
|
+
: props.flexWrap === false
|
|
122
|
+
? 'nowrap'
|
|
123
|
+
: props.flexWrap
|
|
124
|
+
|
|
125
|
+
result.gap = props.gap
|
|
126
|
+
// rowGap/columnGap have no AppKit stack equivalent and are not whitelisted
|
|
127
|
+
// — emit only when set, matching the child-prop treatment above.
|
|
128
|
+
if (props.rowGap !== undefined) {
|
|
129
|
+
result.rowGap = props.rowGap
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (props.columnGap !== undefined) {
|
|
133
|
+
result.columnGap = props.columnGap
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result
|
|
137
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { FlexContainerProps, LayoutChildProps, SmoothCornersProps } from './props'
|
|
2
|
+
|
|
3
|
+
// start/end shorthand → flex-start/flex-end (valid in both engines).
|
|
4
|
+
const FLEX_JUSTIFY: Record<string, string> = { start: 'flex-start', end: 'flex-end' }
|
|
5
|
+
const FLEX_ALIGN: Record<string, string> = { start: 'flex-start', end: 'flex-end' }
|
|
6
|
+
|
|
7
|
+
// Grid-cell alignment vocabulary → CSS self-alignment.
|
|
8
|
+
const JUSTIFY_SELF: Record<string, string> = {
|
|
9
|
+
left: 'start',
|
|
10
|
+
center: 'center',
|
|
11
|
+
middle: 'center',
|
|
12
|
+
right: 'end',
|
|
13
|
+
stretch: 'stretch',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ALIGN_SELF: Record<string, string> = {
|
|
17
|
+
top: 'start',
|
|
18
|
+
center: 'center',
|
|
19
|
+
middle: 'center',
|
|
20
|
+
bottom: 'end',
|
|
21
|
+
stretch: 'stretch',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The element's box geometry — belongs on the outermost element so parent
|
|
25
|
+
// layouts size the whole component, not the clipped box inside it.
|
|
26
|
+
const OUTER_STYLE_KEYS = [
|
|
27
|
+
'width',
|
|
28
|
+
'height',
|
|
29
|
+
'margin',
|
|
30
|
+
'marginTop',
|
|
31
|
+
'marginRight',
|
|
32
|
+
'marginBottom',
|
|
33
|
+
'marginLeft',
|
|
34
|
+
] as const
|
|
35
|
+
|
|
36
|
+
const MARGIN_KEYS = [
|
|
37
|
+
'margin',
|
|
38
|
+
'marginTop',
|
|
39
|
+
'marginRight',
|
|
40
|
+
'marginBottom',
|
|
41
|
+
'marginLeft',
|
|
42
|
+
] as const
|
|
43
|
+
|
|
44
|
+
/** The inner box's `style` — everything except margins, which belong to the
|
|
45
|
+
* element's border box and move out to the wrapper. */
|
|
46
|
+
export function paintedStyle(style: any) {
|
|
47
|
+
if (!style || typeof style !== 'object') {
|
|
48
|
+
return style
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const next = { ...style }
|
|
52
|
+
for (const key of MARGIN_KEYS) {
|
|
53
|
+
delete next[key]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return next
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Parent-layout metadata folded into CSS on the outermost element — the
|
|
60
|
+
* shadow wrapper when present, the clipped box otherwise. Native's `dock`
|
|
61
|
+
* has no CSS equivalent and is ignored on web. Grid indices are zero-based
|
|
62
|
+
* in the shared API, matching NativeScript. */
|
|
63
|
+
export function outerLayoutProps(props: LayoutChildProps & { style?: any }) {
|
|
64
|
+
const style: Record<string, any> = {}
|
|
65
|
+
if (props.row !== undefined) {
|
|
66
|
+
style.gridRow = `${props.row + 1} / span ${props.rowSpan ?? 1}`
|
|
67
|
+
} else if (props.rowSpan !== undefined) {
|
|
68
|
+
style.gridRow = `span ${props.rowSpan}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (props.col !== undefined) {
|
|
72
|
+
style.gridColumn = `${props.col + 1} / span ${props.colSpan ?? 1}`
|
|
73
|
+
} else if (props.colSpan !== undefined) {
|
|
74
|
+
style.gridColumn = `span ${props.colSpan}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const key of ['left', 'top', 'right', 'bottom'] as const) {
|
|
78
|
+
if (props[key] !== undefined) {
|
|
79
|
+
style[key] = props[key]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (props.flexGrow !== undefined) {
|
|
84
|
+
style.flexGrow = props.flexGrow
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (props.flexShrink !== undefined) {
|
|
88
|
+
style.flexShrink = props.flexShrink
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (props.alignSelf !== undefined) {
|
|
92
|
+
style.alignSelf = props.alignSelf
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (props.order !== undefined) {
|
|
96
|
+
style.order = props.order
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (props.horizontalAlignment !== undefined) {
|
|
100
|
+
style.justifySelf = JUSTIFY_SELF[props.horizontalAlignment] ?? props.horizontalAlignment
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// CSS align-self is shared by flex and grid children — the grid-specific
|
|
104
|
+
// prop wins when both are set.
|
|
105
|
+
if (props.verticalAlignment !== undefined) {
|
|
106
|
+
style.alignSelf = ALIGN_SELF[props.verticalAlignment] ?? props.verticalAlignment
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (props.style && typeof props.style === 'object') {
|
|
110
|
+
for (const key of OUTER_STYLE_KEYS) {
|
|
111
|
+
if (props.style[key] !== undefined) {
|
|
112
|
+
style[key] = props.style[key]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { style }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Flex-container props folded into the inner box's style. Emits
|
|
121
|
+
* `display: flex` only when a flex prop is present so a bare SmoothCorners
|
|
122
|
+
* keeps normal block flow; the default direction is column to match the
|
|
123
|
+
* renderers' stack default. */
|
|
124
|
+
export function innerLayoutProps(
|
|
125
|
+
props: FlexContainerProps & Pick<SmoothCornersProps, 'flexDirection'>,
|
|
126
|
+
) {
|
|
127
|
+
const hasFlex =
|
|
128
|
+
props.flexDirection !== undefined ||
|
|
129
|
+
props.justifyContent !== undefined ||
|
|
130
|
+
props.alignItems !== undefined ||
|
|
131
|
+
props.flexWrap !== undefined ||
|
|
132
|
+
props.gap !== undefined ||
|
|
133
|
+
props.rowGap !== undefined ||
|
|
134
|
+
props.columnGap !== undefined
|
|
135
|
+
|
|
136
|
+
if (!hasFlex) {
|
|
137
|
+
return { style: {} }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const style: Record<string, any> = {
|
|
141
|
+
display: 'flex',
|
|
142
|
+
flexDirection: props.flexDirection ?? 'column',
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (props.justifyContent !== undefined) {
|
|
146
|
+
style.justifyContent = FLEX_JUSTIFY[props.justifyContent] ?? props.justifyContent
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (props.alignItems !== undefined) {
|
|
150
|
+
style.alignItems = FLEX_ALIGN[props.alignItems] ?? props.alignItems
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (props.flexWrap !== undefined) {
|
|
154
|
+
style.flexWrap =
|
|
155
|
+
props.flexWrap === true ? 'wrap' : props.flexWrap === false ? 'nowrap' : props.flexWrap
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (props.gap !== undefined) {
|
|
159
|
+
style.gap = props.gap
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (props.rowGap !== undefined) {
|
|
163
|
+
style.rowGap = props.rowGap
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (props.columnGap !== undefined) {
|
|
167
|
+
style.columnGap = props.columnGap
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { style }
|
|
171
|
+
}
|
package/src/props.ts
CHANGED
|
@@ -49,7 +49,45 @@ export interface SmoothShadowConfig {
|
|
|
49
49
|
spread?: number
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
/** Metadata read by parent layouts — the shared LayoutChildProps contract,
|
|
53
|
+
* plus the edges and grid-cell alignments the AppKit absolute/grid parents
|
|
54
|
+
* consume (`right`/`bottom`, `horizontalAlignment`/`verticalAlignment`).
|
|
55
|
+
* On SmoothCorners these land on the outer wrapper — the node the parent
|
|
56
|
+
* layout sees as its child. Native forwards the attached attributes;
|
|
57
|
+
* `right`/`bottom` are honored by the AppKit absolute layout and web CSS
|
|
58
|
+
* only (NativeScript AbsoluteLayout reads left/top). */
|
|
59
|
+
export interface LayoutChildProps {
|
|
60
|
+
row?: number
|
|
61
|
+
col?: number
|
|
62
|
+
rowSpan?: number
|
|
63
|
+
colSpan?: number
|
|
64
|
+
dock?: 'left' | 'top' | 'right' | 'bottom'
|
|
65
|
+
left?: number
|
|
66
|
+
top?: number
|
|
67
|
+
right?: number
|
|
68
|
+
bottom?: number
|
|
69
|
+
horizontalAlignment?: 'left' | 'center' | 'middle' | 'right' | 'stretch'
|
|
70
|
+
verticalAlignment?: 'top' | 'center' | 'middle' | 'bottom' | 'stretch'
|
|
71
|
+
flexGrow?: number
|
|
72
|
+
flexShrink?: number
|
|
73
|
+
alignSelf?: string
|
|
74
|
+
order?: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Flex-container props for the element's own children — RN vocabulary,
|
|
78
|
+
* applied to the inner flex host on web and the AppKit macOS leaf.
|
|
79
|
+
* The native leaf's inner container is a gridlayout, so these are
|
|
80
|
+
* web/macOS-only there. `gap` is a dip number (px on web). */
|
|
81
|
+
export interface FlexContainerProps {
|
|
82
|
+
justifyContent?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'
|
|
83
|
+
alignItems?: 'start' | 'center' | 'end' | 'stretch' | 'baseline'
|
|
84
|
+
flexWrap?: boolean | 'wrap' | 'nowrap' | 'wrap-reverse'
|
|
85
|
+
gap?: number | string
|
|
86
|
+
rowGap?: number | string
|
|
87
|
+
columnGap?: number | string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SmoothCornersProps extends LayoutChildProps, FlexContainerProps {
|
|
53
91
|
/** Radius/curve configuration — the single source of truth. `border-radius`
|
|
54
92
|
* in `style`/`className` is ignored on this element on every target. */
|
|
55
93
|
corners: CornerOptions
|
|
@@ -59,6 +97,9 @@ export interface SmoothCornersProps {
|
|
|
59
97
|
* offsets/spread); iOS/macOS use `shadowPath`; web uses
|
|
60
98
|
* `filter: drop-shadow` (no spread). */
|
|
61
99
|
shadow?: SmoothShadowConfig
|
|
100
|
+
/** Main-axis direction of the inner flex host — the renderer default is
|
|
101
|
+
* column. Web/macOS-only like the rest of FlexContainerProps. */
|
|
102
|
+
flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse'
|
|
62
103
|
className?: any
|
|
63
104
|
style?: any
|
|
64
105
|
id?: string
|
package/types/props.d.ts
CHANGED
|
@@ -42,7 +42,43 @@ export interface SmoothShadowConfig {
|
|
|
42
42
|
/** Spread is web/iOS/macOS only — Android elevation cannot express it. */
|
|
43
43
|
spread?: number;
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
/** Metadata read by parent layouts — the shared LayoutChildProps contract,
|
|
46
|
+
* plus the edges and grid-cell alignments the AppKit absolute/grid parents
|
|
47
|
+
* consume (`right`/`bottom`, `horizontalAlignment`/`verticalAlignment`).
|
|
48
|
+
* On SmoothCorners these land on the outer wrapper — the node the parent
|
|
49
|
+
* layout sees as its child. Native forwards the attached attributes;
|
|
50
|
+
* `right`/`bottom` are honored by the AppKit absolute layout and web CSS
|
|
51
|
+
* only (NativeScript AbsoluteLayout reads left/top). */
|
|
52
|
+
export interface LayoutChildProps {
|
|
53
|
+
row?: number;
|
|
54
|
+
col?: number;
|
|
55
|
+
rowSpan?: number;
|
|
56
|
+
colSpan?: number;
|
|
57
|
+
dock?: 'left' | 'top' | 'right' | 'bottom';
|
|
58
|
+
left?: number;
|
|
59
|
+
top?: number;
|
|
60
|
+
right?: number;
|
|
61
|
+
bottom?: number;
|
|
62
|
+
horizontalAlignment?: 'left' | 'center' | 'middle' | 'right' | 'stretch';
|
|
63
|
+
verticalAlignment?: 'top' | 'center' | 'middle' | 'bottom' | 'stretch';
|
|
64
|
+
flexGrow?: number;
|
|
65
|
+
flexShrink?: number;
|
|
66
|
+
alignSelf?: string;
|
|
67
|
+
order?: number;
|
|
68
|
+
}
|
|
69
|
+
/** Flex-container props for the element's own children — RN vocabulary,
|
|
70
|
+
* applied to the inner flex host on web and the AppKit macOS leaf.
|
|
71
|
+
* The native leaf's inner container is a gridlayout, so these are
|
|
72
|
+
* web/macOS-only there. `gap` is a dip number (px on web). */
|
|
73
|
+
export interface FlexContainerProps {
|
|
74
|
+
justifyContent?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
|
|
75
|
+
alignItems?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
|
|
76
|
+
flexWrap?: boolean | 'wrap' | 'nowrap' | 'wrap-reverse';
|
|
77
|
+
gap?: number | string;
|
|
78
|
+
rowGap?: number | string;
|
|
79
|
+
columnGap?: number | string;
|
|
80
|
+
}
|
|
81
|
+
export interface SmoothCornersProps extends LayoutChildProps, FlexContainerProps {
|
|
46
82
|
/** Radius/curve configuration — the single source of truth. `border-radius`
|
|
47
83
|
* in `style`/`className` is ignored on this element on every target. */
|
|
48
84
|
corners: CornerOptions;
|
|
@@ -52,6 +88,9 @@ export interface SmoothCornersProps {
|
|
|
52
88
|
* offsets/spread); iOS/macOS use `shadowPath`; web uses
|
|
53
89
|
* `filter: drop-shadow` (no spread). */
|
|
54
90
|
shadow?: SmoothShadowConfig;
|
|
91
|
+
/** Main-axis direction of the inner flex host — the renderer default is
|
|
92
|
+
* column. Web/macOS-only like the rest of FlexContainerProps. */
|
|
93
|
+
flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
55
94
|
className?: any;
|
|
56
95
|
style?: any;
|
|
57
96
|
id?: string;
|