react-native-gtkx 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/animated/composite.d.ts +5 -0
- package/dist/animated/composite.js +132 -0
- package/dist/animated/composite.js.map +1 -0
- package/dist/animated/create-animated.d.ts +12 -0
- package/dist/animated/create-animated.js +17 -0
- package/dist/animated/create-animated.js.map +1 -0
- package/dist/animated/easing.d.ts +11 -0
- package/dist/animated/easing.js +91 -0
- package/dist/animated/easing.js.map +1 -0
- package/dist/animated/frame-loop.d.ts +6 -0
- package/dist/animated/frame-loop.js +25 -0
- package/dist/animated/frame-loop.js.map +1 -0
- package/dist/animated/index.d.ts +6 -0
- package/dist/animated/index.js +7 -0
- package/dist/animated/index.js.map +1 -0
- package/dist/animated/interpolate.d.ts +2 -0
- package/dist/animated/interpolate.js +90 -0
- package/dist/animated/interpolate.js.map +1 -0
- package/dist/animated/native-driver.d.ts +1 -0
- package/dist/animated/native-driver.js +17 -0
- package/dist/animated/native-driver.js.map +1 -0
- package/dist/animated/spring.d.ts +3 -0
- package/dist/animated/spring.js +67 -0
- package/dist/animated/spring.js.map +1 -0
- package/dist/animated/timing.d.ts +3 -0
- package/dist/animated/timing.js +30 -0
- package/dist/animated/timing.js.map +1 -0
- package/dist/animated/types.d.ts +51 -0
- package/dist/animated/types.js +6 -0
- package/dist/animated/types.js.map +1 -0
- package/dist/animated/value-animation.d.ts +9 -0
- package/dist/animated/value-animation.js +65 -0
- package/dist/animated/value-animation.js.map +1 -0
- package/dist/animated/value.d.ts +46 -0
- package/dist/animated/value.js +130 -0
- package/dist/animated/value.js.map +1 -0
- package/dist/apis/alert.d.ts +16 -0
- package/dist/apis/alert.js +33 -0
- package/dist/apis/alert.js.map +1 -0
- package/dist/apis/app-state.d.ts +11 -0
- package/dist/apis/app-state.js +54 -0
- package/dist/apis/app-state.js.map +1 -0
- package/dist/apis/appearance.d.ts +12 -0
- package/dist/apis/appearance.js +33 -0
- package/dist/apis/appearance.js.map +1 -0
- package/dist/apis/dev-settings.d.ts +9 -0
- package/dist/apis/dev-settings.js +25 -0
- package/dist/apis/dev-settings.js.map +1 -0
- package/dist/apis/dimensions.d.ts +14 -0
- package/dist/apis/dimensions.js +80 -0
- package/dist/apis/dimensions.js.map +1 -0
- package/dist/apis/emitter.d.ts +8 -0
- package/dist/apis/emitter.js +35 -0
- package/dist/apis/emitter.js.map +1 -0
- package/dist/apis/hooks.d.ts +11 -0
- package/dist/apis/hooks.js +29 -0
- package/dist/apis/hooks.js.map +1 -0
- package/dist/apis/host.d.ts +45 -0
- package/dist/apis/host.gtkx.d.ts +2 -0
- package/dist/apis/host.gtkx.js +301 -0
- package/dist/apis/host.gtkx.js.map +1 -0
- package/dist/apis/host.js +6 -0
- package/dist/apis/host.js.map +1 -0
- package/dist/apis/index.d.ts +39 -0
- package/dist/apis/index.js +21 -0
- package/dist/apis/index.js.map +1 -0
- package/dist/apis/linking.d.ts +7 -0
- package/dist/apis/linking.js +28 -0
- package/dist/apis/linking.js.map +1 -0
- package/dist/apis/platform.d.ts +14 -0
- package/dist/apis/platform.js +23 -0
- package/dist/apis/platform.js.map +1 -0
- package/dist/components/activity-indicator.d.ts +10 -0
- package/dist/components/activity-indicator.js +21 -0
- package/dist/components/activity-indicator.js.map +1 -0
- package/dist/components/animated.d.ts +34 -0
- package/dist/components/animated.js +152 -0
- package/dist/components/animated.js.map +1 -0
- package/dist/components/app-registry.d.ts +14 -0
- package/dist/components/app-registry.js +61 -0
- package/dist/components/app-registry.js.map +1 -0
- package/dist/components/flat-list.d.ts +48 -0
- package/dist/components/flat-list.js +23 -0
- package/dist/components/flat-list.js.map +1 -0
- package/dist/components/host-node.d.ts +10 -0
- package/dist/components/host-node.js +10 -0
- package/dist/components/host-node.js.map +1 -0
- package/dist/components/image-loader.d.ts +17 -0
- package/dist/components/image-loader.js +54 -0
- package/dist/components/image-loader.js.map +1 -0
- package/dist/components/image.d.ts +19 -0
- package/dist/components/image.js +73 -0
- package/dist/components/image.js.map +1 -0
- package/dist/components/index.d.ts +14 -0
- package/dist/components/index.js +14 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/modal.d.ts +12 -0
- package/dist/components/modal.js +18 -0
- package/dist/components/modal.js.map +1 -0
- package/dist/components/pressable.d.ts +33 -0
- package/dist/components/pressable.js +116 -0
- package/dist/components/pressable.js.map +1 -0
- package/dist/components/rect-store.d.ts +17 -0
- package/dist/components/rect-store.js +40 -0
- package/dist/components/rect-store.js.map +1 -0
- package/dist/components/root.d.ts +8 -0
- package/dist/components/root.js +59 -0
- package/dist/components/root.js.map +1 -0
- package/dist/components/scroll-view.d.ts +56 -0
- package/dist/components/scroll-view.js +324 -0
- package/dist/components/scroll-view.js.map +1 -0
- package/dist/components/switch.d.ts +11 -0
- package/dist/components/switch.js +20 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/text-input.d.ts +23 -0
- package/dist/components/text-input.js +151 -0
- package/dist/components/text-input.js.map +1 -0
- package/dist/components/text.d.ts +11 -0
- package/dist/components/text.js +100 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/use-layout-child.d.ts +36 -0
- package/dist/components/use-layout-child.js +177 -0
- package/dist/components/use-layout-child.js.map +1 -0
- package/dist/components/view.d.ts +13 -0
- package/dist/components/view.js +60 -0
- package/dist/components/view.js.map +1 -0
- package/dist/components/virtualized-list.d.ts +67 -0
- package/dist/components/virtualized-list.js +519 -0
- package/dist/components/virtualized-list.js.map +1 -0
- package/dist/contracts.d.ts +119 -0
- package/dist/contracts.js +5 -0
- package/dist/contracts.js.map +1 -0
- package/dist/gtkx/bridge/index.d.ts +16 -0
- package/dist/gtkx/bridge/index.js +25 -0
- package/dist/gtkx/bridge/index.js.map +1 -0
- package/dist/gtkx/bridge/layout-manager.d.ts +11 -0
- package/dist/gtkx/bridge/layout-manager.js +87 -0
- package/dist/gtkx/bridge/layout-manager.js.map +1 -0
- package/dist/gtkx/bridge/measure.d.ts +9 -0
- package/dist/gtkx/bridge/measure.js +20 -0
- package/dist/gtkx/bridge/measure.js.map +1 -0
- package/dist/gtkx/bridge/theme.d.ts +4 -0
- package/dist/gtkx/bridge/theme.js +5 -0
- package/dist/gtkx/bridge/theme.js.map +1 -0
- package/dist/gtkx/bridge/view-box.d.ts +7 -0
- package/dist/gtkx/bridge/view-box.js +40 -0
- package/dist/gtkx/bridge/view-box.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/apply-style.d.ts +4 -0
- package/dist/layout/apply-style.js +148 -0
- package/dist/layout/apply-style.js.map +1 -0
- package/dist/layout/engine.d.ts +22 -0
- package/dist/layout/engine.js +90 -0
- package/dist/layout/engine.js.map +1 -0
- package/dist/layout/index.d.ts +3 -0
- package/dist/layout/index.js +4 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/node.d.ts +33 -0
- package/dist/layout/node.js +151 -0
- package/dist/layout/node.js.map +1 -0
- package/dist/layout/yoga.d.ts +13 -0
- package/dist/layout/yoga.js +52 -0
- package/dist/layout/yoga.js.map +1 -0
- package/dist/metro/index.d.ts +42 -0
- package/dist/metro/index.js +119 -0
- package/dist/metro/index.js.map +1 -0
- package/dist/runner/host-dev.d.ts +1 -0
- package/dist/runner/host-dev.js +208 -0
- package/dist/runner/host-dev.js.map +1 -0
- package/dist/runner/host.d.ts +1 -0
- package/dist/runner/host.js +101 -0
- package/dist/runner/host.js.map +1 -0
- package/dist/runner/index.d.ts +26 -0
- package/dist/runner/index.js +179 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/style/colors.d.ts +17 -0
- package/dist/style/colors.js +375 -0
- package/dist/style/colors.js.map +1 -0
- package/dist/style/dev-warning.d.ts +2 -0
- package/dist/style/dev-warning.js +18 -0
- package/dist/style/dev-warning.js.map +1 -0
- package/dist/style/index.d.ts +7 -0
- package/dist/style/index.js +14 -0
- package/dist/style/index.js.map +1 -0
- package/dist/style/registry.d.ts +12 -0
- package/dist/style/registry.gtkx.d.ts +2 -0
- package/dist/style/registry.gtkx.js +9 -0
- package/dist/style/registry.gtkx.js.map +1 -0
- package/dist/style/registry.js +23 -0
- package/dist/style/registry.js.map +1 -0
- package/dist/style/split-style.d.ts +6 -0
- package/dist/style/split-style.js +107 -0
- package/dist/style/split-style.js.map +1 -0
- package/dist/style/style-sheet.d.ts +43 -0
- package/dist/style/style-sheet.js +57 -0
- package/dist/style/style-sheet.js.map +1 -0
- package/dist/style/text-align.d.ts +7 -0
- package/dist/style/text-align.js +22 -0
- package/dist/style/text-align.js.map +1 -0
- package/dist/style/visual-css.d.ts +6 -0
- package/dist/style/visual-css.js +133 -0
- package/dist/style/visual-css.js.map +1 -0
- package/dist/vite/index.d.ts +53 -0
- package/dist/vite/index.js +127 -0
- package/dist/vite/index.js.map +1 -0
- package/package.json +88 -0
- package/react-native.config.js +21 -0
- package/types.d.ts +43 -0
- package/types.js +4 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { LayoutEngine } from "../layout/index";
|
|
4
|
+
import { GtkBox, queueResize } from "../gtkx/bridge/index";
|
|
5
|
+
import { HostNodeContext } from "./host-node";
|
|
6
|
+
import { beginAllocatePass, endAllocatePass } from "./rect-store";
|
|
7
|
+
import { useRnContainer } from "./use-layout-child";
|
|
8
|
+
// Layout root: one engine per window (or per test harness). The root GtkBox
|
|
9
|
+
// reports the engine viewport as its measure (via RnGtkxLayout) and is sized
|
|
10
|
+
// by its parent (window/harness); the owner keeps the engine viewport in sync
|
|
11
|
+
// (AppRegistry does it from window-size changes).
|
|
12
|
+
export const Root = ({ width, height, followAllocation = false, children, }) => {
|
|
13
|
+
const widgetRef = useRef(null);
|
|
14
|
+
// useState lazy init — see the note in use-layout-child.ts (React Compiler).
|
|
15
|
+
const [engine] = useState(() => new LayoutEngine({ width, height }));
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
if (followAllocation) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
engine.setViewport({ width, height });
|
|
21
|
+
// The viewport is this widget's measure — invalidate the cached one.
|
|
22
|
+
const widget = widgetRef.current;
|
|
23
|
+
if (widget) {
|
|
24
|
+
queueResize(widget);
|
|
25
|
+
}
|
|
26
|
+
}, [engine, followAllocation, width, height]);
|
|
27
|
+
useEffect(() => () => {
|
|
28
|
+
engine.dispose();
|
|
29
|
+
}, [engine]);
|
|
30
|
+
const host = useMemo(() => ({ engine, node: engine.root, widgetRef }), [engine]);
|
|
31
|
+
useRnContainer(widgetRef, engine.root, followAllocation
|
|
32
|
+
? {
|
|
33
|
+
measure: () => 0,
|
|
34
|
+
beforeAllocate: (allocatedWidth, allocatedHeight) => {
|
|
35
|
+
const current = engine.root.getRect();
|
|
36
|
+
if (current &&
|
|
37
|
+
current.width === allocatedWidth &&
|
|
38
|
+
current.height === allocatedHeight) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Synchronous reflow inside the allocation pass: commits fill the
|
|
42
|
+
// rect store now; their GTK queue calls are deferred past the pass.
|
|
43
|
+
beginAllocatePass();
|
|
44
|
+
try {
|
|
45
|
+
engine.setViewport({
|
|
46
|
+
width: allocatedWidth,
|
|
47
|
+
height: allocatedHeight,
|
|
48
|
+
});
|
|
49
|
+
engine.flushSync();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
endAllocatePass();
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
: undefined);
|
|
57
|
+
return (_jsx(GtkBox, { ref: widgetRef, children: _jsx(HostNodeContext.Provider, { value: host, children: children }) }));
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=root.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.js","sourceRoot":"","sources":["../../src/components/root.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,SAAS,EACT,eAAe,EACf,OAAO,EACP,MAAM,EACN,QAAQ,GAET,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAY,MAAM,sBAAsB,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAYnD,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,kDAAkD;AAClD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,KAAK,EACL,MAAM,EACN,gBAAgB,GAAG,KAAK,EACxB,QAAQ,GACE,EAAE,EAAE;IACd,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAE9C,6EAA6E;IAC7E,MAAM,CAAC,MAAM,CAAC,GAAG,QAAQ,CACvB,GAAG,EAAE,CAAC,IAAI,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAC1C,CAAA;IAED,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,gBAAgB,EAAE,CAAC;YACrB,OAAM;QACR,CAAC;QACD,MAAM,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACrC,qEAAqE;QACrE,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,MAAM,EAAE,CAAC;YACX,WAAW,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAE7C,SAAS,CACP,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,MAAM,CAAC,OAAO,EAAE,CAAA;IAClB,CAAC,EACD,CAAC,MAAM,CAAC,CACT,CAAA;IAED,MAAM,IAAI,GAAG,OAAO,CAClB,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAChD,CAAC,MAAM,CAAC,CACT,CAAA;IAED,cAAc,CACZ,SAAS,EACT,MAAM,CAAC,IAAI,EACX,gBAAgB;QACd,CAAC,CAAC;YACE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;YAChB,cAAc,EAAE,CAAC,cAAc,EAAE,eAAe,EAAE,EAAE;gBAClD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAA;gBACrC,IACE,OAAO;oBACP,OAAO,CAAC,KAAK,KAAK,cAAc;oBAChC,OAAO,CAAC,MAAM,KAAK,eAAe,EAClC,CAAC;oBACD,OAAM;gBACR,CAAC;gBACD,kEAAkE;gBAClE,oEAAoE;gBACpE,iBAAiB,EAAE,CAAA;gBACnB,IAAI,CAAC;oBACH,MAAM,CAAC,WAAW,CAAC;wBACjB,KAAK,EAAE,cAAc;wBACrB,MAAM,EAAE,eAAe;qBACxB,CAAC,CAAA;oBACF,MAAM,CAAC,SAAS,EAAE,CAAA;gBACpB,CAAC;wBAAS,CAAC;oBACT,eAAe,EAAE,CAAA;gBACnB,CAAC;YACH,CAAC;SACF;QACH,CAAC,CAAC,SAAS,CACd,CAAA;IAED,OAAO,CACL,KAAC,MAAM,IAAC,GAAG,EAAE,SAAS,YACpB,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,IAAI,YAClC,QAAQ,GACgB,GACpB,CACV,CAAA;AACH,CAAC,CAAA","sourcesContent":["import {\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\"\nimport { LayoutEngine } from \"../layout/index\"\nimport { GtkBox, queueResize, type Gtk } from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport { beginAllocatePass, endAllocatePass } from \"./rect-store\"\nimport { useRnContainer } from \"./use-layout-child\"\n\nexport type RootProps = {\n width: number\n height: number\n // Window mode: report a zero minimum (the window may shrink freely) and\n // adopt whatever the window actually allocates as the engine viewport —\n // the allocation IS the layout viewport, headerbar excluded by GTK itself.\n followAllocation?: boolean\n children?: ReactNode\n}\n\n// Layout root: one engine per window (or per test harness). The root GtkBox\n// reports the engine viewport as its measure (via RnGtkxLayout) and is sized\n// by its parent (window/harness); the owner keeps the engine viewport in sync\n// (AppRegistry does it from window-size changes).\nexport const Root = ({\n width,\n height,\n followAllocation = false,\n children,\n}: RootProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n\n // useState lazy init — see the note in use-layout-child.ts (React Compiler).\n const [engine] = useState<LayoutEngine>(\n () => new LayoutEngine({ width, height }),\n )\n\n useLayoutEffect(() => {\n if (followAllocation) {\n return\n }\n engine.setViewport({ width, height })\n // The viewport is this widget's measure — invalidate the cached one.\n const widget = widgetRef.current\n if (widget) {\n queueResize(widget)\n }\n }, [engine, followAllocation, width, height])\n\n useEffect(\n () => () => {\n engine.dispose()\n },\n [engine],\n )\n\n const host = useMemo(\n () => ({ engine, node: engine.root, widgetRef }),\n [engine],\n )\n\n useRnContainer(\n widgetRef,\n engine.root,\n followAllocation\n ? {\n measure: () => 0,\n beforeAllocate: (allocatedWidth, allocatedHeight) => {\n const current = engine.root.getRect()\n if (\n current &&\n current.width === allocatedWidth &&\n current.height === allocatedHeight\n ) {\n return\n }\n // Synchronous reflow inside the allocation pass: commits fill the\n // rect store now; their GTK queue calls are deferred past the pass.\n beginAllocatePass()\n try {\n engine.setViewport({\n width: allocatedWidth,\n height: allocatedHeight,\n })\n engine.flushSync()\n } finally {\n endAllocatePass()\n }\n },\n }\n : undefined,\n )\n\n return (\n <GtkBox ref={widgetRef}>\n <HostNodeContext.Provider value={host}>\n {children}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { StyleProp } from "../contracts";
|
|
3
|
+
import { Gtk } from "../gtkx/bridge/index";
|
|
4
|
+
import { type LayoutEvent } from "./use-layout-child";
|
|
5
|
+
export type ScrollViewHandle = {
|
|
6
|
+
scrollTo(options: {
|
|
7
|
+
x?: number;
|
|
8
|
+
y?: number;
|
|
9
|
+
animated?: boolean;
|
|
10
|
+
}): void;
|
|
11
|
+
scrollToEnd(options?: {
|
|
12
|
+
animated?: boolean;
|
|
13
|
+
}): void;
|
|
14
|
+
};
|
|
15
|
+
export type ScrollEvent = {
|
|
16
|
+
nativeEvent: {
|
|
17
|
+
contentOffset: {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
};
|
|
21
|
+
contentSize: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export type ScrollViewProps = {
|
|
28
|
+
style?: StyleProp;
|
|
29
|
+
contentContainerStyle?: StyleProp;
|
|
30
|
+
horizontal?: boolean;
|
|
31
|
+
stickyHeaderIndices?: readonly number[];
|
|
32
|
+
onScroll?: (event: ScrollEvent) => void;
|
|
33
|
+
onContentSizeChange?: (width: number, height: number) => void;
|
|
34
|
+
onLayout?: (event: LayoutEvent) => void;
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
testID?: string;
|
|
37
|
+
};
|
|
38
|
+
type StickyRecord = {
|
|
39
|
+
y: number;
|
|
40
|
+
height: number;
|
|
41
|
+
widget: Gtk.Widget;
|
|
42
|
+
};
|
|
43
|
+
export type StickyRegistry = {
|
|
44
|
+
register(key: string, record: StickyRecord): void;
|
|
45
|
+
unregister(key: string): void;
|
|
46
|
+
};
|
|
47
|
+
export declare const StickyRegistryContext: React.Context<StickyRegistry | null>;
|
|
48
|
+
export declare const StickySlot: ({ stickyKey, style, top, onLayout, children, }: {
|
|
49
|
+
stickyKey: string;
|
|
50
|
+
style?: StyleProp;
|
|
51
|
+
top?: number;
|
|
52
|
+
onLayout?: (event: LayoutEvent) => void;
|
|
53
|
+
children?: ReactNode;
|
|
54
|
+
}) => React.JSX.Element;
|
|
55
|
+
export declare const ScrollView: React.ForwardRefExoticComponent<ScrollViewProps & React.RefAttributes<ScrollViewHandle>>;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Children, createContext, forwardRef, useContext, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
|
|
3
|
+
import { splitStyle, StyleSheet } from "../style/index";
|
|
4
|
+
import { Gtk, GtkBox, GtkScrolledWindow, queueAllocate, queueResize, useSignal, } from "../gtkx/bridge/index";
|
|
5
|
+
import { HostNodeContext } from "./host-node";
|
|
6
|
+
import { deferDuringAllocate, setStoredOffset } from "./rect-store";
|
|
7
|
+
import { useLayoutChild, useRnContainer, } from "./use-layout-child";
|
|
8
|
+
export const StickyRegistryContext = createContext(null);
|
|
9
|
+
// A wrapper around a sticky child: a plain container that reports its layout
|
|
10
|
+
// and widget into the registry, so the scroll handler can translate/reorder
|
|
11
|
+
// the REAL instance (RN semantics — no duplicate is ever rendered, external
|
|
12
|
+
// margins travel with the header exactly as designed). An explicit `top`
|
|
13
|
+
// (windowed cells) overrides the measured position.
|
|
14
|
+
export const StickySlot = ({ stickyKey, style, top, onLayout, children, }) => {
|
|
15
|
+
const registry = useContext(StickyRegistryContext);
|
|
16
|
+
const widgetRef = useRef(null);
|
|
17
|
+
const { host, node } = useLayoutChild(widgetRef, {
|
|
18
|
+
style,
|
|
19
|
+
onLayout: (event) => {
|
|
20
|
+
onLayout?.(event);
|
|
21
|
+
const widget = widgetRef.current;
|
|
22
|
+
if (registry && widget) {
|
|
23
|
+
registry.register(stickyKey, {
|
|
24
|
+
y: top ?? event.nativeEvent.layout.y,
|
|
25
|
+
height: event.nativeEvent.layout.height,
|
|
26
|
+
widget,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
useRnContainer(widgetRef, node);
|
|
32
|
+
useLayoutEffect(() => {
|
|
33
|
+
return () => {
|
|
34
|
+
registry?.unregister(stickyKey);
|
|
35
|
+
};
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, [stickyKey]);
|
|
38
|
+
return (_jsx(GtkBox, { ref: widgetRef, children: _jsx(HostNodeContext.Provider, { value: { engine: host.engine, node, widgetRef }, children: children }) }));
|
|
39
|
+
};
|
|
40
|
+
// GtkScrolledWindow whose child is a content GtkBox backed by its own Yoga
|
|
41
|
+
// node inside the same engine: the content's RnGtkxLayout measures the engine
|
|
42
|
+
// content size (which grows past the viewport — that IS the scroll range;
|
|
43
|
+
// overflow: scroll on the outer node) and the scrolled window pans it.
|
|
44
|
+
export const ScrollView = forwardRef(({ style, contentContainerStyle, horizontal = false, stickyHeaderIndices, onScroll, onContentSizeChange, onLayout, children, testID, }, handleRef) => {
|
|
45
|
+
const outerRef = useRef(null);
|
|
46
|
+
const contentRef = useRef(null);
|
|
47
|
+
const [scrolled, setScrolled] = useState(null);
|
|
48
|
+
const { host, node: outerNode } = useLayoutChild(outerRef, {
|
|
49
|
+
// Yoga unconstrains a scroll node's MAIN axis — align it with the
|
|
50
|
+
// scroll direction or a horizontal list's content clamps to the
|
|
51
|
+
// viewport width (hadjustment upper == page).
|
|
52
|
+
style: [
|
|
53
|
+
style,
|
|
54
|
+
{ overflow: "scroll", flexDirection: horizontal ? "row" : "column" },
|
|
55
|
+
],
|
|
56
|
+
onLayout,
|
|
57
|
+
});
|
|
58
|
+
const [contentNode] = useState(() => host.engine.createNode());
|
|
59
|
+
// Sticky positions are computed INSIDE the same allocation pass that
|
|
60
|
+
// places the children (beforeAllocate): the viewport has already settled
|
|
61
|
+
// on this frame's scroll offset, so the pinned header lands exactly —
|
|
62
|
+
// reacting to the adjustment signal instead lags a frame and jitters.
|
|
63
|
+
useRnContainer(contentRef, contentNode, {
|
|
64
|
+
beforeAllocate: () => {
|
|
65
|
+
if (stickyRecords.current.size > 0) {
|
|
66
|
+
updateSticky(outerRef.current?.getVadjustment()?.getValue() ?? 0);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// RN semantics: a ScrollView CLIPS its content. Branch B made containers
|
|
71
|
+
// paint-overflow by default, so without this the scrolled-away content
|
|
72
|
+
// (e.g. the original of a pinned sticky header) keeps drawing past the
|
|
73
|
+
// scroll area's edges.
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
|
+
outerRef.current?.setOverflow(Gtk.Overflow.HIDDEN);
|
|
76
|
+
}, []);
|
|
77
|
+
useLayoutEffect(() => {
|
|
78
|
+
outerNode.insertChild(contentNode, 0);
|
|
79
|
+
contentNode.setCommit(() => {
|
|
80
|
+
// The content size IS this widget's measure (scroll range) — the
|
|
81
|
+
// ScrolledWindow itself allocates the content widget.
|
|
82
|
+
const widget = contentRef.current;
|
|
83
|
+
if (!widget) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const queue = () => {
|
|
87
|
+
queueResize(widget);
|
|
88
|
+
};
|
|
89
|
+
if (!deferDuringAllocate(widget, queue)) {
|
|
90
|
+
queue();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return () => {
|
|
94
|
+
contentNode.setCommit(null);
|
|
95
|
+
outerNode.removeChild(contentNode);
|
|
96
|
+
contentNode.free();
|
|
97
|
+
};
|
|
98
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
+
}, []);
|
|
100
|
+
const contentLayout = splitStyle(StyleSheet.flatten(contentContainerStyle)).layout;
|
|
101
|
+
const contentKey = JSON.stringify({ horizontal, contentLayout });
|
|
102
|
+
useLayoutEffect(() => {
|
|
103
|
+
contentNode.setStyle({
|
|
104
|
+
flexDirection: horizontal ? "row" : "column",
|
|
105
|
+
alignItems: "flex-start",
|
|
106
|
+
...contentLayout,
|
|
107
|
+
});
|
|
108
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
109
|
+
}, [contentNode, contentKey]);
|
|
110
|
+
useImperativeHandle(handleRef, () => ({
|
|
111
|
+
scrollTo({ x, y }) {
|
|
112
|
+
const widget = outerRef.current;
|
|
113
|
+
if (!widget) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (y !== undefined) {
|
|
117
|
+
widget.getVadjustment()?.setValue(y);
|
|
118
|
+
}
|
|
119
|
+
if (x !== undefined) {
|
|
120
|
+
widget.getHadjustment()?.setValue(x);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
scrollToEnd() {
|
|
124
|
+
const widget = outerRef.current;
|
|
125
|
+
const adjustment = horizontal
|
|
126
|
+
? widget?.getHadjustment()
|
|
127
|
+
: widget?.getVadjustment();
|
|
128
|
+
if (adjustment) {
|
|
129
|
+
adjustment.setValue(adjustment.getUpper() - adjustment.getPageSize());
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
}));
|
|
133
|
+
// --- sticky headers -------------------------------------------------
|
|
134
|
+
// The RN model, faithfully: the REAL child is translated (no duplicate,
|
|
135
|
+
// state preserved). RN gives the pinned cell a zIndex; GTK's z-order is
|
|
136
|
+
// sibling paint order, so the active slot is reordered to be the LAST
|
|
137
|
+
// content child while pinned and restored afterwards. Per-frame movement
|
|
138
|
+
// goes through the rect-store offset fast path — zero React work while
|
|
139
|
+
// scrolling.
|
|
140
|
+
const stickySet = stickyHeaderIndices ?? [];
|
|
141
|
+
const stickyRecords = useRef(new Map());
|
|
142
|
+
const restoreSibling = useRef(new Map());
|
|
143
|
+
const activeStickyRef = useRef(null);
|
|
144
|
+
const [hasSticky, setHasSticky] = useState(false);
|
|
145
|
+
const stickyRegistry = useRef({
|
|
146
|
+
register: (key, record) => {
|
|
147
|
+
stickyRecords.current.set(key, record);
|
|
148
|
+
setHasSticky(true);
|
|
149
|
+
const parent = record.widget.getParent();
|
|
150
|
+
if (parent) {
|
|
151
|
+
queueAllocate(parent);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
unregister: (key) => {
|
|
155
|
+
stickyRecords.current.delete(key);
|
|
156
|
+
restoreSibling.current.delete(key);
|
|
157
|
+
if (activeStickyRef.current === key) {
|
|
158
|
+
activeStickyRef.current = null;
|
|
159
|
+
}
|
|
160
|
+
if (stickyRecords.current.size === 0) {
|
|
161
|
+
setHasSticky(false);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
const updateSticky = (rawScrollTop) => {
|
|
166
|
+
if (stickyRecords.current.size === 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// The viewport translates the content by the FRACTIONAL adjustment
|
|
170
|
+
// value while widget allocations are integer — an unquantized pin
|
|
171
|
+
// disagrees with the translation by ±1px and shimmers every frame.
|
|
172
|
+
// Frame telemetry (docs/research/sticky-probe.md) proved the viewport quantizes
|
|
173
|
+
// like floor: with Math.floor the header's window-relative position is
|
|
174
|
+
// a flat 0.00 across every frame; round oscillates 0/1, ceil sits at 1.
|
|
175
|
+
const scrollTop = Math.floor(rawScrollTop);
|
|
176
|
+
let active = null;
|
|
177
|
+
let next = null;
|
|
178
|
+
for (const [key, record] of stickyRecords.current) {
|
|
179
|
+
if (record.y <= scrollTop) {
|
|
180
|
+
if (active === null ||
|
|
181
|
+
record.y >= stickyRecords.current.get(active).y) {
|
|
182
|
+
active = key;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (next === null || record.y < next.y) {
|
|
186
|
+
next = record;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const previous = activeStickyRef.current;
|
|
190
|
+
if (previous !== active) {
|
|
191
|
+
activeStickyRef.current = active;
|
|
192
|
+
if (previous !== null) {
|
|
193
|
+
const record = stickyRecords.current.get(previous);
|
|
194
|
+
if (record) {
|
|
195
|
+
setStoredOffset(record.widget, 0, 0);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Reordering widgets queues GTK work — not allowed mid-allocation.
|
|
199
|
+
// The z-switch lands a frame later; the POSITION is already exact.
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
applyStickyReorder(previous, active);
|
|
202
|
+
}, 0);
|
|
203
|
+
}
|
|
204
|
+
if (active !== null) {
|
|
205
|
+
const record = stickyRecords.current.get(active);
|
|
206
|
+
// Pin at the viewport top; the next sticky header pushes it out.
|
|
207
|
+
const pinned = next
|
|
208
|
+
? Math.min(scrollTop, next.y - record.height)
|
|
209
|
+
: scrollTop;
|
|
210
|
+
setStoredOffset(record.widget, 0, Math.max(0, pinned - record.y));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const applyStickyReorder = (previous, active) => {
|
|
214
|
+
if (previous !== null) {
|
|
215
|
+
const record = stickyRecords.current.get(previous);
|
|
216
|
+
// Slot parents are always our GtkBox containers.
|
|
217
|
+
const parent = record?.widget.getParent();
|
|
218
|
+
if (record && parent) {
|
|
219
|
+
// Put the slot back where the reconciler expects it.
|
|
220
|
+
parent.reorderChildAfter(record.widget, restoreSibling.current.get(previous) ?? null);
|
|
221
|
+
queueAllocate(parent);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (active !== null) {
|
|
225
|
+
const record = stickyRecords.current.get(active);
|
|
226
|
+
const parent = record?.widget.getParent();
|
|
227
|
+
if (record && parent) {
|
|
228
|
+
restoreSibling.current.set(active, record.widget.getPrevSibling());
|
|
229
|
+
parent.reorderChildAfter(record.widget, parent.getLastChild());
|
|
230
|
+
queueAllocate(parent);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const emitScroll = () => {
|
|
235
|
+
if (!onScroll) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const widget = outerRef.current;
|
|
239
|
+
const contentRect = contentNode.getRect();
|
|
240
|
+
if (!widget || !contentRect) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
onScroll({
|
|
244
|
+
nativeEvent: {
|
|
245
|
+
contentOffset: {
|
|
246
|
+
x: widget.getHadjustment()?.getValue() ?? 0,
|
|
247
|
+
y: widget.getVadjustment()?.getValue() ?? 0,
|
|
248
|
+
},
|
|
249
|
+
contentSize: { width: contentRect.width, height: contentRect.height },
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
const adjustment = horizontal
|
|
254
|
+
? (scrolled?.getHadjustment() ?? null)
|
|
255
|
+
: (scrolled?.getVadjustment() ?? null);
|
|
256
|
+
const onAdjustment = () => {
|
|
257
|
+
emitScroll();
|
|
258
|
+
};
|
|
259
|
+
// Content-size reports dedupe on the engine rect: "changed" also fires
|
|
260
|
+
// for pure viewport (page-size) changes, which RN does not report.
|
|
261
|
+
const lastContentSize = useRef({ width: -1, height: -1 });
|
|
262
|
+
const onRangeChanged = () => {
|
|
263
|
+
if (!onContentSizeChange) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const rect = contentNode.getRect();
|
|
267
|
+
if (!rect) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const last = lastContentSize.current;
|
|
271
|
+
if (rect.width === last.width && rect.height === last.height) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
lastContentSize.current = { width: rect.width, height: rect.height };
|
|
275
|
+
onContentSizeChange(rect.width, rect.height);
|
|
276
|
+
};
|
|
277
|
+
// Frame-synced sticky driver: a pure scroll TRANSLATES the content
|
|
278
|
+
// without re-allocating it (same size → GTK skips the vfunc), so signal
|
|
279
|
+
// handlers land a frame late and the pinned header jitters. A tick
|
|
280
|
+
// callback runs in the frame's UPDATE phase, before layout: when the
|
|
281
|
+
// scroll offset moved, queue an allocation for THIS frame — the
|
|
282
|
+
// translation and the pin correction then paint atomically.
|
|
283
|
+
useLayoutEffect(() => {
|
|
284
|
+
if (!hasSticky || horizontal) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const widget = scrolled;
|
|
288
|
+
if (!widget) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const id = widget.addTickCallback(() => {
|
|
292
|
+
// Unconditionally, every frame: the scrolled window's own kinetic
|
|
293
|
+
// tick may run AFTER ours, so comparing values here can miss the
|
|
294
|
+
// frame's final offset. The allocation itself reads the adjustment
|
|
295
|
+
// in the LAYOUT phase — always after every update-phase mutation —
|
|
296
|
+
// so queuing each frame guarantees a same-frame correction.
|
|
297
|
+
const content = contentRef.current;
|
|
298
|
+
if (content) {
|
|
299
|
+
queueAllocate(content);
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
});
|
|
303
|
+
return () => {
|
|
304
|
+
widget.removeTickCallback(id);
|
|
305
|
+
};
|
|
306
|
+
}, [scrolled, hasSticky, horizontal]);
|
|
307
|
+
useSignal(adjustment, "value-changed", onAdjustment);
|
|
308
|
+
// Both axes report into the same handler — RN's callback carries both
|
|
309
|
+
// dimensions no matter which one moved.
|
|
310
|
+
useSignal(scrolled?.getVadjustment() ?? null, "changed", onRangeChanged);
|
|
311
|
+
useSignal(scrolled?.getHadjustment() ?? null, "changed", onRangeChanged);
|
|
312
|
+
return (_jsx(GtkScrolledWindow, { ref: (widget) => {
|
|
313
|
+
outerRef.current = widget;
|
|
314
|
+
setScrolled(widget);
|
|
315
|
+
}, name: testID, children: _jsx(GtkBox, { ref: contentRef, children: _jsx(HostNodeContext.Provider, { value: {
|
|
316
|
+
engine: host.engine,
|
|
317
|
+
node: contentNode,
|
|
318
|
+
widgetRef: contentRef,
|
|
319
|
+
}, children: _jsx(StickyRegistryContext.Provider, { value: stickyRegistry.current, children: stickySet.length === 0
|
|
320
|
+
? children
|
|
321
|
+
: Children.toArray(children).map((child, index) => stickySet.includes(index) ? (_jsx(StickySlot, { stickyKey: `index-${index}`, children: child }, `sticky-slot-${index}`)) : (child)) }) }) }) }));
|
|
322
|
+
});
|
|
323
|
+
ScrollView.displayName = "ScrollView";
|
|
324
|
+
//# sourceMappingURL=scroll-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-view.js","sourceRoot":"","sources":["../../src/components/scroll-view.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Children,\n createContext,\n forwardRef,\n useContext,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\"\nimport { splitStyle, StyleSheet } from \"../style/index\"\nimport type { StyleProp } from \"../contracts\"\nimport {\n Gtk,\n GtkBox,\n GtkScrolledWindow,\n queueAllocate,\n queueResize,\n useSignal,\n} from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport { deferDuringAllocate, setStoredOffset } from \"./rect-store\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\nexport type ScrollViewHandle = {\n scrollTo(options: { x?: number; y?: number; animated?: boolean }): void\n scrollToEnd(options?: { animated?: boolean }): void\n}\n\nexport type ScrollEvent = {\n nativeEvent: {\n contentOffset: { x: number; y: number }\n contentSize: { width: number; height: number }\n }\n}\n\nexport type ScrollViewProps = {\n style?: StyleProp\n contentContainerStyle?: StyleProp\n horizontal?: boolean\n // RN sticky headers (vertical lists): the children at these indices pin to\n // the top while scrolled past, each pushed out by the next one.\n stickyHeaderIndices?: readonly number[]\n onScroll?: (event: ScrollEvent) => void\n // RN onContentSizeChange(contentWidth, contentHeight): fires when the\n // scrollable content changes size. GTK's adjustments emit \"changed\" right\n // after the allocation that resized their range — exactly the moment the\n // new scroll extent is real, which windowed lists rely on to reposition.\n onContentSizeChange?: (width: number, height: number) => void\n onLayout?: (event: LayoutEvent) => void\n children?: ReactNode\n testID?: string\n}\n\ntype StickyRecord = { y: number; height: number; widget: Gtk.Widget }\n\n// The sticky registry lives on the nearest ScrollView: any descendant may\n// register a slot with its content-relative geometry (measured children of\n// the ScrollView itself, or windowed list cells with engine-known offsets).\nexport type StickyRegistry = {\n register(key: string, record: StickyRecord): void\n unregister(key: string): void\n}\n\nexport const StickyRegistryContext = createContext<StickyRegistry | null>(null)\n\n// A wrapper around a sticky child: a plain container that reports its layout\n// and widget into the registry, so the scroll handler can translate/reorder\n// the REAL instance (RN semantics — no duplicate is ever rendered, external\n// margins travel with the header exactly as designed). An explicit `top`\n// (windowed cells) overrides the measured position.\nexport const StickySlot = ({\n stickyKey,\n style,\n top,\n onLayout,\n children,\n}: {\n stickyKey: string\n style?: StyleProp\n top?: number\n onLayout?: (event: LayoutEvent) => void\n children?: ReactNode\n}) => {\n const registry = useContext(StickyRegistryContext)\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { host, node } = useLayoutChild(widgetRef, {\n style,\n onLayout: (event) => {\n onLayout?.(event)\n const widget = widgetRef.current\n if (registry && widget) {\n registry.register(stickyKey, {\n y: top ?? event.nativeEvent.layout.y,\n height: event.nativeEvent.layout.height,\n widget,\n })\n }\n },\n })\n useRnContainer(widgetRef, node)\n useLayoutEffect(() => {\n return () => {\n registry?.unregister(stickyKey)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [stickyKey])\n return (\n <GtkBox ref={widgetRef}>\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {children}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\n// GtkScrolledWindow whose child is a content GtkBox backed by its own Yoga\n// node inside the same engine: the content's RnGtkxLayout measures the engine\n// content size (which grows past the viewport — that IS the scroll range;\n// overflow: scroll on the outer node) and the scrolled window pans it.\nexport const ScrollView = forwardRef<ScrollViewHandle, ScrollViewProps>(\n (\n {\n style,\n contentContainerStyle,\n horizontal = false,\n stickyHeaderIndices,\n onScroll,\n onContentSizeChange,\n onLayout,\n children,\n testID,\n },\n handleRef,\n ) => {\n const outerRef = useRef<Gtk.ScrolledWindow | null>(null)\n const contentRef = useRef<Gtk.Box | null>(null)\n const [scrolled, setScrolled] = useState<Gtk.ScrolledWindow | null>(null)\n\n const { host, node: outerNode } = useLayoutChild(outerRef, {\n // Yoga unconstrains a scroll node's MAIN axis — align it with the\n // scroll direction or a horizontal list's content clamps to the\n // viewport width (hadjustment upper == page).\n style: [\n style,\n { overflow: \"scroll\", flexDirection: horizontal ? \"row\" : \"column\" },\n ],\n onLayout,\n })\n\n const [contentNode] = useState(() => host.engine.createNode())\n // Sticky positions are computed INSIDE the same allocation pass that\n // places the children (beforeAllocate): the viewport has already settled\n // on this frame's scroll offset, so the pinned header lands exactly —\n // reacting to the adjustment signal instead lags a frame and jitters.\n useRnContainer(contentRef, contentNode, {\n beforeAllocate: () => {\n if (stickyRecords.current.size > 0) {\n updateSticky(outerRef.current?.getVadjustment()?.getValue() ?? 0)\n }\n },\n })\n\n // RN semantics: a ScrollView CLIPS its content. Branch B made containers\n // paint-overflow by default, so without this the scrolled-away content\n // (e.g. the original of a pinned sticky header) keeps drawing past the\n // scroll area's edges.\n useLayoutEffect(() => {\n outerRef.current?.setOverflow(Gtk.Overflow.HIDDEN)\n }, [])\n\n useLayoutEffect(() => {\n outerNode.insertChild(contentNode, 0)\n contentNode.setCommit(() => {\n // The content size IS this widget's measure (scroll range) — the\n // ScrolledWindow itself allocates the content widget.\n const widget = contentRef.current\n if (!widget) {\n return\n }\n const queue = (): void => {\n queueResize(widget)\n }\n if (!deferDuringAllocate(widget, queue)) {\n queue()\n }\n })\n return () => {\n contentNode.setCommit(null)\n outerNode.removeChild(contentNode)\n contentNode.free()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const contentLayout = splitStyle(\n StyleSheet.flatten(contentContainerStyle),\n ).layout\n const contentKey = JSON.stringify({ horizontal, contentLayout })\n useLayoutEffect(() => {\n contentNode.setStyle({\n flexDirection: horizontal ? \"row\" : \"column\",\n alignItems: \"flex-start\",\n ...contentLayout,\n })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [contentNode, contentKey])\n\n useImperativeHandle(handleRef, () => ({\n scrollTo({ x, y }) {\n const widget = outerRef.current\n if (!widget) {\n return\n }\n if (y !== undefined) {\n widget.getVadjustment()?.setValue(y)\n }\n if (x !== undefined) {\n widget.getHadjustment()?.setValue(x)\n }\n },\n scrollToEnd() {\n const widget = outerRef.current\n const adjustment = horizontal\n ? widget?.getHadjustment()\n : widget?.getVadjustment()\n if (adjustment) {\n adjustment.setValue(adjustment.getUpper() - adjustment.getPageSize())\n }\n },\n }))\n\n // --- sticky headers -------------------------------------------------\n // The RN model, faithfully: the REAL child is translated (no duplicate,\n // state preserved). RN gives the pinned cell a zIndex; GTK's z-order is\n // sibling paint order, so the active slot is reordered to be the LAST\n // content child while pinned and restored afterwards. Per-frame movement\n // goes through the rect-store offset fast path — zero React work while\n // scrolling.\n const stickySet = stickyHeaderIndices ?? []\n const stickyRecords = useRef(new Map<string, StickyRecord>())\n const restoreSibling = useRef(new Map<string, Gtk.Widget | null>())\n const activeStickyRef = useRef<string | null>(null)\n const [hasSticky, setHasSticky] = useState(false)\n\n const stickyRegistry = useRef<StickyRegistry>({\n register: (key, record) => {\n stickyRecords.current.set(key, record)\n setHasSticky(true)\n const parent = record.widget.getParent()\n if (parent) {\n queueAllocate(parent)\n }\n },\n unregister: (key) => {\n stickyRecords.current.delete(key)\n restoreSibling.current.delete(key)\n if (activeStickyRef.current === key) {\n activeStickyRef.current = null\n }\n if (stickyRecords.current.size === 0) {\n setHasSticky(false)\n }\n },\n })\n\n const updateSticky = (rawScrollTop: number): void => {\n if (stickyRecords.current.size === 0) {\n return\n }\n // The viewport translates the content by the FRACTIONAL adjustment\n // value while widget allocations are integer — an unquantized pin\n // disagrees with the translation by ±1px and shimmers every frame.\n // Frame telemetry (docs/research/sticky-probe.md) proved the viewport quantizes\n // like floor: with Math.floor the header's window-relative position is\n // a flat 0.00 across every frame; round oscillates 0/1, ceil sits at 1.\n const scrollTop = Math.floor(rawScrollTop)\n let active: string | null = null\n let next: StickyRecord | null = null\n for (const [key, record] of stickyRecords.current) {\n if (record.y <= scrollTop) {\n if (\n active === null ||\n record.y >= stickyRecords.current.get(active)!.y\n ) {\n active = key\n }\n } else if (next === null || record.y < next.y) {\n next = record\n }\n }\n\n const previous = activeStickyRef.current\n if (previous !== active) {\n activeStickyRef.current = active\n if (previous !== null) {\n const record = stickyRecords.current.get(previous)\n if (record) {\n setStoredOffset(record.widget, 0, 0)\n }\n }\n // Reordering widgets queues GTK work — not allowed mid-allocation.\n // The z-switch lands a frame later; the POSITION is already exact.\n setTimeout(() => {\n applyStickyReorder(previous, active)\n }, 0)\n }\n\n if (active !== null) {\n const record = stickyRecords.current.get(active)!\n // Pin at the viewport top; the next sticky header pushes it out.\n const pinned = next\n ? Math.min(scrollTop, next.y - record.height)\n : scrollTop\n setStoredOffset(record.widget, 0, Math.max(0, pinned - record.y))\n }\n }\n\n const applyStickyReorder = (\n previous: string | null,\n active: string | null,\n ): void => {\n if (previous !== null) {\n const record = stickyRecords.current.get(previous)\n // Slot parents are always our GtkBox containers.\n const parent = record?.widget.getParent() as Gtk.Box | null\n if (record && parent) {\n // Put the slot back where the reconciler expects it.\n parent.reorderChildAfter(\n record.widget,\n restoreSibling.current.get(previous) ?? null,\n )\n queueAllocate(parent)\n }\n }\n if (active !== null) {\n const record = stickyRecords.current.get(active)\n const parent = record?.widget.getParent() as Gtk.Box | null\n if (record && parent) {\n restoreSibling.current.set(active, record.widget.getPrevSibling())\n parent.reorderChildAfter(record.widget, parent.getLastChild())\n queueAllocate(parent)\n }\n }\n }\n\n const emitScroll = (): void => {\n if (!onScroll) {\n return\n }\n const widget = outerRef.current\n const contentRect = contentNode.getRect()\n if (!widget || !contentRect) {\n return\n }\n onScroll({\n nativeEvent: {\n contentOffset: {\n x: widget.getHadjustment()?.getValue() ?? 0,\n y: widget.getVadjustment()?.getValue() ?? 0,\n },\n contentSize: { width: contentRect.width, height: contentRect.height },\n },\n })\n }\n\n const adjustment = horizontal\n ? (scrolled?.getHadjustment() ?? null)\n : (scrolled?.getVadjustment() ?? null)\n const onAdjustment = (): void => {\n emitScroll()\n }\n\n // Content-size reports dedupe on the engine rect: \"changed\" also fires\n // for pure viewport (page-size) changes, which RN does not report.\n const lastContentSize = useRef({ width: -1, height: -1 })\n const onRangeChanged = (): void => {\n if (!onContentSizeChange) {\n return\n }\n const rect = contentNode.getRect()\n if (!rect) {\n return\n }\n const last = lastContentSize.current\n if (rect.width === last.width && rect.height === last.height) {\n return\n }\n lastContentSize.current = { width: rect.width, height: rect.height }\n onContentSizeChange(rect.width, rect.height)\n }\n\n // Frame-synced sticky driver: a pure scroll TRANSLATES the content\n // without re-allocating it (same size → GTK skips the vfunc), so signal\n // handlers land a frame late and the pinned header jitters. A tick\n // callback runs in the frame's UPDATE phase, before layout: when the\n // scroll offset moved, queue an allocation for THIS frame — the\n // translation and the pin correction then paint atomically.\n useLayoutEffect(() => {\n if (!hasSticky || horizontal) {\n return\n }\n const widget = scrolled\n if (!widget) {\n return\n }\n const id = widget.addTickCallback(() => {\n // Unconditionally, every frame: the scrolled window's own kinetic\n // tick may run AFTER ours, so comparing values here can miss the\n // frame's final offset. The allocation itself reads the adjustment\n // in the LAYOUT phase — always after every update-phase mutation —\n // so queuing each frame guarantees a same-frame correction.\n const content = contentRef.current\n if (content) {\n queueAllocate(content)\n }\n return true\n })\n return () => {\n widget.removeTickCallback(id)\n }\n }, [scrolled, hasSticky, horizontal])\n useSignal(adjustment, \"value-changed\", onAdjustment)\n // Both axes report into the same handler — RN's callback carries both\n // dimensions no matter which one moved.\n useSignal(scrolled?.getVadjustment() ?? null, \"changed\", onRangeChanged)\n useSignal(scrolled?.getHadjustment() ?? null, \"changed\", onRangeChanged)\n\n return (\n <GtkScrolledWindow\n ref={(widget: Gtk.ScrolledWindow | null) => {\n outerRef.current = widget\n setScrolled(widget)\n }}\n name={testID}\n >\n <GtkBox ref={contentRef}>\n <HostNodeContext.Provider\n value={{\n engine: host.engine,\n node: contentNode,\n widgetRef: contentRef,\n }}\n >\n <StickyRegistryContext.Provider value={stickyRegistry.current}>\n {stickySet.length === 0\n ? children\n : Children.toArray(children).map((child, index) =>\n stickySet.includes(index) ? (\n <StickySlot\n key={`sticky-slot-${index}`}\n stickyKey={`index-${index}`}\n >\n {child}\n </StickySlot>\n ) : (\n child\n ),\n )}\n </StickyRegistryContext.Provider>\n </HostNodeContext.Provider>\n </GtkBox>\n </GtkScrolledWindow>\n )\n },\n)\nScrollView.displayName = \"ScrollView\"\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { StyleProp } from "../contracts";
|
|
2
|
+
import { type LayoutEvent } from "./use-layout-child";
|
|
3
|
+
export type SwitchProps = {
|
|
4
|
+
value?: boolean;
|
|
5
|
+
onValueChange?: (value: boolean) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
style?: StyleProp;
|
|
8
|
+
onLayout?: (event: LayoutEvent) => void;
|
|
9
|
+
testID?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const Switch: ({ value, onValueChange, disabled, style, onLayout, testID, }: SwitchProps) => React.JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { GtkSwitch } from "../gtkx/bridge/index";
|
|
4
|
+
import { useLayoutChild } from "./use-layout-child";
|
|
5
|
+
// GtkSwitch natural size drives the Yoga leaf; controlled like RN: the state
|
|
6
|
+
// change is reported but the widget follows the `value` prop.
|
|
7
|
+
export const Switch = ({ value = false, onValueChange, disabled = false, style, onLayout, testID, }) => {
|
|
8
|
+
const widgetRef = useRef(null);
|
|
9
|
+
useLayoutChild(widgetRef, {
|
|
10
|
+
style,
|
|
11
|
+
onLayout,
|
|
12
|
+
measureFromWidget: true,
|
|
13
|
+
});
|
|
14
|
+
return (_jsx(GtkSwitch, { ref: widgetRef, name: testID, active: value, sensitive: !disabled, onStateSet: (state) => {
|
|
15
|
+
onValueChange?.(state);
|
|
16
|
+
// Controlled: swallow the default toggle, the prop drives the widget.
|
|
17
|
+
return true;
|
|
18
|
+
} }));
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=switch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"switch.js","sourceRoot":"","sources":["../../src/components/switch.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,SAAS,EAAY,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAoB,MAAM,oBAAoB,CAAA;AAWrE,6EAA6E;AAC7E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,KAAK,GAAG,KAAK,EACb,aAAa,EACb,QAAQ,GAAG,KAAK,EAChB,KAAK,EACL,QAAQ,EACR,MAAM,GACM,EAAE,EAAE;IAChB,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IAEjD,cAAc,CAAC,SAAS,EAAE;QACxB,KAAK;QACL,QAAQ;QACR,iBAAiB,EAAE,IAAI;KACxB,CAAC,CAAA;IAEF,OAAO,CACL,KAAC,SAAS,IACR,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,EACb,SAAS,EAAE,CAAC,QAAQ,EACpB,UAAU,EAAE,CAAC,KAAc,EAAE,EAAE;YAC7B,aAAa,EAAE,CAAC,KAAK,CAAC,CAAA;YACtB,sEAAsE;YACtE,OAAO,IAAI,CAAA;QACb,CAAC,GACD,CACH,CAAA;AACH,CAAC,CAAA","sourcesContent":["import { useRef } from \"react\"\nimport type { StyleProp } from \"../contracts\"\nimport { GtkSwitch, type Gtk } from \"../gtkx/bridge/index\"\nimport { useLayoutChild, type LayoutEvent } from \"./use-layout-child\"\n\nexport type SwitchProps = {\n value?: boolean\n onValueChange?: (value: boolean) => void\n disabled?: boolean\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\n// GtkSwitch natural size drives the Yoga leaf; controlled like RN: the state\n// change is reported but the widget follows the `value` prop.\nexport const Switch = ({\n value = false,\n onValueChange,\n disabled = false,\n style,\n onLayout,\n testID,\n}: SwitchProps) => {\n const widgetRef = useRef<Gtk.Switch | null>(null)\n\n useLayoutChild(widgetRef, {\n style,\n onLayout,\n measureFromWidget: true,\n })\n\n return (\n <GtkSwitch\n ref={widgetRef}\n name={testID}\n active={value}\n sensitive={!disabled}\n onStateSet={(state: boolean) => {\n onValueChange?.(state)\n // Controlled: swallow the default toggle, the prop drives the widget.\n return true\n }}\n />\n )\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StyleProp } from "../contracts";
|
|
2
|
+
import { type LayoutEvent } from "./use-layout-child";
|
|
3
|
+
export type TextInputProps = {
|
|
4
|
+
value?: string;
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
onChangeText?: (text: string) => void;
|
|
7
|
+
onSubmitEditing?: (event: {
|
|
8
|
+
nativeEvent: {
|
|
9
|
+
text: string;
|
|
10
|
+
};
|
|
11
|
+
}) => void;
|
|
12
|
+
onFocus?: () => void;
|
|
13
|
+
onBlur?: () => void;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
secureTextEntry?: boolean;
|
|
16
|
+
editable?: boolean;
|
|
17
|
+
keyboardType?: "default" | "numeric" | "email-address" | "phone-pad" | "url";
|
|
18
|
+
multiline?: boolean;
|
|
19
|
+
style?: StyleProp;
|
|
20
|
+
onLayout?: (event: LayoutEvent) => void;
|
|
21
|
+
testID?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const TextInput: (props: TextInputProps) => React.JSX.Element;
|