react-native-gtkx 0.1.0-alpha.4 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/dist/adw/index.d.ts +3 -0
- package/dist/adw/index.js +41 -0
- package/dist/adw/index.js.map +1 -0
- package/dist/adw/widgets.generated.d.ts +63 -0
- package/dist/adw/widgets.generated.js +125 -0
- package/dist/adw/widgets.generated.js.map +1 -0
- package/dist/animated/create-animated.d.ts +2 -0
- package/dist/animated/create-animated.js +2 -0
- package/dist/animated/create-animated.js.map +1 -1
- package/dist/animated/index.d.ts +2 -0
- package/dist/animated/index.js +1 -0
- package/dist/animated/index.js.map +1 -1
- package/dist/animated/value-xy.d.ts +41 -0
- package/dist/animated/value-xy.js +90 -0
- package/dist/animated/value-xy.js.map +1 -0
- package/dist/animated/value.d.ts +9 -0
- package/dist/animated/value.js +30 -2
- package/dist/animated/value.js.map +1 -1
- package/dist/common/content.d.ts +69 -0
- package/dist/common/content.js +84 -0
- package/dist/common/content.js.map +1 -0
- package/dist/common/icon.d.ts +23 -0
- package/dist/common/icon.js +14 -0
- package/dist/common/icon.js.map +1 -0
- package/dist/common/index.d.ts +5 -0
- package/dist/common/index.js +21 -0
- package/dist/common/index.js.map +1 -0
- package/dist/common/list.d.ts +82 -0
- package/dist/common/list.js +166 -0
- package/dist/common/list.js.map +1 -0
- package/dist/common/navigation-stack.d.ts +84 -0
- package/dist/common/navigation-stack.js +313 -0
- package/dist/common/navigation-stack.js.map +1 -0
- package/dist/common/widget.d.ts +62 -0
- package/dist/common/widget.js +209 -0
- package/dist/common/widget.js.map +1 -0
- package/dist/components/animated.d.ts +7 -4
- package/dist/components/animated.js +88 -67
- package/dist/components/animated.js.map +1 -1
- package/dist/components/app-registry.d.ts +36 -1
- package/dist/components/app-registry.js +8 -3
- package/dist/components/app-registry.js.map +1 -1
- package/dist/components/host-node.d.ts +7 -0
- package/dist/components/host-node.js +26 -1
- package/dist/components/host-node.js.map +1 -1
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.js +2 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/measure.d.ts +13 -0
- package/dist/components/measure.js +51 -0
- package/dist/components/measure.js.map +1 -0
- package/dist/components/modal.d.ts +1 -1
- package/dist/components/press-event.d.ts +28 -0
- package/dist/components/press-event.js +46 -0
- package/dist/components/press-event.js.map +1 -0
- package/dist/components/pressable.d.ts +2 -6
- package/dist/components/pressable.js +119 -20
- package/dist/components/pressable.js.map +1 -1
- package/dist/components/rect-store.d.ts +11 -1
- package/dist/components/rect-store.js +27 -3
- package/dist/components/rect-store.js.map +1 -1
- package/dist/components/scroll-view.d.ts +4 -0
- package/dist/components/scroll-view.js +8 -2
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/svg/animated-support.d.ts +19 -0
- package/dist/components/svg/animated-support.js +64 -0
- package/dist/components/svg/animated-support.js.map +1 -0
- package/dist/components/svg/context.d.ts +8 -0
- package/dist/components/svg/context.js +15 -0
- package/dist/components/svg/context.js.map +1 -0
- package/dist/components/svg/gradients.d.ts +31 -0
- package/dist/components/svg/gradients.js +93 -0
- package/dist/components/svg/gradients.js.map +1 -0
- package/dist/components/svg/group.d.ts +8 -0
- package/dist/components/svg/group.js +30 -0
- package/dist/components/svg/group.js.map +1 -0
- package/dist/components/svg/index.d.ts +6 -0
- package/dist/components/svg/index.js +5 -0
- package/dist/components/svg/index.js.map +1 -0
- package/dist/components/svg/node.d.ts +7 -0
- package/dist/components/svg/node.js +7 -0
- package/dist/components/svg/node.js.map +1 -0
- package/dist/components/svg/paint.d.ts +24 -0
- package/dist/components/svg/paint.js +57 -0
- package/dist/components/svg/paint.js.map +1 -0
- package/dist/components/svg/shapes.d.ts +47 -0
- package/dist/components/svg/shapes.js +51 -0
- package/dist/components/svg/shapes.js.map +1 -0
- package/dist/components/svg/svg.d.ts +14 -0
- package/dist/components/svg/svg.js +48 -0
- package/dist/components/svg/svg.js.map +1 -0
- package/dist/components/svg/use-shape-node.d.ts +12 -0
- package/dist/components/svg/use-shape-node.js +52 -0
- package/dist/components/svg/use-shape-node.js.map +1 -0
- package/dist/components/text-input.js +1 -1
- package/dist/components/text-input.js.map +1 -1
- package/dist/components/text.js +36 -3
- package/dist/components/text.js.map +1 -1
- package/dist/components/use-layout-child.js +50 -4
- package/dist/components/use-layout-child.js.map +1 -1
- package/dist/components/view.d.ts +9 -4
- package/dist/components/view.js +8 -2
- package/dist/components/view.js.map +1 -1
- package/dist/contracts.d.ts +31 -4
- package/dist/contracts.js.map +1 -1
- package/dist/gtk/index.d.ts +7 -0
- package/dist/gtk/index.js +46 -0
- package/dist/gtk/index.js.map +1 -0
- package/dist/gtk/widgets.generated.d.ts +103 -0
- package/dist/gtk/widgets.generated.js +205 -0
- package/dist/gtk/widgets.generated.js.map +1 -0
- package/dist/gtk/window-actions.d.ts +77 -0
- package/dist/gtk/window-actions.js +193 -0
- package/dist/gtk/window-actions.js.map +1 -0
- package/dist/gtkx/bridge/geometry.d.ts +15 -0
- package/dist/gtkx/bridge/geometry.js +38 -0
- package/dist/gtkx/bridge/geometry.js.map +1 -0
- package/dist/gtkx/bridge/index.d.ts +11 -4
- package/dist/gtkx/bridge/index.js +47 -6
- package/dist/gtkx/bridge/index.js.map +1 -1
- package/dist/gtkx/bridge/layout-manager.d.ts +2 -1
- package/dist/gtkx/bridge/layout-manager.js +31 -9
- package/dist/gtkx/bridge/layout-manager.js.map +1 -1
- package/dist/gtkx/bridge/slot-portal.d.ts +12 -0
- package/dist/gtkx/bridge/slot-portal.js +17 -0
- package/dist/gtkx/bridge/slot-portal.js.map +1 -0
- package/dist/gtkx/bridge/svg-node.d.ts +79 -0
- package/dist/gtkx/bridge/svg-node.js +323 -0
- package/dist/gtkx/bridge/svg-node.js.map +1 -0
- package/dist/gtkx/bridge/use-signal.js +19 -9
- package/dist/gtkx/bridge/use-signal.js.map +1 -1
- package/dist/gtkx/bridge/widgets.generated.d.ts +2 -0
- package/dist/gtkx/bridge/widgets.generated.js +14 -0
- package/dist/gtkx/bridge/widgets.generated.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/bin.d.ts +2 -0
- package/dist/mcp/bin.js +17 -0
- package/dist/mcp/bin.js.map +1 -0
- package/dist/mcp/data/generated.d.ts +1304 -0
- package/dist/mcp/data/generated.js +1179 -0
- package/dist/mcp/data/generated.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/list.d.ts +4 -0
- package/dist/mcp/list.js +56 -0
- package/dist/mcp/list.js.map +1 -0
- package/dist/mcp/resolve.d.ts +55 -0
- package/dist/mcp/resolve.js +151 -0
- package/dist/mcp/resolve.js.map +1 -0
- package/dist/mcp/search.d.ts +13 -0
- package/dist/mcp/search.js +48 -0
- package/dist/mcp/search.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +135 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tool.d.ts +21 -0
- package/dist/mcp/tool.js +36 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/metro/index.js +10 -0
- package/dist/metro/index.js.map +1 -1
- package/dist/navigation/index.d.ts +50 -13
- package/dist/navigation/index.js +183 -148
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/option-warnings.js +9 -1
- package/dist/navigation/option-warnings.js.map +1 -1
- package/dist/navigation/sidebar.d.ts +207 -9
- package/dist/navigation/sidebar.js +294 -10
- package/dist/navigation/sidebar.js.map +1 -1
- package/dist/responder/system.d.ts +27 -0
- package/dist/responder/system.js +184 -0
- package/dist/responder/system.js.map +1 -0
- package/dist/responder/touch-history.d.ts +10 -0
- package/dist/responder/touch-history.js +78 -0
- package/dist/responder/touch-history.js.map +1 -0
- package/dist/responder/types.d.ts +63 -0
- package/dist/responder/types.js +29 -0
- package/dist/responder/types.js.map +1 -0
- package/dist/responder/use-responder.d.ts +12 -0
- package/dist/responder/use-responder.js +98 -0
- package/dist/responder/use-responder.js.map +1 -0
- package/dist/runner/host-dev.js +3 -1
- package/dist/runner/host-dev.js.map +1 -1
- package/dist/runner/host.js +5 -1
- package/dist/runner/host.js.map +1 -1
- package/dist/runner/index.d.ts +22 -2
- package/dist/runner/index.js +163 -13
- package/dist/runner/index.js.map +1 -1
- package/dist/sea/assemble.d.ts +29 -0
- package/dist/sea/assemble.js +129 -0
- package/dist/sea/assemble.js.map +1 -0
- package/dist/sea/bundle.d.ts +61 -0
- package/dist/sea/bundle.js +202 -0
- package/dist/sea/bundle.js.map +1 -0
- package/dist/sea/gtkx-config-module.d.ts +18 -0
- package/dist/sea/gtkx-config-module.js +67 -0
- package/dist/sea/gtkx-config-module.js.map +1 -0
- package/dist/sea/native-shim.d.ts +40 -0
- package/dist/sea/native-shim.js +121 -0
- package/dist/sea/native-shim.js.map +1 -0
- package/dist/sea/react-anchor-plugin.d.ts +2 -0
- package/dist/sea/react-anchor-plugin.js +30 -0
- package/dist/sea/react-anchor-plugin.js.map +1 -0
- package/dist/style/box-shadow.d.ts +7 -0
- package/dist/style/box-shadow.js +197 -0
- package/dist/style/box-shadow.js.map +1 -0
- package/dist/style/index.d.ts +3 -0
- package/dist/style/index.js +3 -0
- package/dist/style/index.js.map +1 -1
- package/dist/style/split-style.d.ts +5 -2
- package/dist/style/split-style.js +33 -5
- package/dist/style/split-style.js.map +1 -1
- package/dist/style/text-decoration.d.ts +15 -0
- package/dist/style/text-decoration.js +26 -0
- package/dist/style/text-decoration.js.map +1 -0
- package/dist/style/transform.d.ts +22 -0
- package/dist/style/transform.js +137 -0
- package/dist/style/transform.js.map +1 -0
- package/dist/style/visual-css.js +40 -3
- package/dist/style/visual-css.js.map +1 -1
- package/dist/svg/geometry.d.ts +19 -0
- package/dist/svg/geometry.js +80 -0
- package/dist/svg/geometry.js.map +1 -0
- package/dist/svg/gradient-geometry.d.ts +15 -0
- package/dist/svg/gradient-geometry.js +15 -0
- package/dist/svg/gradient-geometry.js.map +1 -0
- package/dist/svg/transform.d.ts +22 -0
- package/dist/svg/transform.js +74 -0
- package/dist/svg/transform.js.map +1 -0
- package/dist/svg/view-box.d.ts +34 -0
- package/dist/svg/view-box.js +108 -0
- package/dist/svg/view-box.js.map +1 -0
- package/dist/svg-compat/index.d.ts +1 -0
- package/dist/svg-compat/index.js +16 -0
- package/dist/svg-compat/index.js.map +1 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +14 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/vendor/react-native/pan-responder.d.ts +102 -0
- package/dist/vendor/react-native/pan-responder.js +196 -0
- package/dist/vendor/react-native/pan-responder.js.map +1 -0
- package/dist/vendor/react-native/touch-history-math.d.ts +18 -0
- package/dist/vendor/react-native/touch-history-math.js +102 -0
- package/dist/vendor/react-native/touch-history-math.js.map +1 -0
- package/dist/vite/index.d.ts +7 -3
- package/dist/vite/index.js +16 -4
- package/dist/vite/index.js.map +1 -1
- package/dist/vitest/index.d.ts +52 -0
- package/dist/vitest/index.js +58 -0
- package/dist/vitest/index.js.map +1 -0
- package/dist/vitest/setup.d.ts +4 -0
- package/dist/vitest/setup.js +22 -0
- package/dist/vitest/setup.js.map +1 -0
- package/package.json +52 -13
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Transform2D, TransformPart } from "../contracts";
|
|
2
|
+
export declare const IDENTITY_TRANSFORM: Transform2D;
|
|
3
|
+
/**
|
|
4
|
+
* RN angle syntax: a number with an explicit `deg` or `rad` unit. Returns
|
|
5
|
+
* degrees, or null for anything unparseable (warned once, then dropped —
|
|
6
|
+
* a bad angle must not take the whole style down).
|
|
7
|
+
*/
|
|
8
|
+
export declare const parseAngle: (value: unknown) => number | null;
|
|
9
|
+
/**
|
|
10
|
+
* Folds an RN transform array into a single matrix. Order is RN's (and
|
|
11
|
+
* CSS's): the leftmost entry is the outermost matrix, so it is applied to a
|
|
12
|
+
* point LAST — `[{rotate}, {translateX}]` translates, then rotates the
|
|
13
|
+
* result about the origin.
|
|
14
|
+
*/
|
|
15
|
+
export declare const composeTransform: (parts: readonly TransformPart[]) => Transform2D;
|
|
16
|
+
export declare const isIdentityTransform: (matrix: Transform2D) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* True when the matrix is a pure translation. Those keep the existing
|
|
19
|
+
* positional fast path (the rect is simply offset), which costs no
|
|
20
|
+
* GskTransform at all — only rotate/scale need one.
|
|
21
|
+
*/
|
|
22
|
+
export declare const isTranslationOnly: (matrix: Transform2D) => boolean;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Composes an RN `transform` array into one 2D affine matrix. Pure module —
|
|
2
|
+
// no bridge imports — so the whole ordering contract is unit-testable on any
|
|
3
|
+
// OS; the bridge only turns the finished matrix into a GskTransform.
|
|
4
|
+
//
|
|
5
|
+
// Why a matrix and not a chain of GskTransform calls: measured in the VM
|
|
6
|
+
// (docs/research/transforms.md), a five-op GskTransform chain costs 18.3 us
|
|
7
|
+
// per build against 5.7 us for one gsk_transform_matrix2d(), and both
|
|
8
|
+
// produce a bit-identical to2d(). Composition is six multiplies of plain
|
|
9
|
+
// numbers, so it stays on the Animated fast path.
|
|
10
|
+
import { warnOnce } from "./dev-warning";
|
|
11
|
+
export const IDENTITY_TRANSFORM = {
|
|
12
|
+
xx: 1,
|
|
13
|
+
yx: 0,
|
|
14
|
+
xy: 0,
|
|
15
|
+
yy: 1,
|
|
16
|
+
dx: 0,
|
|
17
|
+
dy: 0,
|
|
18
|
+
};
|
|
19
|
+
const DEG_PER_RAD = 180 / Math.PI;
|
|
20
|
+
/**
|
|
21
|
+
* RN angle syntax: a number with an explicit `deg` or `rad` unit. Returns
|
|
22
|
+
* degrees, or null for anything unparseable (warned once, then dropped —
|
|
23
|
+
* a bad angle must not take the whole style down).
|
|
24
|
+
*/
|
|
25
|
+
export const parseAngle = (value) => {
|
|
26
|
+
if (typeof value === "number") {
|
|
27
|
+
// RN rejects unitless angles in styles, but an Animated node driving a
|
|
28
|
+
// rotation with a plain numeric outputRange is a common shortcut and
|
|
29
|
+
// degrees are the only sane reading of it.
|
|
30
|
+
return Number.isFinite(value) ? value : null;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value !== "string") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const match = /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)(deg|rad)\s*$/.exec(value);
|
|
36
|
+
if (!match) {
|
|
37
|
+
warnOnce(`invalid-angle:${value}`, `[react-native-gtkx] Invalid angle ${JSON.stringify(value)} in "transform" — expected "<number>deg" or "<number>rad"; the entry is ignored`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const amount = parseFloat(match[1]);
|
|
41
|
+
return match[2] === "rad" ? amount * DEG_PER_RAD : amount;
|
|
42
|
+
};
|
|
43
|
+
// left * right, both in the Transform2D component order.
|
|
44
|
+
const multiply = (left, right) => ({
|
|
45
|
+
xx: left.xx * right.xx + left.xy * right.yx,
|
|
46
|
+
yx: left.yx * right.xx + left.yy * right.yx,
|
|
47
|
+
xy: left.xx * right.xy + left.xy * right.yy,
|
|
48
|
+
yy: left.yx * right.xy + left.yy * right.yy,
|
|
49
|
+
dx: left.xx * right.dx + left.xy * right.dy + left.dx,
|
|
50
|
+
dy: left.yx * right.dx + left.yy * right.dy + left.dy,
|
|
51
|
+
});
|
|
52
|
+
const scaleMatrix = (x, y) => Number.isFinite(x) && Number.isFinite(y)
|
|
53
|
+
? { xx: x, yx: 0, xy: 0, yy: y, dx: 0, dy: 0 }
|
|
54
|
+
: null;
|
|
55
|
+
const rotateMatrix = (angle) => {
|
|
56
|
+
const degrees = parseAngle(angle);
|
|
57
|
+
if (degrees === null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const radians = degrees / DEG_PER_RAD;
|
|
61
|
+
const cos = Math.cos(radians);
|
|
62
|
+
const sin = Math.sin(radians);
|
|
63
|
+
return { xx: cos, yx: sin, xy: -sin, yy: cos, dx: 0, dy: 0 };
|
|
64
|
+
};
|
|
65
|
+
// A TransformPart is a single-key object, and this runs on every animated
|
|
66
|
+
// frame — so the supported keys are probed directly (a monomorphic property
|
|
67
|
+
// load) instead of being discovered with Object.keys/for-in, and the matrices
|
|
68
|
+
// are plain literals rather than spreads of the identity. Key discovery only
|
|
69
|
+
// happens on the miss path, to name the unsupported entry in the warning.
|
|
70
|
+
const partToMatrix = (part) => {
|
|
71
|
+
const entry = part;
|
|
72
|
+
const translateX = entry.translateX;
|
|
73
|
+
if (typeof translateX === "number") {
|
|
74
|
+
return Number.isFinite(translateX)
|
|
75
|
+
? { xx: 1, yx: 0, xy: 0, yy: 1, dx: translateX, dy: 0 }
|
|
76
|
+
: null;
|
|
77
|
+
}
|
|
78
|
+
const translateY = entry.translateY;
|
|
79
|
+
if (typeof translateY === "number") {
|
|
80
|
+
return Number.isFinite(translateY)
|
|
81
|
+
? { xx: 1, yx: 0, xy: 0, yy: 1, dx: 0, dy: translateY }
|
|
82
|
+
: null;
|
|
83
|
+
}
|
|
84
|
+
const scale = entry.scale;
|
|
85
|
+
if (typeof scale === "number") {
|
|
86
|
+
return scaleMatrix(scale, scale);
|
|
87
|
+
}
|
|
88
|
+
const scaleX = entry.scaleX;
|
|
89
|
+
if (typeof scaleX === "number") {
|
|
90
|
+
return scaleMatrix(scaleX, 1);
|
|
91
|
+
}
|
|
92
|
+
const scaleY = entry.scaleY;
|
|
93
|
+
if (typeof scaleY === "number") {
|
|
94
|
+
return scaleMatrix(1, scaleY);
|
|
95
|
+
}
|
|
96
|
+
if (entry.rotate !== undefined) {
|
|
97
|
+
return rotateMatrix(entry.rotate);
|
|
98
|
+
}
|
|
99
|
+
if (entry.rotateZ !== undefined) {
|
|
100
|
+
return rotateMatrix(entry.rotateZ);
|
|
101
|
+
}
|
|
102
|
+
for (const key of Object.keys(entry)) {
|
|
103
|
+
warnOnce(`unsupported-transform:${key}`, `[react-native-gtkx] Unsupported transform "${key}" — supported: translateX, translateY, scale, scaleX, scaleY, rotate, rotateZ`);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Folds an RN transform array into a single matrix. Order is RN's (and
|
|
109
|
+
* CSS's): the leftmost entry is the outermost matrix, so it is applied to a
|
|
110
|
+
* point LAST — `[{rotate}, {translateX}]` translates, then rotates the
|
|
111
|
+
* result about the origin.
|
|
112
|
+
*/
|
|
113
|
+
export const composeTransform = (parts) => {
|
|
114
|
+
// `matrix === null` means "still the identity", which lets the common
|
|
115
|
+
// single-entry array skip the multiply entirely.
|
|
116
|
+
let matrix = null;
|
|
117
|
+
for (const part of parts) {
|
|
118
|
+
const next = partToMatrix(part);
|
|
119
|
+
if (next) {
|
|
120
|
+
matrix = matrix === null ? next : multiply(matrix, next);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return matrix ?? IDENTITY_TRANSFORM;
|
|
124
|
+
};
|
|
125
|
+
export const isIdentityTransform = (matrix) => matrix.xx === 1 &&
|
|
126
|
+
matrix.yx === 0 &&
|
|
127
|
+
matrix.xy === 0 &&
|
|
128
|
+
matrix.yy === 1 &&
|
|
129
|
+
matrix.dx === 0 &&
|
|
130
|
+
matrix.dy === 0;
|
|
131
|
+
/**
|
|
132
|
+
* True when the matrix is a pure translation. Those keep the existing
|
|
133
|
+
* positional fast path (the rect is simply offset), which costs no
|
|
134
|
+
* GskTransform at all — only rotate/scale need one.
|
|
135
|
+
*/
|
|
136
|
+
export const isTranslationOnly = (matrix) => matrix.xx === 1 && matrix.yx === 0 && matrix.xy === 0 && matrix.yy === 1;
|
|
137
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/style/transform.ts"],"names":[],"mappings":"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","sourcesContent":["// Composes an RN `transform` array into one 2D affine matrix. Pure module —\n// no bridge imports — so the whole ordering contract is unit-testable on any\n// OS; the bridge only turns the finished matrix into a GskTransform.\n//\n// Why a matrix and not a chain of GskTransform calls: measured in the VM\n// (docs/research/transforms.md), a five-op GskTransform chain costs 18.3 us\n// per build against 5.7 us for one gsk_transform_matrix2d(), and both\n// produce a bit-identical to2d(). Composition is six multiplies of plain\n// numbers, so it stays on the Animated fast path.\n\nimport type { Transform2D, TransformPart } from \"../contracts\"\nimport { warnOnce } from \"./dev-warning\"\n\nexport const IDENTITY_TRANSFORM: Transform2D = {\n xx: 1,\n yx: 0,\n xy: 0,\n yy: 1,\n dx: 0,\n dy: 0,\n}\n\nconst DEG_PER_RAD = 180 / Math.PI\n\n/**\n * RN angle syntax: a number with an explicit `deg` or `rad` unit. Returns\n * degrees, or null for anything unparseable (warned once, then dropped —\n * a bad angle must not take the whole style down).\n */\nexport const parseAngle = (value: unknown): number | null => {\n if (typeof value === \"number\") {\n // RN rejects unitless angles in styles, but an Animated node driving a\n // rotation with a plain numeric outputRange is a common shortcut and\n // degrees are the only sane reading of it.\n return Number.isFinite(value) ? value : null\n }\n if (typeof value !== \"string\") {\n return null\n }\n const match =\n /^\\s*([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?)(deg|rad)\\s*$/.exec(value)\n if (!match) {\n warnOnce(\n `invalid-angle:${value}`,\n `[react-native-gtkx] Invalid angle ${JSON.stringify(value)} in \"transform\" — expected \"<number>deg\" or \"<number>rad\"; the entry is ignored`,\n )\n return null\n }\n const amount = parseFloat(match[1]!)\n return match[2] === \"rad\" ? amount * DEG_PER_RAD : amount\n}\n\n// left * right, both in the Transform2D component order.\nconst multiply = (left: Transform2D, right: Transform2D): Transform2D => ({\n xx: left.xx * right.xx + left.xy * right.yx,\n yx: left.yx * right.xx + left.yy * right.yx,\n xy: left.xx * right.xy + left.xy * right.yy,\n yy: left.yx * right.xy + left.yy * right.yy,\n dx: left.xx * right.dx + left.xy * right.dy + left.dx,\n dy: left.yx * right.dx + left.yy * right.dy + left.dy,\n})\n\nconst scaleMatrix = (x: number, y: number): Transform2D | null =>\n Number.isFinite(x) && Number.isFinite(y)\n ? { xx: x, yx: 0, xy: 0, yy: y, dx: 0, dy: 0 }\n : null\n\nconst rotateMatrix = (angle: unknown): Transform2D | null => {\n const degrees = parseAngle(angle)\n if (degrees === null) {\n return null\n }\n const radians = degrees / DEG_PER_RAD\n const cos = Math.cos(radians)\n const sin = Math.sin(radians)\n return { xx: cos, yx: sin, xy: -sin, yy: cos, dx: 0, dy: 0 }\n}\n\n// A TransformPart is a single-key object, and this runs on every animated\n// frame — so the supported keys are probed directly (a monomorphic property\n// load) instead of being discovered with Object.keys/for-in, and the matrices\n// are plain literals rather than spreads of the identity. Key discovery only\n// happens on the miss path, to name the unsupported entry in the warning.\nconst partToMatrix = (part: TransformPart): Transform2D | null => {\n const entry = part as Record<string, unknown>\n\n const translateX = entry.translateX\n if (typeof translateX === \"number\") {\n return Number.isFinite(translateX)\n ? { xx: 1, yx: 0, xy: 0, yy: 1, dx: translateX, dy: 0 }\n : null\n }\n const translateY = entry.translateY\n if (typeof translateY === \"number\") {\n return Number.isFinite(translateY)\n ? { xx: 1, yx: 0, xy: 0, yy: 1, dx: 0, dy: translateY }\n : null\n }\n const scale = entry.scale\n if (typeof scale === \"number\") {\n return scaleMatrix(scale, scale)\n }\n const scaleX = entry.scaleX\n if (typeof scaleX === \"number\") {\n return scaleMatrix(scaleX, 1)\n }\n const scaleY = entry.scaleY\n if (typeof scaleY === \"number\") {\n return scaleMatrix(1, scaleY)\n }\n if (entry.rotate !== undefined) {\n return rotateMatrix(entry.rotate)\n }\n if (entry.rotateZ !== undefined) {\n return rotateMatrix(entry.rotateZ)\n }\n\n for (const key of Object.keys(entry)) {\n warnOnce(\n `unsupported-transform:${key}`,\n `[react-native-gtkx] Unsupported transform \"${key}\" — supported: translateX, translateY, scale, scaleX, scaleY, rotate, rotateZ`,\n )\n }\n return null\n}\n\n/**\n * Folds an RN transform array into a single matrix. Order is RN's (and\n * CSS's): the leftmost entry is the outermost matrix, so it is applied to a\n * point LAST — `[{rotate}, {translateX}]` translates, then rotates the\n * result about the origin.\n */\nexport const composeTransform = (\n parts: readonly TransformPart[],\n): Transform2D => {\n // `matrix === null` means \"still the identity\", which lets the common\n // single-entry array skip the multiply entirely.\n let matrix: Transform2D | null = null\n for (const part of parts) {\n const next = partToMatrix(part)\n if (next) {\n matrix = matrix === null ? next : multiply(matrix, next)\n }\n }\n return matrix ?? IDENTITY_TRANSFORM\n}\n\nexport const isIdentityTransform = (matrix: Transform2D): boolean =>\n matrix.xx === 1 &&\n matrix.yx === 0 &&\n matrix.xy === 0 &&\n matrix.yy === 1 &&\n matrix.dx === 0 &&\n matrix.dy === 0\n\n/**\n * True when the matrix is a pure translation. Those keep the existing\n * positional fast path (the rect is simply offset), which costs no\n * GskTransform at all — only rotate/scale need one.\n */\nexport const isTranslationOnly = (matrix: Transform2D): boolean =>\n matrix.xx === 1 && matrix.yx === 0 && matrix.xy === 0 && matrix.yy === 1\n"]}
|
package/dist/style/visual-css.js
CHANGED
|
@@ -5,11 +5,19 @@
|
|
|
5
5
|
// GTK4 CSS is not web CSS. What this generator relies on:
|
|
6
6
|
// - background-color, border-*, border-radius (incl. per-corner), opacity,
|
|
7
7
|
// color, font-*, letter-spacing: supported by GTK4;
|
|
8
|
+
// - box-shadow and outline-*: supported by GTK4, and used by Adwaita itself
|
|
9
|
+
// for the `.card`/`.boxed-list` frame and for every focus ring in the
|
|
10
|
+
// theme — which is why an app cannot reproduce the platform's own look
|
|
11
|
+
// without them. box-shadow is compiled by ./box-shadow.ts;
|
|
8
12
|
// - line-height: supported since GTK 4.6 (we target 4.20+);
|
|
9
13
|
// - text-align does NOT exist in GTK CSS — textAlign is applied by the Text
|
|
10
14
|
// component via widget properties (justify/xalign), so it is skipped here;
|
|
11
|
-
// -
|
|
12
|
-
//
|
|
15
|
+
// - text-decoration does not exist for GTK widgets either: Pango carries it,
|
|
16
|
+
// and the Text component applies textDecorationLine as label attributes;
|
|
17
|
+
// - transform does NOT exist as a widget property in GTK4 CSS either: it is
|
|
18
|
+
// composed into a matrix by ./transform.ts and applied as the GskTransform
|
|
19
|
+
// of the child's allocation, so it is skipped here as well.
|
|
20
|
+
import { boxShadowToCss } from "./box-shadow";
|
|
13
21
|
import { parseColor } from "./colors";
|
|
14
22
|
import { warnOnce } from "./dev-warning";
|
|
15
23
|
const px = (value) => `${value}px`;
|
|
@@ -43,6 +51,12 @@ export const visualStyleToCss = (visual) => {
|
|
|
43
51
|
if (visual.opacity !== undefined) {
|
|
44
52
|
decls.push(`opacity: ${clamp01(visual.opacity)};`);
|
|
45
53
|
}
|
|
54
|
+
if (visual.boxShadow !== undefined) {
|
|
55
|
+
const shadow = boxShadowToCss(visual.boxShadow);
|
|
56
|
+
if (shadow !== null) {
|
|
57
|
+
decls.push(`box-shadow: ${shadow};`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
46
60
|
// GTK defaults border-style to none — width alone renders nothing. An
|
|
47
61
|
// explicit borderStyle wins (GTK4 CSS supports solid/dotted/dashed);
|
|
48
62
|
// otherwise any positive border width turns the border on with "solid".
|
|
@@ -106,6 +120,28 @@ export const visualStyleToCss = (visual) => {
|
|
|
106
120
|
if (visual.borderBottomLeftRadius !== undefined) {
|
|
107
121
|
decls.push(`border-bottom-left-radius: ${px(visual.borderBottomLeftRadius)};`);
|
|
108
122
|
}
|
|
123
|
+
// outline is a ring around the border box that takes no layout space, so
|
|
124
|
+
// unlike `border` it needs no Yoga involvement at all — GTK4 draws it from
|
|
125
|
+
// CSS exactly as the web does, which is how Adwaita itself paints every
|
|
126
|
+
// focus ring in the theme. GTK, like the web, defaults outline-style to
|
|
127
|
+
// `none`, so a width on its own would draw nothing; the same auto-solid
|
|
128
|
+
// rule the borders above use applies.
|
|
129
|
+
const hasVisibleOutlineWidth = visual.outlineWidth !== undefined && visual.outlineWidth > 0;
|
|
130
|
+
if (visual.outlineStyle !== undefined) {
|
|
131
|
+
decls.push(`outline-style: ${visual.outlineStyle};`);
|
|
132
|
+
}
|
|
133
|
+
else if (hasVisibleOutlineWidth) {
|
|
134
|
+
decls.push("outline-style: solid;");
|
|
135
|
+
}
|
|
136
|
+
if (visual.outlineWidth !== undefined) {
|
|
137
|
+
decls.push(`outline-width: ${px(visual.outlineWidth)};`);
|
|
138
|
+
}
|
|
139
|
+
if (visual.outlineColor !== undefined) {
|
|
140
|
+
pushColor(decls, "outline-color", visual.outlineColor, "outlineColor");
|
|
141
|
+
}
|
|
142
|
+
if (visual.outlineOffset !== undefined) {
|
|
143
|
+
decls.push(`outline-offset: ${px(visual.outlineOffset)};`);
|
|
144
|
+
}
|
|
109
145
|
if (visual.color !== undefined) {
|
|
110
146
|
pushColor(decls, "color", visual.color, "color");
|
|
111
147
|
}
|
|
@@ -127,7 +163,8 @@ export const visualStyleToCss = (visual) => {
|
|
|
127
163
|
if (visual.lineHeight !== undefined) {
|
|
128
164
|
decls.push(`line-height: ${px(visual.lineHeight)};`);
|
|
129
165
|
}
|
|
130
|
-
// visual.textAlign, visual.transform:
|
|
166
|
+
// visual.textAlign, visual.textDecorationLine, visual.transform:
|
|
167
|
+
// intentionally not CSS (see header).
|
|
131
168
|
return decls.join("\n");
|
|
132
169
|
};
|
|
133
170
|
//# sourceMappingURL=visual-css.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visual-css.js","sourceRoot":"","sources":["../../src/style/visual-css.ts"],"names":[],"mappings":"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","sourcesContent":["// Compiles a VisualStyle into a GTK4 CSS declaration block (no selector).\n// Pure module — the resulting string is registered as a class through an\n// injected `css` function (see registry.ts / registry.gtkx.ts).\n//\n// GTK4 CSS is not web CSS. What this generator relies on:\n// - background-color, border-*, border-radius (incl. per-corner), opacity,\n// color, font-*, letter-spacing: supported by GTK4;\n// - line-height: supported since GTK 4.6 (we target 4.20+);\n// - text-align does NOT exist in GTK CSS — textAlign is applied by the Text\n// component via widget properties (justify/xalign), so it is skipped here;\n// - transform is applied by the layout engine via Fixed.Child matrices,\n// never through CSS, and is skipped here as well.\n\nimport type { VisualStyle } from \"../contracts\"\nimport { parseColor } from \"./colors\"\nimport { warnOnce } from \"./dev-warning\"\n\nconst px = (value: number): string => `${value}px`\n\nconst clamp01 = (value: number): number => Math.min(1, Math.max(0, value))\n\nconst pushColor = (\n declarations: string[],\n cssProperty: string,\n value: string,\n styleProperty: string,\n): void => {\n const parsed = parseColor(value)\n if (parsed === null) {\n warnOnce(\n `invalid-color:${styleProperty}:${value}`,\n `[react-native-gtkx] Invalid color ${JSON.stringify(value)} in \"${styleProperty}\" — declaration dropped`,\n )\n return\n }\n declarations.push(`${cssProperty}: ${parsed};`)\n}\n\nconst formatFontFamily = (family: string): string => {\n const trimmed = family.trim()\n if (/^(\".*\"|'.*')$/.test(trimmed)) {\n return trimmed\n }\n return /^[A-Za-z][\\w-]*$/.test(trimmed)\n ? trimmed\n : `\"${trimmed.replace(/\"/g, '\\\\\"')}\"`\n}\n\n/**\n * Renders a VisualStyle as GTK CSS declarations (one per line, stable\n * order). Returns \"\" when the style produces no CSS (e.g. transform-only).\n */\nexport const visualStyleToCss = (visual: VisualStyle): string => {\n const decls: string[] = []\n\n if (visual.backgroundColor !== undefined) {\n pushColor(\n decls,\n \"background-color\",\n visual.backgroundColor,\n \"backgroundColor\",\n )\n }\n if (visual.opacity !== undefined) {\n decls.push(`opacity: ${clamp01(visual.opacity)};`)\n }\n\n // GTK defaults border-style to none — width alone renders nothing. An\n // explicit borderStyle wins (GTK4 CSS supports solid/dotted/dashed);\n // otherwise any positive border width turns the border on with \"solid\".\n const hasVisibleBorderWidth = [\n visual.borderWidth,\n visual.borderTopWidth,\n visual.borderRightWidth,\n visual.borderBottomWidth,\n visual.borderLeftWidth,\n ].some((width) => width !== undefined && width > 0)\n if (visual.borderStyle !== undefined) {\n decls.push(`border-style: ${visual.borderStyle};`)\n } else if (hasVisibleBorderWidth) {\n decls.push(\"border-style: solid;\")\n }\n // Shorthands first, then per-side overrides so the sides win.\n if (visual.borderWidth !== undefined) {\n decls.push(`border-width: ${px(visual.borderWidth)};`)\n }\n if (visual.borderTopWidth !== undefined) {\n decls.push(`border-top-width: ${px(visual.borderTopWidth)};`)\n }\n if (visual.borderRightWidth !== undefined) {\n decls.push(`border-right-width: ${px(visual.borderRightWidth)};`)\n }\n if (visual.borderBottomWidth !== undefined) {\n decls.push(`border-bottom-width: ${px(visual.borderBottomWidth)};`)\n }\n if (visual.borderLeftWidth !== undefined) {\n decls.push(`border-left-width: ${px(visual.borderLeftWidth)};`)\n }\n if (visual.borderColor !== undefined) {\n pushColor(decls, \"border-color\", visual.borderColor, \"borderColor\")\n }\n if (visual.borderTopColor !== undefined) {\n pushColor(\n decls,\n \"border-top-color\",\n visual.borderTopColor,\n \"borderTopColor\",\n )\n }\n if (visual.borderRightColor !== undefined) {\n pushColor(\n decls,\n \"border-right-color\",\n visual.borderRightColor,\n \"borderRightColor\",\n )\n }\n if (visual.borderBottomColor !== undefined) {\n pushColor(\n decls,\n \"border-bottom-color\",\n visual.borderBottomColor,\n \"borderBottomColor\",\n )\n }\n if (visual.borderLeftColor !== undefined) {\n pushColor(\n decls,\n \"border-left-color\",\n visual.borderLeftColor,\n \"borderLeftColor\",\n )\n }\n if (visual.borderRadius !== undefined) {\n decls.push(`border-radius: ${px(visual.borderRadius)};`)\n }\n // Per-corner radii come after the shorthand so they override it.\n if (visual.borderTopLeftRadius !== undefined) {\n decls.push(`border-top-left-radius: ${px(visual.borderTopLeftRadius)};`)\n }\n if (visual.borderTopRightRadius !== undefined) {\n decls.push(`border-top-right-radius: ${px(visual.borderTopRightRadius)};`)\n }\n if (visual.borderBottomRightRadius !== undefined) {\n decls.push(\n `border-bottom-right-radius: ${px(visual.borderBottomRightRadius)};`,\n )\n }\n if (visual.borderBottomLeftRadius !== undefined) {\n decls.push(\n `border-bottom-left-radius: ${px(visual.borderBottomLeftRadius)};`,\n )\n }\n\n if (visual.color !== undefined) {\n pushColor(decls, \"color\", visual.color, \"color\")\n }\n if (visual.fontFamily !== undefined) {\n decls.push(`font-family: ${formatFontFamily(visual.fontFamily)};`)\n }\n if (visual.fontSize !== undefined) {\n decls.push(`font-size: ${px(visual.fontSize)};`)\n }\n if (visual.fontStyle !== undefined) {\n decls.push(`font-style: ${visual.fontStyle};`)\n }\n if (visual.fontWeight !== undefined) {\n decls.push(`font-weight: ${visual.fontWeight};`)\n }\n if (visual.letterSpacing !== undefined) {\n decls.push(`letter-spacing: ${px(visual.letterSpacing)};`)\n }\n if (visual.lineHeight !== undefined) {\n decls.push(`line-height: ${px(visual.lineHeight)};`)\n }\n\n // visual.textAlign, visual.transform: intentionally not CSS (see header).\n\n return decls.join(\"\\n\")\n}\n"]}
|
|
1
|
+
{"version":3,"file":"visual-css.js","sourceRoot":"","sources":["../../src/style/visual-css.ts"],"names":[],"mappings":"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","sourcesContent":["// Compiles a VisualStyle into a GTK4 CSS declaration block (no selector).\n// Pure module — the resulting string is registered as a class through an\n// injected `css` function (see registry.ts / registry.gtkx.ts).\n//\n// GTK4 CSS is not web CSS. What this generator relies on:\n// - background-color, border-*, border-radius (incl. per-corner), opacity,\n// color, font-*, letter-spacing: supported by GTK4;\n// - box-shadow and outline-*: supported by GTK4, and used by Adwaita itself\n// for the `.card`/`.boxed-list` frame and for every focus ring in the\n// theme — which is why an app cannot reproduce the platform's own look\n// without them. box-shadow is compiled by ./box-shadow.ts;\n// - line-height: supported since GTK 4.6 (we target 4.20+);\n// - text-align does NOT exist in GTK CSS — textAlign is applied by the Text\n// component via widget properties (justify/xalign), so it is skipped here;\n// - text-decoration does not exist for GTK widgets either: Pango carries it,\n// and the Text component applies textDecorationLine as label attributes;\n// - transform does NOT exist as a widget property in GTK4 CSS either: it is\n// composed into a matrix by ./transform.ts and applied as the GskTransform\n// of the child's allocation, so it is skipped here as well.\n\nimport type { VisualStyle } from \"../contracts\"\nimport { boxShadowToCss } from \"./box-shadow\"\nimport { parseColor } from \"./colors\"\nimport { warnOnce } from \"./dev-warning\"\n\nconst px = (value: number): string => `${value}px`\n\nconst clamp01 = (value: number): number => Math.min(1, Math.max(0, value))\n\nconst pushColor = (\n declarations: string[],\n cssProperty: string,\n value: string,\n styleProperty: string,\n): void => {\n const parsed = parseColor(value)\n if (parsed === null) {\n warnOnce(\n `invalid-color:${styleProperty}:${value}`,\n `[react-native-gtkx] Invalid color ${JSON.stringify(value)} in \"${styleProperty}\" — declaration dropped`,\n )\n return\n }\n declarations.push(`${cssProperty}: ${parsed};`)\n}\n\nconst formatFontFamily = (family: string): string => {\n const trimmed = family.trim()\n if (/^(\".*\"|'.*')$/.test(trimmed)) {\n return trimmed\n }\n return /^[A-Za-z][\\w-]*$/.test(trimmed)\n ? trimmed\n : `\"${trimmed.replace(/\"/g, '\\\\\"')}\"`\n}\n\n/**\n * Renders a VisualStyle as GTK CSS declarations (one per line, stable\n * order). Returns \"\" when the style produces no CSS (e.g. transform-only).\n */\nexport const visualStyleToCss = (visual: VisualStyle): string => {\n const decls: string[] = []\n\n if (visual.backgroundColor !== undefined) {\n pushColor(\n decls,\n \"background-color\",\n visual.backgroundColor,\n \"backgroundColor\",\n )\n }\n if (visual.opacity !== undefined) {\n decls.push(`opacity: ${clamp01(visual.opacity)};`)\n }\n if (visual.boxShadow !== undefined) {\n const shadow = boxShadowToCss(visual.boxShadow)\n if (shadow !== null) {\n decls.push(`box-shadow: ${shadow};`)\n }\n }\n\n // GTK defaults border-style to none — width alone renders nothing. An\n // explicit borderStyle wins (GTK4 CSS supports solid/dotted/dashed);\n // otherwise any positive border width turns the border on with \"solid\".\n const hasVisibleBorderWidth = [\n visual.borderWidth,\n visual.borderTopWidth,\n visual.borderRightWidth,\n visual.borderBottomWidth,\n visual.borderLeftWidth,\n ].some((width) => width !== undefined && width > 0)\n if (visual.borderStyle !== undefined) {\n decls.push(`border-style: ${visual.borderStyle};`)\n } else if (hasVisibleBorderWidth) {\n decls.push(\"border-style: solid;\")\n }\n // Shorthands first, then per-side overrides so the sides win.\n if (visual.borderWidth !== undefined) {\n decls.push(`border-width: ${px(visual.borderWidth)};`)\n }\n if (visual.borderTopWidth !== undefined) {\n decls.push(`border-top-width: ${px(visual.borderTopWidth)};`)\n }\n if (visual.borderRightWidth !== undefined) {\n decls.push(`border-right-width: ${px(visual.borderRightWidth)};`)\n }\n if (visual.borderBottomWidth !== undefined) {\n decls.push(`border-bottom-width: ${px(visual.borderBottomWidth)};`)\n }\n if (visual.borderLeftWidth !== undefined) {\n decls.push(`border-left-width: ${px(visual.borderLeftWidth)};`)\n }\n if (visual.borderColor !== undefined) {\n pushColor(decls, \"border-color\", visual.borderColor, \"borderColor\")\n }\n if (visual.borderTopColor !== undefined) {\n pushColor(\n decls,\n \"border-top-color\",\n visual.borderTopColor,\n \"borderTopColor\",\n )\n }\n if (visual.borderRightColor !== undefined) {\n pushColor(\n decls,\n \"border-right-color\",\n visual.borderRightColor,\n \"borderRightColor\",\n )\n }\n if (visual.borderBottomColor !== undefined) {\n pushColor(\n decls,\n \"border-bottom-color\",\n visual.borderBottomColor,\n \"borderBottomColor\",\n )\n }\n if (visual.borderLeftColor !== undefined) {\n pushColor(\n decls,\n \"border-left-color\",\n visual.borderLeftColor,\n \"borderLeftColor\",\n )\n }\n if (visual.borderRadius !== undefined) {\n decls.push(`border-radius: ${px(visual.borderRadius)};`)\n }\n // Per-corner radii come after the shorthand so they override it.\n if (visual.borderTopLeftRadius !== undefined) {\n decls.push(`border-top-left-radius: ${px(visual.borderTopLeftRadius)};`)\n }\n if (visual.borderTopRightRadius !== undefined) {\n decls.push(`border-top-right-radius: ${px(visual.borderTopRightRadius)};`)\n }\n if (visual.borderBottomRightRadius !== undefined) {\n decls.push(\n `border-bottom-right-radius: ${px(visual.borderBottomRightRadius)};`,\n )\n }\n if (visual.borderBottomLeftRadius !== undefined) {\n decls.push(\n `border-bottom-left-radius: ${px(visual.borderBottomLeftRadius)};`,\n )\n }\n\n // outline is a ring around the border box that takes no layout space, so\n // unlike `border` it needs no Yoga involvement at all — GTK4 draws it from\n // CSS exactly as the web does, which is how Adwaita itself paints every\n // focus ring in the theme. GTK, like the web, defaults outline-style to\n // `none`, so a width on its own would draw nothing; the same auto-solid\n // rule the borders above use applies.\n const hasVisibleOutlineWidth =\n visual.outlineWidth !== undefined && visual.outlineWidth > 0\n if (visual.outlineStyle !== undefined) {\n decls.push(`outline-style: ${visual.outlineStyle};`)\n } else if (hasVisibleOutlineWidth) {\n decls.push(\"outline-style: solid;\")\n }\n if (visual.outlineWidth !== undefined) {\n decls.push(`outline-width: ${px(visual.outlineWidth)};`)\n }\n if (visual.outlineColor !== undefined) {\n pushColor(decls, \"outline-color\", visual.outlineColor, \"outlineColor\")\n }\n if (visual.outlineOffset !== undefined) {\n decls.push(`outline-offset: ${px(visual.outlineOffset)};`)\n }\n\n if (visual.color !== undefined) {\n pushColor(decls, \"color\", visual.color, \"color\")\n }\n if (visual.fontFamily !== undefined) {\n decls.push(`font-family: ${formatFontFamily(visual.fontFamily)};`)\n }\n if (visual.fontSize !== undefined) {\n decls.push(`font-size: ${px(visual.fontSize)};`)\n }\n if (visual.fontStyle !== undefined) {\n decls.push(`font-style: ${visual.fontStyle};`)\n }\n if (visual.fontWeight !== undefined) {\n decls.push(`font-weight: ${visual.fontWeight};`)\n }\n if (visual.letterSpacing !== undefined) {\n decls.push(`letter-spacing: ${px(visual.letterSpacing)};`)\n }\n if (visual.lineHeight !== undefined) {\n decls.push(`line-height: ${px(visual.lineHeight)};`)\n }\n\n // visual.textAlign, visual.textDecorationLine, visual.transform:\n // intentionally not CSS (see header).\n\n return decls.join(\"\\n\")\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Point = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
export declare const parsePoints: (points: string) => Point[];
|
|
6
|
+
export type RectShape = {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
rx?: number;
|
|
12
|
+
ry?: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const rectToPathData: ({ x, y, width, height, rx, ry, }: RectShape) => string;
|
|
15
|
+
export declare const ellipseToPathData: (cx: number, cy: number, rx: number, ry: number) => string;
|
|
16
|
+
export declare const circleToPathData: (cx: number, cy: number, r: number) => string;
|
|
17
|
+
export declare const lineToPathData: (x1: number, y1: number, x2: number, y2: number) => string;
|
|
18
|
+
export declare const polygonToPathData: (points: string | Point[]) => string;
|
|
19
|
+
export declare const polylineToPathData: (points: string | Point[]) => string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Pure shape → SVG path `d` string generators. No gtkx imports (unit-testable
|
|
2
|
+
// on any OS, like style/colors.ts) — the only consumer of the output is
|
|
3
|
+
// `Gsk.Path.parse()` in the bridge, so every generator here produces (a
|
|
4
|
+
// superset of) SVG path syntax and nothing else. This is the "no parser of
|
|
5
|
+
// our own" bet from the PRD: <Path> passes its `d` straight through, and
|
|
6
|
+
// every other shape is just a small amount of arithmetic away from the same
|
|
7
|
+
// syntax.
|
|
8
|
+
// Accepts both the comma-separated and whitespace-separated forms RN-svg
|
|
9
|
+
// tolerates ("0,0 10,5" and "0 0 10 5"), and odd trailing tokens are
|
|
10
|
+
// dropped rather than throwing — malformed `points` should render nothing
|
|
11
|
+
// useful, not crash the tree.
|
|
12
|
+
export const parsePoints = (points) => {
|
|
13
|
+
const numbers = points
|
|
14
|
+
.trim()
|
|
15
|
+
.split(/[\s,]+/)
|
|
16
|
+
.filter((token) => token.length > 0)
|
|
17
|
+
.map(Number)
|
|
18
|
+
.filter((value) => !Number.isNaN(value));
|
|
19
|
+
const result = [];
|
|
20
|
+
for (let i = 0; i + 1 < numbers.length; i += 2) {
|
|
21
|
+
result.push({ x: numbers[i], y: numbers[i + 1] });
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
// SVG's rx/ry resolution: either one fills in for a missing other, and both
|
|
26
|
+
// clamp to half the box so opposite corners never overlap.
|
|
27
|
+
const resolveCornerRadii = (width, height, rx, ry) => {
|
|
28
|
+
const rawRx = rx ?? ry ?? 0;
|
|
29
|
+
const rawRy = ry ?? rx ?? 0;
|
|
30
|
+
return [
|
|
31
|
+
Math.max(0, Math.min(rawRx, width / 2)),
|
|
32
|
+
Math.max(0, Math.min(rawRy, height / 2)),
|
|
33
|
+
];
|
|
34
|
+
};
|
|
35
|
+
export const rectToPathData = ({ x, y, width, height, rx, ry, }) => {
|
|
36
|
+
if (width <= 0 || height <= 0) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
const [radiusX, radiusY] = resolveCornerRadii(width, height, rx, ry);
|
|
40
|
+
if (radiusX <= 0 || radiusY <= 0) {
|
|
41
|
+
return `M ${x} ${y} H ${x + width} V ${y + height} H ${x} Z`;
|
|
42
|
+
}
|
|
43
|
+
const right = x + width;
|
|
44
|
+
const bottom = y + height;
|
|
45
|
+
return (`M ${x + radiusX} ${y} ` +
|
|
46
|
+
`H ${right - radiusX} ` +
|
|
47
|
+
`A ${radiusX} ${radiusY} 0 0 1 ${right} ${y + radiusY} ` +
|
|
48
|
+
`V ${bottom - radiusY} ` +
|
|
49
|
+
`A ${radiusX} ${radiusY} 0 0 1 ${right - radiusX} ${bottom} ` +
|
|
50
|
+
`H ${x + radiusX} ` +
|
|
51
|
+
`A ${radiusX} ${radiusY} 0 0 1 ${x} ${bottom - radiusY} ` +
|
|
52
|
+
`V ${y + radiusY} ` +
|
|
53
|
+
`A ${radiusX} ${radiusY} 0 0 1 ${x + radiusX} ${y} Z`);
|
|
54
|
+
};
|
|
55
|
+
// Two semicircle arcs — the standard trick for a full ellipse/circle in a
|
|
56
|
+
// path grammar that has no dedicated "ellipse" command.
|
|
57
|
+
export const ellipseToPathData = (cx, cy, rx, ry) => {
|
|
58
|
+
if (rx <= 0 || ry <= 0) {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
return (`M ${cx - rx} ${cy} ` +
|
|
62
|
+
`A ${rx} ${ry} 0 1 0 ${cx + rx} ${cy} ` +
|
|
63
|
+
`A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy} Z`);
|
|
64
|
+
};
|
|
65
|
+
export const circleToPathData = (cx, cy, r) => ellipseToPathData(cx, cy, r, r);
|
|
66
|
+
export const lineToPathData = (x1, y1, x2, y2) => `M ${x1} ${y1} L ${x2} ${y2}`;
|
|
67
|
+
const pointsToPathData = (points, close) => {
|
|
68
|
+
if (points.length === 0) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
const [first, ...rest] = points;
|
|
72
|
+
const segments = rest.map((p) => `L ${p.x} ${p.y}`).join(" ");
|
|
73
|
+
const body = segments.length > 0
|
|
74
|
+
? `M ${first.x} ${first.y} ${segments}`
|
|
75
|
+
: `M ${first.x} ${first.y}`;
|
|
76
|
+
return close ? `${body} Z` : body;
|
|
77
|
+
};
|
|
78
|
+
export const polygonToPathData = (points) => pointsToPathData(typeof points === "string" ? parsePoints(points) : points, true);
|
|
79
|
+
export const polylineToPathData = (points) => pointsToPathData(typeof points === "string" ? parsePoints(points) : points, false);
|
|
80
|
+
//# sourceMappingURL=geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry.js","sourceRoot":"","sources":["../../src/svg/geometry.ts"],"names":[],"mappings":"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","sourcesContent":["// Pure shape → SVG path `d` string generators. No gtkx imports (unit-testable\n// on any OS, like style/colors.ts) — the only consumer of the output is\n// `Gsk.Path.parse()` in the bridge, so every generator here produces (a\n// superset of) SVG path syntax and nothing else. This is the \"no parser of\n// our own\" bet from the PRD: <Path> passes its `d` straight through, and\n// every other shape is just a small amount of arithmetic away from the same\n// syntax.\n\nexport type Point = { x: number; y: number }\n\n// Accepts both the comma-separated and whitespace-separated forms RN-svg\n// tolerates (\"0,0 10,5\" and \"0 0 10 5\"), and odd trailing tokens are\n// dropped rather than throwing — malformed `points` should render nothing\n// useful, not crash the tree.\nexport const parsePoints = (points: string): Point[] => {\n const numbers = points\n .trim()\n .split(/[\\s,]+/)\n .filter((token) => token.length > 0)\n .map(Number)\n .filter((value) => !Number.isNaN(value))\n const result: Point[] = []\n for (let i = 0; i + 1 < numbers.length; i += 2) {\n result.push({ x: numbers[i]!, y: numbers[i + 1]! })\n }\n return result\n}\n\nexport type RectShape = {\n x: number\n y: number\n width: number\n height: number\n rx?: number\n ry?: number\n}\n\n// SVG's rx/ry resolution: either one fills in for a missing other, and both\n// clamp to half the box so opposite corners never overlap.\nconst resolveCornerRadii = (\n width: number,\n height: number,\n rx: number | undefined,\n ry: number | undefined,\n): [number, number] => {\n const rawRx = rx ?? ry ?? 0\n const rawRy = ry ?? rx ?? 0\n return [\n Math.max(0, Math.min(rawRx, width / 2)),\n Math.max(0, Math.min(rawRy, height / 2)),\n ]\n}\n\nexport const rectToPathData = ({\n x,\n y,\n width,\n height,\n rx,\n ry,\n}: RectShape): string => {\n if (width <= 0 || height <= 0) {\n return \"\"\n }\n const [radiusX, radiusY] = resolveCornerRadii(width, height, rx, ry)\n if (radiusX <= 0 || radiusY <= 0) {\n return `M ${x} ${y} H ${x + width} V ${y + height} H ${x} Z`\n }\n const right = x + width\n const bottom = y + height\n return (\n `M ${x + radiusX} ${y} ` +\n `H ${right - radiusX} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${right} ${y + radiusY} ` +\n `V ${bottom - radiusY} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${right - radiusX} ${bottom} ` +\n `H ${x + radiusX} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${x} ${bottom - radiusY} ` +\n `V ${y + radiusY} ` +\n `A ${radiusX} ${radiusY} 0 0 1 ${x + radiusX} ${y} Z`\n )\n}\n\n// Two semicircle arcs — the standard trick for a full ellipse/circle in a\n// path grammar that has no dedicated \"ellipse\" command.\nexport const ellipseToPathData = (\n cx: number,\n cy: number,\n rx: number,\n ry: number,\n): string => {\n if (rx <= 0 || ry <= 0) {\n return \"\"\n }\n return (\n `M ${cx - rx} ${cy} ` +\n `A ${rx} ${ry} 0 1 0 ${cx + rx} ${cy} ` +\n `A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy} Z`\n )\n}\n\nexport const circleToPathData = (cx: number, cy: number, r: number): string =>\n ellipseToPathData(cx, cy, r, r)\n\nexport const lineToPathData = (\n x1: number,\n y1: number,\n x2: number,\n y2: number,\n): string => `M ${x1} ${y1} L ${x2} ${y2}`\n\nconst pointsToPathData = (points: Point[], close: boolean): string => {\n if (points.length === 0) {\n return \"\"\n }\n const [first, ...rest] = points\n const segments = rest.map((p) => `L ${p.x} ${p.y}`).join(\" \")\n const body =\n segments.length > 0\n ? `M ${first!.x} ${first!.y} ${segments}`\n : `M ${first!.x} ${first!.y}`\n return close ? `${body} Z` : body\n}\n\nexport const polygonToPathData = (points: string | Point[]): string =>\n pointsToPathData(\n typeof points === \"string\" ? parsePoints(points) : points,\n true,\n )\n\nexport const polylineToPathData = (points: string | Point[]): string =>\n pointsToPathData(\n typeof points === \"string\" ? parsePoints(points) : points,\n false,\n )\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type BoundingBox = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
export type GradientUnits = "objectBoundingBox" | "userSpaceOnUse";
|
|
8
|
+
export declare const resolveGradientPoint: (bounds: BoundingBox, fractionX: number, fractionY: number, units: GradientUnits) => {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const resolveGradientRadius: (bounds: BoundingBox, r: number, units: GradientUnits) => {
|
|
13
|
+
hradius: number;
|
|
14
|
+
vradius: number;
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Pure objectBoundingBox / userSpaceOnUse coordinate math for gradients — no
|
|
2
|
+
// gtkx imports, so it is unit-testable off Linux; the bridge
|
|
3
|
+
// (gtkx/bridge/svg-node.ts) feeds the results to Gsk's gradient nodes.
|
|
4
|
+
export const resolveGradientPoint = (bounds, fractionX, fractionY, units) => units === "userSpaceOnUse"
|
|
5
|
+
? { x: fractionX, y: fractionY }
|
|
6
|
+
: {
|
|
7
|
+
x: bounds.x + fractionX * bounds.width,
|
|
8
|
+
y: bounds.y + fractionY * bounds.height,
|
|
9
|
+
};
|
|
10
|
+
// SVG only has one radial radius; objectBoundingBox stretches it into an
|
|
11
|
+
// ellipse matching the shape's own aspect ratio (what browsers do too).
|
|
12
|
+
export const resolveGradientRadius = (bounds, r, units) => units === "userSpaceOnUse"
|
|
13
|
+
? { hradius: r, vradius: r }
|
|
14
|
+
: { hradius: r * bounds.width, vradius: r * bounds.height };
|
|
15
|
+
//# sourceMappingURL=gradient-geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradient-geometry.js","sourceRoot":"","sources":["../../src/svg/gradient-geometry.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6DAA6D;AAC7D,uEAAuE;AAUvE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAAmB,EACnB,SAAiB,EACjB,SAAiB,EACjB,KAAoB,EACM,EAAE,CAC5B,KAAK,KAAK,gBAAgB;IACxB,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE;IAChC,CAAC,CAAC;QACE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,KAAK;QACtC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM;KACxC,CAAA;AAEP,yEAAyE;AACzE,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,MAAmB,EACnB,CAAS,EACT,KAAoB,EACkB,EAAE,CACxC,KAAK,KAAK,gBAAgB;IACxB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;IAC5B,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAA","sourcesContent":["// Pure objectBoundingBox / userSpaceOnUse coordinate math for gradients — no\n// gtkx imports, so it is unit-testable off Linux; the bridge\n// (gtkx/bridge/svg-node.ts) feeds the results to Gsk's gradient nodes.\n\nexport type BoundingBox = {\n x: number\n y: number\n width: number\n height: number\n}\nexport type GradientUnits = \"objectBoundingBox\" | \"userSpaceOnUse\"\n\nexport const resolveGradientPoint = (\n bounds: BoundingBox,\n fractionX: number,\n fractionY: number,\n units: GradientUnits,\n): { x: number; y: number } =>\n units === \"userSpaceOnUse\"\n ? { x: fractionX, y: fractionY }\n : {\n x: bounds.x + fractionX * bounds.width,\n y: bounds.y + fractionY * bounds.height,\n }\n\n// SVG only has one radial radius; objectBoundingBox stretches it into an\n// ellipse matching the shape's own aspect ratio (what browsers do too).\nexport const resolveGradientRadius = (\n bounds: BoundingBox,\n r: number,\n units: GradientUnits,\n): { hradius: number; vradius: number } =>\n units === \"userSpaceOnUse\"\n ? { hradius: r, vradius: r }\n : { hradius: r * bounds.width, vradius: r * bounds.height }\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type SvgTransformOp = {
|
|
2
|
+
type: "translate";
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
} | {
|
|
6
|
+
type: "scale";
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
} | {
|
|
10
|
+
type: "rotate";
|
|
11
|
+
angleDeg: number;
|
|
12
|
+
} | {
|
|
13
|
+
type: "matrix";
|
|
14
|
+
a: number;
|
|
15
|
+
b: number;
|
|
16
|
+
c: number;
|
|
17
|
+
d: number;
|
|
18
|
+
e: number;
|
|
19
|
+
f: number;
|
|
20
|
+
};
|
|
21
|
+
/** Parses an SVG transform-list string left to right into primitive ops. */
|
|
22
|
+
export declare const parseSvgTransform: (value: string | undefined) => SvgTransformOp[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Pure parser for the SVG `transform` attribute's function-list syntax
|
|
2
|
+
// (translate/scale/rotate/matrix — no gtkx imports, unit-testable anywhere).
|
|
3
|
+
// `skewX`/`skewY` are a deliberate cut (see epic.md): nothing in the shapes
|
|
4
|
+
// this API ships needs them, and Gsk's skew() takes degrees on both axes
|
|
5
|
+
// while SVG's skewX/skewY are single-axis, different enough to not be a
|
|
6
|
+
// one-line addition later if it turns out to matter.
|
|
7
|
+
//
|
|
8
|
+
// `rotate(angle, cx, cy)` is expanded here into translate/rotate/translate
|
|
9
|
+
// so the bridge only ever has to apply four primitive ops, never carry
|
|
10
|
+
// pivot-point bookkeeping of its own.
|
|
11
|
+
const CALL_PATTERN = /([a-zA-Z]+)\s*\(([^)]*)\)/g;
|
|
12
|
+
const parseArgs = (raw) => raw
|
|
13
|
+
.trim()
|
|
14
|
+
.split(/[\s,]+/)
|
|
15
|
+
.filter((token) => token.length > 0)
|
|
16
|
+
.map(Number);
|
|
17
|
+
const isAllFinite = (values) => values.every((value) => Number.isFinite(value));
|
|
18
|
+
const buildOps = (name, args) => {
|
|
19
|
+
switch (name) {
|
|
20
|
+
case "translate": {
|
|
21
|
+
if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const [x, y = 0] = args;
|
|
25
|
+
return [{ type: "translate", x: x, y }];
|
|
26
|
+
}
|
|
27
|
+
case "scale": {
|
|
28
|
+
if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const [x, y = x] = args;
|
|
32
|
+
return [{ type: "scale", x: x, y: y }];
|
|
33
|
+
}
|
|
34
|
+
case "rotate": {
|
|
35
|
+
if ((args.length !== 1 && args.length !== 3) || !isAllFinite(args)) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const [angleDeg, cx, cy] = args;
|
|
39
|
+
if (cx === undefined || cy === undefined) {
|
|
40
|
+
return [{ type: "rotate", angleDeg: angleDeg }];
|
|
41
|
+
}
|
|
42
|
+
return [
|
|
43
|
+
{ type: "translate", x: cx, y: cy },
|
|
44
|
+
{ type: "rotate", angleDeg: angleDeg },
|
|
45
|
+
{ type: "translate", x: -cx, y: -cy },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
case "matrix": {
|
|
49
|
+
if (args.length !== 6 || !isAllFinite(args)) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const [a, b, c, d, e, f] = args;
|
|
53
|
+
return [{ type: "matrix", a: a, b: b, c: c, d: d, e: e, f: f }];
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
// skewX/skewY (deliberate cut) and anything unrecognized: ignored,
|
|
57
|
+
// same leniency RN-svg shows toward transform-list garbage.
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/** Parses an SVG transform-list string left to right into primitive ops. */
|
|
62
|
+
export const parseSvgTransform = (value) => {
|
|
63
|
+
if (!value) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const ops = [];
|
|
67
|
+
for (const match of value.matchAll(CALL_PATTERN)) {
|
|
68
|
+
const name = match[1];
|
|
69
|
+
const args = parseArgs(match[2] ?? "");
|
|
70
|
+
ops.push(...buildOps(name, args));
|
|
71
|
+
}
|
|
72
|
+
return ops;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/svg/transform.ts"],"names":[],"mappings":"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","sourcesContent":["// Pure parser for the SVG `transform` attribute's function-list syntax\n// (translate/scale/rotate/matrix — no gtkx imports, unit-testable anywhere).\n// `skewX`/`skewY` are a deliberate cut (see epic.md): nothing in the shapes\n// this API ships needs them, and Gsk's skew() takes degrees on both axes\n// while SVG's skewX/skewY are single-axis, different enough to not be a\n// one-line addition later if it turns out to matter.\n//\n// `rotate(angle, cx, cy)` is expanded here into translate/rotate/translate\n// so the bridge only ever has to apply four primitive ops, never carry\n// pivot-point bookkeeping of its own.\n\nexport type SvgTransformOp =\n | { type: \"translate\"; x: number; y: number }\n | { type: \"scale\"; x: number; y: number }\n | { type: \"rotate\"; angleDeg: number }\n | {\n type: \"matrix\"\n a: number\n b: number\n c: number\n d: number\n e: number\n f: number\n }\n\nconst CALL_PATTERN = /([a-zA-Z]+)\\s*\\(([^)]*)\\)/g\n\nconst parseArgs = (raw: string): number[] =>\n raw\n .trim()\n .split(/[\\s,]+/)\n .filter((token) => token.length > 0)\n .map(Number)\n\nconst isAllFinite = (values: number[]): boolean =>\n values.every((value) => Number.isFinite(value))\n\nconst buildOps = (name: string, args: number[]): SvgTransformOp[] => {\n switch (name) {\n case \"translate\": {\n if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {\n return []\n }\n const [x, y = 0] = args\n return [{ type: \"translate\", x: x!, y }]\n }\n case \"scale\": {\n if (args.length < 1 || args.length > 2 || !isAllFinite(args)) {\n return []\n }\n const [x, y = x] = args\n return [{ type: \"scale\", x: x!, y: y! }]\n }\n case \"rotate\": {\n if ((args.length !== 1 && args.length !== 3) || !isAllFinite(args)) {\n return []\n }\n const [angleDeg, cx, cy] = args\n if (cx === undefined || cy === undefined) {\n return [{ type: \"rotate\", angleDeg: angleDeg! }]\n }\n return [\n { type: \"translate\", x: cx, y: cy },\n { type: \"rotate\", angleDeg: angleDeg! },\n { type: \"translate\", x: -cx, y: -cy },\n ]\n }\n case \"matrix\": {\n if (args.length !== 6 || !isAllFinite(args)) {\n return []\n }\n const [a, b, c, d, e, f] = args\n return [{ type: \"matrix\", a: a!, b: b!, c: c!, d: d!, e: e!, f: f! }]\n }\n default:\n // skewX/skewY (deliberate cut) and anything unrecognized: ignored,\n // same leniency RN-svg shows toward transform-list garbage.\n return []\n }\n}\n\n/** Parses an SVG transform-list string left to right into primitive ops. */\nexport const parseSvgTransform = (\n value: string | undefined,\n): SvgTransformOp[] => {\n if (!value) {\n return []\n }\n const ops: SvgTransformOp[] = []\n for (const match of value.matchAll(CALL_PATTERN)) {\n const name = match[1]!\n const args = parseArgs(match[2] ?? \"\")\n ops.push(...buildOps(name, args))\n }\n return ops\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ViewBox = {
|
|
2
|
+
minX: number;
|
|
3
|
+
minY: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const parseViewBox: (value: string | undefined) => ViewBox | null;
|
|
8
|
+
export type AlignX = "min" | "mid" | "max";
|
|
9
|
+
export type AlignY = "min" | "mid" | "max";
|
|
10
|
+
export type MeetOrSlice = "meet" | "slice";
|
|
11
|
+
export type PreserveAspectRatio = {
|
|
12
|
+
align: "none" | {
|
|
13
|
+
x: AlignX;
|
|
14
|
+
y: AlignY;
|
|
15
|
+
};
|
|
16
|
+
meetOrSlice: MeetOrSlice;
|
|
17
|
+
};
|
|
18
|
+
export declare const DEFAULT_PRESERVE_ASPECT_RATIO: PreserveAspectRatio;
|
|
19
|
+
export declare const parsePreserveAspectRatio: (value: string | undefined) => PreserveAspectRatio;
|
|
20
|
+
export type ViewBoxTransform = {
|
|
21
|
+
scaleX: number;
|
|
22
|
+
scaleY: number;
|
|
23
|
+
translateX: number;
|
|
24
|
+
translateY: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Maps viewBox user-space coordinates onto a `viewportWidth` x
|
|
28
|
+
* `viewportHeight` box: `screen = user * scale + translate`. Mirrors the SVG
|
|
29
|
+
* algorithm exactly (see spec §7.11) — "none" stretches independently per
|
|
30
|
+
* axis, "meet" fits the whole viewBox inside the viewport (letterboxing),
|
|
31
|
+
* "slice" fills the viewport and overflows the viewBox (the bridge clips to
|
|
32
|
+
* the viewport bounds, same as a real SVG renderer).
|
|
33
|
+
*/
|
|
34
|
+
export declare const resolveViewBoxTransform: (viewBox: ViewBox | null, viewportWidth: number, viewportHeight: number, preserveAspectRatio?: PreserveAspectRatio) => ViewBoxTransform;
|