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,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { VirtualizedList, } from "./virtualized-list";
|
|
4
|
+
// FlatList is a thin veneer over the windowed VirtualizedList core: only the
|
|
5
|
+
// rows around the viewport are mounted (see virtualized-list.tsx).
|
|
6
|
+
const FlatListInner = forwardRef((props, ref) => (_jsx(VirtualizedList, { ...props, ref: ref })));
|
|
7
|
+
FlatListInner.displayName = "FlatList";
|
|
8
|
+
export const FlatList = FlatListInner;
|
|
9
|
+
export const SectionList = ({ sections, renderItem, renderSectionHeader, stickySectionHeadersEnabled = true, ref, ...rest }) => {
|
|
10
|
+
const rows = [];
|
|
11
|
+
const headerIndices = [];
|
|
12
|
+
for (const section of sections) {
|
|
13
|
+
headerIndices.push(rows.length);
|
|
14
|
+
rows.push({ kind: "header", section });
|
|
15
|
+
section.data.forEach((item, index) => {
|
|
16
|
+
rows.push({ kind: "item", item, index });
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return (_jsx(FlatList, { ...rest, ref: ref, data: rows, stickyHeaderIndices: stickySectionHeadersEnabled ? headerIndices : undefined, keyExtractor: (_row, index) => String(index), renderItem: ({ item: row }) => row.kind === "header"
|
|
20
|
+
? (renderSectionHeader?.({ section: row.section }) ?? null)
|
|
21
|
+
: renderItem({ item: row.item, index: row.index }) }));
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=flat-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flat-list.js","sourceRoot":"","sources":["../../src/components/flat-list.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAyC,MAAM,OAAO,CAAA;AAGzE,OAAO,EACL,eAAe,GAMhB,MAAM,oBAAoB,CAAA;AAqC3B,6EAA6E;AAC7E,mEAAmE;AACnE,MAAM,aAAa,GAAG,UAAU,CAC9B,CAAK,KAAuB,EAAE,GAAqC,EAAE,EAAE,CAAC,CACtE,KAAC,eAAe,OACV,KAAK,EACT,GAAG,EAAE,GAAG,GACR,CACH,CACF,CAAA;AACD,aAAa,CAAC,WAAW,GAAG,UAAU,CAAA;AAEtC,MAAM,CAAC,MAAM,QAAQ,GAAG,aAEP,CAAA;AA+BjB,MAAM,CAAC,MAAM,WAAW,GAAG,CAAK,EAC9B,QAAQ,EACR,UAAU,EACV,mBAAmB,EACnB,2BAA2B,GAAG,IAAI,EAClC,GAAG,EACH,GAAG,IAAI,EACa,EAAgB,EAAE;IACtC,MAAM,IAAI,GAAoB,EAAE,CAAA;IAChC,MAAM,aAAa,GAAa,EAAE,CAAA;IAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;QACtC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACnC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,CACL,KAAC,QAAQ,OACH,IAAI,EACR,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,IAAI,EACV,mBAAmB,EACjB,2BAA2B,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,EAEzD,YAAY,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAC5C,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAC5B,GAAG,CAAC,IAAI,KAAK,QAAQ;YACnB,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,IAAI,CAAC;YAC3D,CAAC,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,GAEtD,CACH,CAAA;AACH,CAAC,CAAA","sourcesContent":["import { forwardRef, type ComponentType, type ReactElement } from \"react\"\nimport type { StyleProp } from \"../contracts\"\nimport type { ScrollViewProps } from \"./scroll-view\"\nimport {\n VirtualizedList,\n type ItemLayout,\n type ListRenderItemInfo,\n type ViewabilityConfig,\n type ViewToken,\n type VirtualizedListHandle,\n} from \"./virtualized-list\"\n\nexport type {\n ListRenderItemInfo,\n ViewabilityConfig,\n ViewToken,\n} from \"./virtualized-list\"\n// The FlatList ref exposes the full scroll surface of the windowed core\n// (scrollTo, scrollToEnd, scrollToIndex, scrollToItem, scrollToOffset).\nexport type { VirtualizedListHandle as FlatListHandle } from \"./virtualized-list\"\n\nexport type FlatListProps<T> = Omit<ScrollViewProps, \"children\"> & {\n data: readonly T[]\n renderItem: (info: ListRenderItemInfo<T>) => ReactElement | null\n keyExtractor?: (item: T, index: number) => string\n ItemSeparatorComponent?: ComponentType | null\n ListHeaderComponent?: ComponentType | ReactElement | null\n ListFooterComponent?: ComponentType | ReactElement | null\n ListEmptyComponent?: ComponentType | ReactElement | null\n onEndReached?: () => void\n onEndReachedThreshold?: number\n onViewableItemsChanged?: (info: {\n viewableItems: ViewToken<T>[]\n changed: ViewToken<T>[]\n }) => void\n viewabilityConfig?: ViewabilityConfig\n estimatedItemSize?: number\n getItemLayout?: (data: readonly T[], index: number) => ItemLayout\n windowSize?: number\n initialNumToRender?: number\n extraData?: unknown\n inverted?: boolean\n refreshing?: boolean\n onRefresh?: () => void\n style?: StyleProp\n}\n\n// FlatList is a thin veneer over the windowed VirtualizedList core: only the\n// rows around the viewport are mounted (see virtualized-list.tsx).\nconst FlatListInner = forwardRef(\n <T,>(props: FlatListProps<T>, ref: React.Ref<VirtualizedListHandle>) => (\n <VirtualizedList\n {...props}\n ref={ref}\n />\n ),\n)\nFlatListInner.displayName = \"FlatList\"\n\nexport const FlatList = FlatListInner as <T>(\n props: FlatListProps<T> & { ref?: React.Ref<VirtualizedListHandle> },\n) => ReactElement\n\nexport type SectionListData<T> = { title: string; data: readonly T[] }\n\n// Viewability props are excluded: SectionList flattens sections into private\n// row records, so ViewTokens would leak that internal row type instead of T —\n// section-aware tokens are not implemented yet.\nexport type SectionListProps<T> = Omit<\n FlatListProps<T>,\n | \"data\"\n | \"renderItem\"\n | \"getItemLayout\"\n | \"onViewableItemsChanged\"\n | \"viewabilityConfig\"\n> & {\n sections: readonly SectionListData<T>[]\n renderItem: (info: ListRenderItemInfo<T>) => ReactElement | null\n renderSectionHeader?: (info: {\n section: SectionListData<T>\n }) => ReactElement | null\n // RN default on iOS; ours defaults to true too — section headers pin to\n // the top and get pushed out by the next section's header.\n stickySectionHeadersEnabled?: boolean\n // React 19 ref-as-prop: the same scroll surface FlatList exposes.\n ref?: React.Ref<VirtualizedListHandle>\n}\n\ntype SectionRow<T> =\n | { kind: \"header\"; section: SectionListData<T> }\n | { kind: \"item\"; item: T; index: number }\n\nexport const SectionList = <T,>({\n sections,\n renderItem,\n renderSectionHeader,\n stickySectionHeadersEnabled = true,\n ref,\n ...rest\n}: SectionListProps<T>): ReactElement => {\n const rows: SectionRow<T>[] = []\n const headerIndices: number[] = []\n for (const section of sections) {\n headerIndices.push(rows.length)\n rows.push({ kind: \"header\", section })\n section.data.forEach((item, index) => {\n rows.push({ kind: \"item\", item, index })\n })\n }\n return (\n <FlatList\n {...rest}\n ref={ref}\n data={rows}\n stickyHeaderIndices={\n stickySectionHeadersEnabled ? headerIndices : undefined\n }\n keyExtractor={(_row, index) => String(index)}\n renderItem={({ item: row }) =>\n row.kind === \"header\"\n ? (renderSectionHeader?.({ section: row.section }) ?? null)\n : renderItem({ item: row.item, index: row.index })\n }\n />\n )\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import type { LayoutEngine, LayoutNode } from "../layout/index";
|
|
3
|
+
import type { Gtk } from "../gtkx/bridge/index";
|
|
4
|
+
export type HostNode = {
|
|
5
|
+
engine: LayoutEngine;
|
|
6
|
+
node: LayoutNode;
|
|
7
|
+
widgetRef: RefObject<Gtk.Box | null>;
|
|
8
|
+
};
|
|
9
|
+
export declare const HostNodeContext: React.Context<HostNode | null>;
|
|
10
|
+
export declare const useHostNode: () => HostNode;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
export const HostNodeContext = createContext(null);
|
|
3
|
+
export const useHostNode = () => {
|
|
4
|
+
const host = useContext(HostNodeContext);
|
|
5
|
+
if (host === null) {
|
|
6
|
+
throw new Error("react-native-gtkx components must be rendered inside AppRegistry.runApplication() or a <Root>");
|
|
7
|
+
}
|
|
8
|
+
return host;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=host-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-node.js","sourceRoot":"","sources":["../../src/components/host-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAA;AAajE,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAkB,IAAI,CAAC,CAAA;AAEnE,MAAM,CAAC,MAAM,WAAW,GAAG,GAAa,EAAE;IACxC,MAAM,IAAI,GAAG,UAAU,CAAC,eAAe,CAAC,CAAA;IACxC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,+FAA+F,CAChG,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA","sourcesContent":["import { createContext, useContext, type RefObject } from \"react\"\nimport type { LayoutEngine, LayoutNode } from \"../layout/index\"\nimport type { Gtk } from \"../gtkx/bridge/index\"\n\n// One per mounted container (Root or View): children register their layout\n// nodes here and commit rects into the store read by the parent's\n// RnGtkxLayout allocate().\nexport type HostNode = {\n engine: LayoutEngine\n node: LayoutNode\n widgetRef: RefObject<Gtk.Box | null>\n}\n\nexport const HostNodeContext = createContext<HostNode | null>(null)\n\nexport const useHostNode = (): HostNode => {\n const host = useContext(HostNodeContext)\n if (host === null) {\n throw new Error(\n \"react-native-gtkx components must be rendered inside AppRegistry.runApplication() or a <Root>\",\n )\n }\n return host\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const isRemoteUri: (uri: string) => boolean;
|
|
2
|
+
/** Deterministic on-disk location for a remote URL. */
|
|
3
|
+
export declare const cachePathFor: (uri: string) => string;
|
|
4
|
+
export type ImageFetcher = (uri: string) => Promise<{
|
|
5
|
+
ok: boolean;
|
|
6
|
+
status: number;
|
|
7
|
+
headers: {
|
|
8
|
+
get(name: string): string | null;
|
|
9
|
+
};
|
|
10
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a remote URL to a local file path: instantly when cached,
|
|
14
|
+
* otherwise downloads once (concurrent callers share the promise). Rejects
|
|
15
|
+
* on network errors, non-2xx responses and non-image payloads.
|
|
16
|
+
*/
|
|
17
|
+
export declare const loadRemoteImage: (uri: string, fetcher?: ImageFetcher) => Promise<string>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Remote image loading for <Image source={{uri: "https://…"}}/>: Node's
|
|
2
|
+
// own fetch downloads into an on-disk cache (XDG cache dir, keyed by
|
|
3
|
+
// sha256 of the URL) and GtkPicture reads the file. Pure module — the
|
|
4
|
+
// fetcher is injectable, so the download logic is unit-tested without a
|
|
5
|
+
// network or a widget.
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { existsSync, mkdirSync, renameSync } from "node:fs";
|
|
8
|
+
import { writeFile } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
export const isRemoteUri = (uri) => uri.startsWith("http://") || uri.startsWith("https://");
|
|
12
|
+
const cacheDir = () => join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "react-native-gtkx", "images");
|
|
13
|
+
/** Deterministic on-disk location for a remote URL. */
|
|
14
|
+
export const cachePathFor = (uri) => join(cacheDir(), createHash("sha256").update(uri).digest("hex"));
|
|
15
|
+
// N simultaneous mounts of the same URL share one download.
|
|
16
|
+
const inFlight = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Resolves a remote URL to a local file path: instantly when cached,
|
|
19
|
+
* otherwise downloads once (concurrent callers share the promise). Rejects
|
|
20
|
+
* on network errors, non-2xx responses and non-image payloads.
|
|
21
|
+
*/
|
|
22
|
+
export const loadRemoteImage = (uri, fetcher = fetch) => {
|
|
23
|
+
const target = cachePathFor(uri);
|
|
24
|
+
if (existsSync(target)) {
|
|
25
|
+
return Promise.resolve(target);
|
|
26
|
+
}
|
|
27
|
+
const pending = inFlight.get(uri);
|
|
28
|
+
if (pending) {
|
|
29
|
+
return pending;
|
|
30
|
+
}
|
|
31
|
+
const download = (async () => {
|
|
32
|
+
const response = await fetcher(uri);
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`HTTP ${response.status} for ${uri}`);
|
|
35
|
+
}
|
|
36
|
+
const contentType = response.headers.get("content-type");
|
|
37
|
+
if (contentType !== null && !contentType.startsWith("image/")) {
|
|
38
|
+
throw new Error(`Not an image (${contentType}) at ${uri}`);
|
|
39
|
+
}
|
|
40
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
41
|
+
mkdirSync(cacheDir(), { recursive: true });
|
|
42
|
+
// Write-then-rename: a crash mid-write must not leave a partial file
|
|
43
|
+
// that later reads as a cache hit.
|
|
44
|
+
const scratch = `${target}.${process.pid}.part`;
|
|
45
|
+
await writeFile(scratch, bytes);
|
|
46
|
+
renameSync(scratch, target);
|
|
47
|
+
return target;
|
|
48
|
+
})().finally(() => {
|
|
49
|
+
inFlight.delete(uri);
|
|
50
|
+
});
|
|
51
|
+
inFlight.set(uri, download);
|
|
52
|
+
return download;
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=image-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-loader.js","sourceRoot":"","sources":["../../src/components/image-loader.ts"],"names":[],"mappings":"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","sourcesContent":["// Remote image loading for <Image source={{uri: \"https://…\"}}/>: Node's\n// own fetch downloads into an on-disk cache (XDG cache dir, keyed by\n// sha256 of the URL) and GtkPicture reads the file. Pure module — the\n// fetcher is injectable, so the download logic is unit-tested without a\n// network or a widget.\nimport { createHash } from \"node:crypto\"\nimport { existsSync, mkdirSync, renameSync } from \"node:fs\"\nimport { writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nexport const isRemoteUri = (uri: string): boolean =>\n uri.startsWith(\"http://\") || uri.startsWith(\"https://\")\n\nconst cacheDir = (): string =>\n join(\n process.env.XDG_CACHE_HOME ?? join(homedir(), \".cache\"),\n \"react-native-gtkx\",\n \"images\",\n )\n\n/** Deterministic on-disk location for a remote URL. */\nexport const cachePathFor = (uri: string): string =>\n join(cacheDir(), createHash(\"sha256\").update(uri).digest(\"hex\"))\n\nexport type ImageFetcher = (uri: string) => Promise<{\n ok: boolean\n status: number\n headers: { get(name: string): string | null }\n arrayBuffer(): Promise<ArrayBuffer>\n}>\n\n// N simultaneous mounts of the same URL share one download.\nconst inFlight = new Map<string, Promise<string>>()\n\n/**\n * Resolves a remote URL to a local file path: instantly when cached,\n * otherwise downloads once (concurrent callers share the promise). Rejects\n * on network errors, non-2xx responses and non-image payloads.\n */\nexport const loadRemoteImage = (\n uri: string,\n fetcher: ImageFetcher = fetch,\n): Promise<string> => {\n const target = cachePathFor(uri)\n if (existsSync(target)) {\n return Promise.resolve(target)\n }\n const pending = inFlight.get(uri)\n if (pending) {\n return pending\n }\n const download = (async (): Promise<string> => {\n const response = await fetcher(uri)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} for ${uri}`)\n }\n const contentType = response.headers.get(\"content-type\")\n if (contentType !== null && !contentType.startsWith(\"image/\")) {\n throw new Error(`Not an image (${contentType}) at ${uri}`)\n }\n const bytes = Buffer.from(await response.arrayBuffer())\n mkdirSync(cacheDir(), { recursive: true })\n // Write-then-rename: a crash mid-write must not leave a partial file\n // that later reads as a cache hit.\n const scratch = `${target}.${process.pid}.part`\n await writeFile(scratch, bytes)\n renameSync(scratch, target)\n return target\n })().finally(() => {\n inFlight.delete(uri)\n })\n inFlight.set(uri, download)\n return download\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StyleProp } from "../contracts";
|
|
2
|
+
import { type LayoutEvent } from "./use-layout-child";
|
|
3
|
+
export type ImageSource = {
|
|
4
|
+
uri: string;
|
|
5
|
+
} | string;
|
|
6
|
+
export type ImageProps = {
|
|
7
|
+
source: ImageSource;
|
|
8
|
+
style?: StyleProp;
|
|
9
|
+
resizeMode?: "cover" | "contain" | "stretch" | "center";
|
|
10
|
+
onLayout?: (event: LayoutEvent) => void;
|
|
11
|
+
onLoad?: () => void;
|
|
12
|
+
onError?: (error: {
|
|
13
|
+
nativeEvent: {
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
}) => void;
|
|
17
|
+
testID?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const Image: ({ source, style, resizeMode, onLayout, onLoad, onError, testID, }: ImageProps) => React.JSX.Element;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { Gdk, Gtk, GtkPicture } from "../gtkx/bridge/index";
|
|
5
|
+
import { isRemoteUri, loadRemoteImage } from "./image-loader";
|
|
6
|
+
import { useLayoutChild } from "./use-layout-child";
|
|
7
|
+
const CONTENT_FIT = {
|
|
8
|
+
cover: Gtk.ContentFit.COVER,
|
|
9
|
+
contain: Gtk.ContentFit.CONTAIN,
|
|
10
|
+
stretch: Gtk.ContentFit.FILL,
|
|
11
|
+
center: Gtk.ContentFit.SCALE_DOWN,
|
|
12
|
+
};
|
|
13
|
+
const toPath = (source) => {
|
|
14
|
+
const uri = typeof source === "string" ? source : source.uri;
|
|
15
|
+
return uri.startsWith("file://") ? uri.slice("file://".length) : uri;
|
|
16
|
+
};
|
|
17
|
+
// Local files load synchronously; http(s) URIs download through the
|
|
18
|
+
// on-disk cache (image-loader.ts) and set the picture when ready. The
|
|
19
|
+
// texture is decoded explicitly (Gdk.Texture throws on unsupported
|
|
20
|
+
// formats — e.g. ICO favicons), so onError is honest instead of showing
|
|
21
|
+
// an empty picture. Sizing comes from the style (width/height or flex) —
|
|
22
|
+
// like RN, which also cannot infer remote image sizes synchronously.
|
|
23
|
+
export const Image = ({ source, style, resizeMode = "cover", onLayout, onLoad, onError, testID, }) => {
|
|
24
|
+
const widgetRef = useRef(null);
|
|
25
|
+
useLayoutChild(widgetRef, { style, onLayout });
|
|
26
|
+
const path = toPath(source);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const widget = widgetRef.current;
|
|
29
|
+
if (!widget) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const show = (file) => {
|
|
33
|
+
const target = widgetRef.current;
|
|
34
|
+
if (!target) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
// Decode explicitly: setFilename fails silently on formats the
|
|
39
|
+
// texture loader does not support, newFromFilename throws.
|
|
40
|
+
target.setPaintable(Gdk.Texture.newFromFilename(file));
|
|
41
|
+
onLoad?.();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
onError?.({ nativeEvent: { error: String(error) } });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
if (isRemoteUri(path)) {
|
|
48
|
+
let cancelled = false;
|
|
49
|
+
loadRemoteImage(path).then((file) => {
|
|
50
|
+
// Unmount (or a source change) during the download must not touch
|
|
51
|
+
// the widget or fire stale callbacks.
|
|
52
|
+
if (!cancelled) {
|
|
53
|
+
show(file);
|
|
54
|
+
}
|
|
55
|
+
}, (error) => {
|
|
56
|
+
if (!cancelled) {
|
|
57
|
+
onError?.({ nativeEvent: { error: String(error) } });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (!existsSync(path)) {
|
|
65
|
+
onError?.({ nativeEvent: { error: `Image not found: ${path}` } });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
show(path);
|
|
69
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
+
}, [path]);
|
|
71
|
+
return (_jsx(GtkPicture, { ref: widgetRef, name: testID, contentFit: CONTENT_FIT[resizeMode] }));
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/components/image.tsx"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\"\nimport { useEffect, useRef } from \"react\"\nimport type { StyleProp } from \"../contracts\"\nimport { Gdk, Gtk, GtkPicture } from \"../gtkx/bridge/index\"\nimport { isRemoteUri, loadRemoteImage } from \"./image-loader\"\nimport { useLayoutChild, type LayoutEvent } from \"./use-layout-child\"\n\nexport type ImageSource = { uri: string } | string\n\nexport type ImageProps = {\n source: ImageSource\n style?: StyleProp\n resizeMode?: \"cover\" | \"contain\" | \"stretch\" | \"center\"\n onLayout?: (event: LayoutEvent) => void\n onLoad?: () => void\n onError?: (error: { nativeEvent: { error: string } }) => void\n testID?: string\n}\n\nconst CONTENT_FIT = {\n cover: Gtk.ContentFit.COVER,\n contain: Gtk.ContentFit.CONTAIN,\n stretch: Gtk.ContentFit.FILL,\n center: Gtk.ContentFit.SCALE_DOWN,\n} as const\n\nconst toPath = (source: ImageSource): string => {\n const uri = typeof source === \"string\" ? source : source.uri\n return uri.startsWith(\"file://\") ? uri.slice(\"file://\".length) : uri\n}\n\n// Local files load synchronously; http(s) URIs download through the\n// on-disk cache (image-loader.ts) and set the picture when ready. The\n// texture is decoded explicitly (Gdk.Texture throws on unsupported\n// formats — e.g. ICO favicons), so onError is honest instead of showing\n// an empty picture. Sizing comes from the style (width/height or flex) —\n// like RN, which also cannot infer remote image sizes synchronously.\nexport const Image = ({\n source,\n style,\n resizeMode = \"cover\",\n onLayout,\n onLoad,\n onError,\n testID,\n}: ImageProps) => {\n const widgetRef = useRef<Gtk.Picture | null>(null)\n useLayoutChild(widgetRef, { style, onLayout })\n\n const path = toPath(source)\n\n useEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const show = (file: string): void => {\n const target = widgetRef.current\n if (!target) {\n return\n }\n try {\n // Decode explicitly: setFilename fails silently on formats the\n // texture loader does not support, newFromFilename throws.\n target.setPaintable(Gdk.Texture.newFromFilename(file))\n onLoad?.()\n } catch (error) {\n onError?.({ nativeEvent: { error: String(error) } })\n }\n }\n if (isRemoteUri(path)) {\n let cancelled = false\n loadRemoteImage(path).then(\n (file) => {\n // Unmount (or a source change) during the download must not touch\n // the widget or fire stale callbacks.\n if (!cancelled) {\n show(file)\n }\n },\n (error: unknown) => {\n if (!cancelled) {\n onError?.({ nativeEvent: { error: String(error) } })\n }\n },\n )\n return () => {\n cancelled = true\n }\n }\n if (!existsSync(path)) {\n onError?.({ nativeEvent: { error: `Image not found: ${path}` } })\n return\n }\n show(path)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [path])\n\n return (\n <GtkPicture\n ref={widgetRef}\n name={testID}\n contentFit={CONTENT_FIT[resizeMode]}\n />\n )\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { ActivityIndicator, type ActivityIndicatorProps, } from "./activity-indicator";
|
|
2
|
+
export { Animated, Easing, type AnimatedViewProps, type AnimatedViewStyle, } from "./animated";
|
|
3
|
+
export { AppRegistry, type RunApplicationParams } from "./app-registry";
|
|
4
|
+
export { FlatList, SectionList, type FlatListProps, type ListRenderItemInfo, type SectionListData, type SectionListProps, type ViewabilityConfig, type ViewToken, } from "./flat-list";
|
|
5
|
+
export { Image, type ImageProps, type ImageSource } from "./image";
|
|
6
|
+
export { Modal, type ModalProps } from "./modal";
|
|
7
|
+
export { Pressable, TouchableOpacity, type PressableProps, type PressableStateCallbackType, type PressEvent, type TouchableOpacityProps, } from "./pressable";
|
|
8
|
+
export { Root, type RootProps } from "./root";
|
|
9
|
+
export { ScrollView, type ScrollEvent, type ScrollViewHandle, type ScrollViewProps, } from "./scroll-view";
|
|
10
|
+
export { Switch, type SwitchProps } from "./switch";
|
|
11
|
+
export { Text, type TextProps } from "./text";
|
|
12
|
+
export { TextInput, type TextInputProps } from "./text-input";
|
|
13
|
+
export { SafeAreaView, StatusBar, View, type ViewProps } from "./view";
|
|
14
|
+
export { type LayoutEvent } from "./use-layout-child";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { ActivityIndicator, } from "./activity-indicator";
|
|
2
|
+
export { Animated, Easing, } from "./animated";
|
|
3
|
+
export { AppRegistry } from "./app-registry";
|
|
4
|
+
export { FlatList, SectionList, } from "./flat-list";
|
|
5
|
+
export { Image } from "./image";
|
|
6
|
+
export { Modal } from "./modal";
|
|
7
|
+
export { Pressable, TouchableOpacity, } from "./pressable";
|
|
8
|
+
export { Root } from "./root";
|
|
9
|
+
export { ScrollView, } from "./scroll-view";
|
|
10
|
+
export { Switch } from "./switch";
|
|
11
|
+
export { Text } from "./text";
|
|
12
|
+
export { TextInput } from "./text-input";
|
|
13
|
+
export { SafeAreaView, StatusBar, View } from "./view";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAElB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,QAAQ,EACR,MAAM,GAGP,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,WAAW,EAA6B,MAAM,gBAAgB,CAAA;AACvE,OAAO,EACL,QAAQ,EACR,WAAW,GAOZ,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,KAAK,EAAqC,MAAM,SAAS,CAAA;AAClE,OAAO,EAAE,KAAK,EAAmB,MAAM,SAAS,CAAA;AAChD,OAAO,EACL,SAAS,EACT,gBAAgB,GAKjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,IAAI,EAAkB,MAAM,QAAQ,CAAA;AAC7C,OAAO,EACL,UAAU,GAIX,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,MAAM,EAAoB,MAAM,UAAU,CAAA;AACnD,OAAO,EAAE,IAAI,EAAkB,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAuB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAkB,MAAM,QAAQ,CAAA","sourcesContent":["export {\n ActivityIndicator,\n type ActivityIndicatorProps,\n} from \"./activity-indicator\"\nexport {\n Animated,\n Easing,\n type AnimatedViewProps,\n type AnimatedViewStyle,\n} from \"./animated\"\nexport { AppRegistry, type RunApplicationParams } from \"./app-registry\"\nexport {\n FlatList,\n SectionList,\n type FlatListProps,\n type ListRenderItemInfo,\n type SectionListData,\n type SectionListProps,\n type ViewabilityConfig,\n type ViewToken,\n} from \"./flat-list\"\nexport { Image, type ImageProps, type ImageSource } from \"./image\"\nexport { Modal, type ModalProps } from \"./modal\"\nexport {\n Pressable,\n TouchableOpacity,\n type PressableProps,\n type PressableStateCallbackType,\n type PressEvent,\n type TouchableOpacityProps,\n} from \"./pressable\"\nexport { Root, type RootProps } from \"./root\"\nexport {\n ScrollView,\n type ScrollEvent,\n type ScrollViewHandle,\n type ScrollViewProps,\n} from \"./scroll-view\"\nexport { Switch, type SwitchProps } from \"./switch\"\nexport { Text, type TextProps } from \"./text\"\nexport { TextInput, type TextInputProps } from \"./text-input\"\nexport { SafeAreaView, StatusBar, View, type ViewProps } from \"./view\"\nexport { type LayoutEvent } from \"./use-layout-child\"\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type ModalProps = {
|
|
3
|
+
visible?: boolean;
|
|
4
|
+
onRequestClose?: () => void;
|
|
5
|
+
title?: string;
|
|
6
|
+
transparent?: boolean;
|
|
7
|
+
animationType?: "none" | "slide" | "fade";
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
export declare const Modal: ({ visible, onRequestClose, title, width, height, children, }: ModalProps) => React.ReactPortal | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createPortal, GtkWindow, useApplication, useParentWindow, } from "../gtkx/bridge/index";
|
|
3
|
+
import { Root } from "./root";
|
|
4
|
+
// RN Modal → a modal GtkWindow portaled onto the application, transient for
|
|
5
|
+
// the window that rendered it. Visibility is fully controlled by the prop:
|
|
6
|
+
// the close request is reported and swallowed.
|
|
7
|
+
export const Modal = ({ visible = false, onRequestClose, title = "", width = 480, height = 400, children, }) => {
|
|
8
|
+
const application = useApplication();
|
|
9
|
+
const parentWindow = useParentWindow();
|
|
10
|
+
if (!visible) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return createPortal(_jsx(GtkWindow, { title: title, modal: true, transientFor: parentWindow ?? undefined, defaultWidth: width, defaultHeight: height, onCloseRequest: () => {
|
|
14
|
+
onRequestClose?.();
|
|
15
|
+
return true;
|
|
16
|
+
}, children: _jsx(Root, { width: width, height: height, followAllocation: true, children: children }) }), application);
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=modal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal.js","sourceRoot":"","sources":["../../src/components/modal.tsx"],"names":[],"mappings":";AACA,OAAO,EACL,YAAY,EACZ,SAAS,EACT,cAAc,EACd,eAAe,GAChB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAe7B,4EAA4E;AAC5E,2EAA2E;AAC3E,+CAA+C;AAC/C,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,OAAO,GAAG,KAAK,EACf,cAAc,EACd,KAAK,GAAG,EAAE,EACV,KAAK,GAAG,GAAG,EACX,MAAM,GAAG,GAAG,EACZ,QAAQ,GACG,EAAE,EAAE;IACf,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IACpC,MAAM,YAAY,GAAG,eAAe,EAAE,CAAA;IAEtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,YAAY,CACjB,KAAC,SAAS,IACR,KAAK,EAAE,KAAK,EACZ,KAAK,QACL,YAAY,EAAE,YAAY,IAAI,SAAS,EACvC,YAAY,EAAE,KAAK,EACnB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,GAAG,EAAE;YACnB,cAAc,EAAE,EAAE,CAAA;YAClB,OAAO,IAAI,CAAA;QACb,CAAC,YAED,KAAC,IAAI,IACH,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,gBAAgB,kBAEf,QAAQ,GACJ,GACG,EACZ,WAAW,CACZ,CAAA;AACH,CAAC,CAAA","sourcesContent":["import { type ReactNode } from \"react\"\nimport {\n createPortal,\n GtkWindow,\n useApplication,\n useParentWindow,\n} from \"../gtkx/bridge/index\"\nimport { Root } from \"./root\"\n\nexport type ModalProps = {\n visible?: boolean\n onRequestClose?: () => void\n title?: string\n // Desktop mapping: RN modals become modal windows; transparent/animationType\n // have no GTK equivalent and are accepted as no-ops for API parity.\n transparent?: boolean\n animationType?: \"none\" | \"slide\" | \"fade\"\n width?: number\n height?: number\n children?: ReactNode\n}\n\n// RN Modal → a modal GtkWindow portaled onto the application, transient for\n// the window that rendered it. Visibility is fully controlled by the prop:\n// the close request is reported and swallowed.\nexport const Modal = ({\n visible = false,\n onRequestClose,\n title = \"\",\n width = 480,\n height = 400,\n children,\n}: ModalProps) => {\n const application = useApplication()\n const parentWindow = useParentWindow()\n\n if (!visible) {\n return null\n }\n\n return createPortal(\n <GtkWindow\n title={title}\n modal\n transientFor={parentWindow ?? undefined}\n defaultWidth={width}\n defaultHeight={height}\n onCloseRequest={() => {\n onRequestClose?.()\n return true\n }}\n >\n <Root\n width={width}\n height={height}\n followAllocation\n >\n {children}\n </Root>\n </GtkWindow>,\n application,\n )\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { StyleProp } from "../contracts";
|
|
3
|
+
import { type LayoutEvent } from "./use-layout-child";
|
|
4
|
+
export type PressableStateCallbackType = {
|
|
5
|
+
pressed: boolean;
|
|
6
|
+
hovered: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type PressEvent = {
|
|
9
|
+
nativeEvent: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type PressableProps = {
|
|
15
|
+
style?: StyleProp | ((state: PressableStateCallbackType) => StyleProp);
|
|
16
|
+
children?: ReactNode | ((state: PressableStateCallbackType) => ReactNode);
|
|
17
|
+
onPress?: (event: PressEvent) => void;
|
|
18
|
+
onPressIn?: (event: PressEvent) => void;
|
|
19
|
+
onPressOut?: (event: PressEvent) => void;
|
|
20
|
+
onLongPress?: (event: PressEvent) => void;
|
|
21
|
+
onHoverIn?: () => void;
|
|
22
|
+
onHoverOut?: () => void;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
delayLongPress?: number;
|
|
25
|
+
onLayout?: (event: LayoutEvent) => void;
|
|
26
|
+
testID?: string;
|
|
27
|
+
};
|
|
28
|
+
export declare const Pressable: ({ style, children, onPress, onPressIn, onPressOut, onLongPress, onHoverIn, onHoverOut, disabled, delayLongPress, onLayout, testID, }: PressableProps) => React.JSX.Element;
|
|
29
|
+
export type TouchableOpacityProps = Omit<PressableProps, "style"> & {
|
|
30
|
+
style?: StyleProp;
|
|
31
|
+
activeOpacity?: number;
|
|
32
|
+
};
|
|
33
|
+
export declare const TouchableOpacity: ({ style, activeOpacity, ...rest }: TouchableOpacityProps) => React.JSX.Element;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { Gtk, GtkBox } from "../gtkx/bridge/index";
|
|
4
|
+
import { HostNodeContext } from "./host-node";
|
|
5
|
+
import { useLayoutChild, useRnContainer, } from "./use-layout-child";
|
|
6
|
+
const pressEvent = (x, y) => ({
|
|
7
|
+
nativeEvent: { x, y },
|
|
8
|
+
});
|
|
9
|
+
// A View with a click gesture and hover tracking. State-dependent style and
|
|
10
|
+
// children follow the RN Pressable function-prop contract.
|
|
11
|
+
export const Pressable = ({ style, children, onPress, onPressIn, onPressOut, onLongPress, onHoverIn, onHoverOut, disabled = false, delayLongPress = 500, onLayout, testID, }) => {
|
|
12
|
+
const widgetRef = useRef(null);
|
|
13
|
+
const [pressed, setPressed] = useState(false);
|
|
14
|
+
const [hovered, setHovered] = useState(false);
|
|
15
|
+
const longPressTimer = useRef(null);
|
|
16
|
+
const longPressFired = useRef(false);
|
|
17
|
+
const state = { pressed, hovered };
|
|
18
|
+
// RC1-WORKAROUND(controllers-as-children): see docs/gtkx-rc1-vs-main.md
|
|
19
|
+
// rc.1 has no controller-as-JSX-children support (main-only feature):
|
|
20
|
+
// gestures are attached imperatively; handlers read the latest props via a
|
|
21
|
+
// ref so the controllers are wired exactly once.
|
|
22
|
+
const handlersRef = useRef({
|
|
23
|
+
handlePressed: (n, x, y) => {
|
|
24
|
+
void n;
|
|
25
|
+
void x;
|
|
26
|
+
void y;
|
|
27
|
+
},
|
|
28
|
+
handleReleased: (n, x, y) => {
|
|
29
|
+
void n;
|
|
30
|
+
void x;
|
|
31
|
+
void y;
|
|
32
|
+
},
|
|
33
|
+
handleCancel: () => { },
|
|
34
|
+
handleEnter: () => { },
|
|
35
|
+
handleLeave: () => { },
|
|
36
|
+
});
|
|
37
|
+
const resolvedStyle = typeof style === "function" ? style(state) : style;
|
|
38
|
+
const resolvedChildren = typeof children === "function" ? children(state) : children;
|
|
39
|
+
const { host, node, cssClass } = useLayoutChild(widgetRef, {
|
|
40
|
+
style: resolvedStyle,
|
|
41
|
+
onLayout,
|
|
42
|
+
});
|
|
43
|
+
useRnContainer(widgetRef, node);
|
|
44
|
+
const clearLongPress = () => {
|
|
45
|
+
if (longPressTimer.current !== null) {
|
|
46
|
+
clearTimeout(longPressTimer.current);
|
|
47
|
+
longPressTimer.current = null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const handlePressed = (_n, x, y) => {
|
|
51
|
+
if (disabled) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
longPressFired.current = false;
|
|
55
|
+
setPressed(true);
|
|
56
|
+
onPressIn?.(pressEvent(x, y));
|
|
57
|
+
if (onLongPress) {
|
|
58
|
+
clearLongPress();
|
|
59
|
+
longPressTimer.current = setTimeout(() => {
|
|
60
|
+
longPressFired.current = true;
|
|
61
|
+
onLongPress(pressEvent(x, y));
|
|
62
|
+
}, delayLongPress);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const handleReleased = (_n, x, y) => {
|
|
66
|
+
if (disabled) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
clearLongPress();
|
|
70
|
+
setPressed(false);
|
|
71
|
+
onPressOut?.(pressEvent(x, y));
|
|
72
|
+
if (!longPressFired.current) {
|
|
73
|
+
onPress?.(pressEvent(x, y));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const handleCancel = () => {
|
|
77
|
+
clearLongPress();
|
|
78
|
+
setPressed(false);
|
|
79
|
+
};
|
|
80
|
+
handlersRef.current = {
|
|
81
|
+
handlePressed,
|
|
82
|
+
handleReleased,
|
|
83
|
+
handleCancel,
|
|
84
|
+
handleEnter: () => {
|
|
85
|
+
setHovered(true);
|
|
86
|
+
onHoverIn?.();
|
|
87
|
+
},
|
|
88
|
+
handleLeave: () => {
|
|
89
|
+
setHovered(false);
|
|
90
|
+
onHoverOut?.();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
useLayoutEffect(() => {
|
|
94
|
+
const widget = widgetRef.current;
|
|
95
|
+
if (!widget) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const click = new Gtk.GestureClick();
|
|
99
|
+
click.on("pressed", (n, x, y) => handlersRef.current.handlePressed(n, x, y));
|
|
100
|
+
click.on("released", (n, x, y) => handlersRef.current.handleReleased(n, x, y));
|
|
101
|
+
click.on("cancel", () => handlersRef.current.handleCancel());
|
|
102
|
+
widget.addController(click);
|
|
103
|
+
const motion = new Gtk.EventControllerMotion();
|
|
104
|
+
motion.on("enter", () => handlersRef.current.handleEnter());
|
|
105
|
+
motion.on("leave", () => handlersRef.current.handleLeave());
|
|
106
|
+
widget.addController(motion);
|
|
107
|
+
return () => {
|
|
108
|
+
widget.removeController(click);
|
|
109
|
+
widget.removeController(motion);
|
|
110
|
+
};
|
|
111
|
+
}, []);
|
|
112
|
+
return (_jsx(GtkBox, { ref: widgetRef, name: testID, cssClasses: cssClass ? [cssClass] : [], children: _jsx(HostNodeContext.Provider, { value: { engine: host.engine, node, widgetRef }, children: resolvedChildren }) }));
|
|
113
|
+
};
|
|
114
|
+
// Classic RN touchable: dims itself while pressed via widget opacity.
|
|
115
|
+
export const TouchableOpacity = ({ style, activeOpacity = 0.5, ...rest }) => (_jsx(Pressable, { ...rest, style: ({ pressed }) => [style, pressed && { opacity: activeOpacity }] }));
|
|
116
|
+
//# sourceMappingURL=pressable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pressable.js","sourceRoot":"","sources":["../../src/components/pressable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type ReactNode } from \"react\"\nimport type { StyleProp } from \"../contracts\"\nimport { Gtk, GtkBox } from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\nexport type PressableStateCallbackType = {\n pressed: boolean\n hovered: boolean\n}\n\nexport type PressEvent = { nativeEvent: { x: number; y: number } }\n\nexport type PressableProps = {\n style?: StyleProp | ((state: PressableStateCallbackType) => StyleProp)\n children?: ReactNode | ((state: PressableStateCallbackType) => ReactNode)\n onPress?: (event: PressEvent) => void\n onPressIn?: (event: PressEvent) => void\n onPressOut?: (event: PressEvent) => void\n onLongPress?: (event: PressEvent) => void\n onHoverIn?: () => void\n onHoverOut?: () => void\n disabled?: boolean\n delayLongPress?: number\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\nconst pressEvent = (x: number, y: number): PressEvent => ({\n nativeEvent: { x, y },\n})\n\n// A View with a click gesture and hover tracking. State-dependent style and\n// children follow the RN Pressable function-prop contract.\nexport const Pressable = ({\n style,\n children,\n onPress,\n onPressIn,\n onPressOut,\n onLongPress,\n onHoverIn,\n onHoverOut,\n disabled = false,\n delayLongPress = 500,\n onLayout,\n testID,\n}: PressableProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const [pressed, setPressed] = useState(false)\n const [hovered, setHovered] = useState(false)\n const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const longPressFired = useRef(false)\n\n const state: PressableStateCallbackType = { pressed, hovered }\n\n // RC1-WORKAROUND(controllers-as-children): see docs/gtkx-rc1-vs-main.md\n // rc.1 has no controller-as-JSX-children support (main-only feature):\n // gestures are attached imperatively; handlers read the latest props via a\n // ref so the controllers are wired exactly once.\n const handlersRef = useRef({\n handlePressed: (n: number, x: number, y: number): void => {\n void n\n void x\n void y\n },\n handleReleased: (n: number, x: number, y: number): void => {\n void n\n void x\n void y\n },\n handleCancel: (): void => {},\n handleEnter: (): void => {},\n handleLeave: (): void => {},\n })\n const resolvedStyle = typeof style === \"function\" ? style(state) : style\n const resolvedChildren =\n typeof children === \"function\" ? children(state) : children\n\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style: resolvedStyle,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n const clearLongPress = (): void => {\n if (longPressTimer.current !== null) {\n clearTimeout(longPressTimer.current)\n longPressTimer.current = null\n }\n }\n\n const handlePressed = (_n: number, x: number, y: number): void => {\n if (disabled) {\n return\n }\n longPressFired.current = false\n setPressed(true)\n onPressIn?.(pressEvent(x, y))\n if (onLongPress) {\n clearLongPress()\n longPressTimer.current = setTimeout(() => {\n longPressFired.current = true\n onLongPress(pressEvent(x, y))\n }, delayLongPress)\n }\n }\n\n const handleReleased = (_n: number, x: number, y: number): void => {\n if (disabled) {\n return\n }\n clearLongPress()\n setPressed(false)\n onPressOut?.(pressEvent(x, y))\n if (!longPressFired.current) {\n onPress?.(pressEvent(x, y))\n }\n }\n\n const handleCancel = (): void => {\n clearLongPress()\n setPressed(false)\n }\n\n handlersRef.current = {\n handlePressed,\n handleReleased,\n handleCancel,\n handleEnter: () => {\n setHovered(true)\n onHoverIn?.()\n },\n handleLeave: () => {\n setHovered(false)\n onHoverOut?.()\n },\n }\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const click = new Gtk.GestureClick()\n click.on(\"pressed\", (n: number, x: number, y: number) =>\n handlersRef.current.handlePressed(n, x, y),\n )\n click.on(\"released\", (n: number, x: number, y: number) =>\n handlersRef.current.handleReleased(n, x, y),\n )\n click.on(\"cancel\", () => handlersRef.current.handleCancel())\n widget.addController(click)\n\n const motion = new Gtk.EventControllerMotion()\n motion.on(\"enter\", () => handlersRef.current.handleEnter())\n motion.on(\"leave\", () => handlersRef.current.handleLeave())\n widget.addController(motion)\n\n return () => {\n widget.removeController(click)\n widget.removeController(motion)\n }\n }, [])\n\n return (\n <GtkBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {resolvedChildren}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\nexport type TouchableOpacityProps = Omit<PressableProps, \"style\"> & {\n style?: StyleProp\n activeOpacity?: number\n}\n\n// Classic RN touchable: dims itself while pressed via widget opacity.\nexport const TouchableOpacity = ({\n style,\n activeOpacity = 0.5,\n ...rest\n}: TouchableOpacityProps) => (\n <Pressable\n {...rest}\n style={({ pressed }) => [style, pressed && { opacity: activeOpacity }]}\n />\n)\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type StoredRect = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
export type StoredOffset = {
|
|
8
|
+
dx: number;
|
|
9
|
+
dy: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const setStoredRect: (widget: object, rect: StoredRect) => void;
|
|
12
|
+
export declare const getStoredRect: (widget: object) => StoredRect | undefined;
|
|
13
|
+
export declare const setStoredOffset: (widget: object, dx: number, dy: number) => void;
|
|
14
|
+
export declare const getStoredOffset: (widget: object) => StoredOffset;
|
|
15
|
+
export declare const beginAllocatePass: () => void;
|
|
16
|
+
export declare const endAllocatePass: () => void;
|
|
17
|
+
export declare const deferDuringAllocate: (widget: object, job: () => void) => boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const ZERO_OFFSET = { dx: 0, dy: 0 };
|
|
2
|
+
const rects = new WeakMap();
|
|
3
|
+
const offsets = new WeakMap();
|
|
4
|
+
export const setStoredRect = (widget, rect) => {
|
|
5
|
+
rects.set(widget, rect);
|
|
6
|
+
};
|
|
7
|
+
export const getStoredRect = (widget) => rects.get(widget);
|
|
8
|
+
export const setStoredOffset = (widget, dx, dy) => {
|
|
9
|
+
offsets.set(widget, { dx, dy });
|
|
10
|
+
};
|
|
11
|
+
export const getStoredOffset = (widget) => offsets.get(widget) ?? ZERO_OFFSET;
|
|
12
|
+
// While a window-root allocate() pass runs the engine synchronously, commit
|
|
13
|
+
// callbacks must not queue GTK resizes mid-pass — the pass itself is about to
|
|
14
|
+
// place everything. Queue jobs are deferred (deduped per widget) and run once
|
|
15
|
+
// the pass ends; commits whose rect did not change never re-defer, so the
|
|
16
|
+
// follow-up converges.
|
|
17
|
+
let allocatePassDepth = 0;
|
|
18
|
+
const deferredJobs = new Map();
|
|
19
|
+
export const beginAllocatePass = () => {
|
|
20
|
+
allocatePassDepth += 1;
|
|
21
|
+
};
|
|
22
|
+
export const endAllocatePass = () => {
|
|
23
|
+
allocatePassDepth -= 1;
|
|
24
|
+
if (allocatePassDepth === 0 && deferredJobs.size > 0) {
|
|
25
|
+
const jobs = [...deferredJobs.values()];
|
|
26
|
+
deferredJobs.clear();
|
|
27
|
+
for (const job of jobs) {
|
|
28
|
+
job();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
// Returns true when the job was deferred (an allocate pass is running).
|
|
33
|
+
export const deferDuringAllocate = (widget, job) => {
|
|
34
|
+
if (allocatePassDepth === 0) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
deferredJobs.set(widget, job);
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=rect-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rect-store.js","sourceRoot":"","sources":["../../src/components/rect-store.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,GAAiB,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;AAElD,MAAM,KAAK,GAAG,IAAI,OAAO,EAAsB,CAAA;AAC/C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAwB,CAAA;AAEnD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAc,EAAE,IAAgB,EAAQ,EAAE;IACtE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AACzB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAc,EAA0B,EAAE,CACtE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AAEnB,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAAc,EACd,EAAU,EACV,EAAU,EACJ,EAAE;IACR,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAgB,EAAE,CAC9D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,WAAW,CAAA;AAEpC,4EAA4E;AAC5E,8EAA8E;AAC9E,8EAA8E;AAC9E,0EAA0E;AAC1E,uBAAuB;AACvB,IAAI,iBAAiB,GAAG,CAAC,CAAA;AACzB,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAA;AAElD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAS,EAAE;IAC1C,iBAAiB,IAAI,CAAC,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE;IACxC,iBAAiB,IAAI,CAAC,CAAA;IACtB,IAAI,iBAAiB,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;QACvC,YAAY,CAAC,KAAK,EAAE,CAAA;QACpB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,GAAG,EAAE,CAAA;QACP,CAAC;IACH,CAAC;AACH,CAAC,CAAA;AAED,wEAAwE;AACxE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,MAAc,EACd,GAAe,EACN,EAAE;IACX,IAAI,iBAAiB,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7B,OAAO,IAAI,CAAA;AACb,CAAC,CAAA","sourcesContent":["// Committed engine rects and animated offsets, keyed by the CHILD widget.\n// Written by commit callbacks (use-layout-child) and by Animated bindings;\n// read by the parent container's RnGtkxLayout allocate() hook. Pure data —\n// no FFI here, so both writers and the bridge-side reader stay testable.\nexport type StoredRect = {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport type StoredOffset = { dx: number; dy: number }\n\nconst ZERO_OFFSET: StoredOffset = { dx: 0, dy: 0 }\n\nconst rects = new WeakMap<object, StoredRect>()\nconst offsets = new WeakMap<object, StoredOffset>()\n\nexport const setStoredRect = (widget: object, rect: StoredRect): void => {\n rects.set(widget, rect)\n}\n\nexport const getStoredRect = (widget: object): StoredRect | undefined =>\n rects.get(widget)\n\nexport const setStoredOffset = (\n widget: object,\n dx: number,\n dy: number,\n): void => {\n offsets.set(widget, { dx, dy })\n}\n\nexport const getStoredOffset = (widget: object): StoredOffset =>\n offsets.get(widget) ?? ZERO_OFFSET\n\n// While a window-root allocate() pass runs the engine synchronously, commit\n// callbacks must not queue GTK resizes mid-pass — the pass itself is about to\n// place everything. Queue jobs are deferred (deduped per widget) and run once\n// the pass ends; commits whose rect did not change never re-defer, so the\n// follow-up converges.\nlet allocatePassDepth = 0\nconst deferredJobs = new Map<object, () => void>()\n\nexport const beginAllocatePass = (): void => {\n allocatePassDepth += 1\n}\n\nexport const endAllocatePass = (): void => {\n allocatePassDepth -= 1\n if (allocatePassDepth === 0 && deferredJobs.size > 0) {\n const jobs = [...deferredJobs.values()]\n deferredJobs.clear()\n for (const job of jobs) {\n job()\n }\n }\n}\n\n// Returns true when the job was deferred (an allocate pass is running).\nexport const deferDuringAllocate = (\n widget: object,\n job: () => void,\n): boolean => {\n if (allocatePassDepth === 0) {\n return false\n }\n deferredJobs.set(widget, job)\n return true\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type RootProps = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
followAllocation?: boolean;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export declare const Root: ({ width, height, followAllocation, children, }: RootProps) => React.JSX.Element;
|