+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/CheckBox.vue b/source/src/components/InfiniteTable/components/CheckBox.vue
new file mode 100644
index 000000000..ef99e5710
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/CheckBox.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/InfiniteTableHeader/useInfiniteColumnFilterEditor.vue.ts b/source/src/components/InfiniteTable/components/InfiniteTableHeader/useInfiniteColumnFilterEditor.vue.ts
new file mode 100644
index 000000000..71f549acc
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/InfiniteTableHeader/useInfiniteColumnFilterEditor.vue.ts
@@ -0,0 +1,64 @@
+import { ref, computed, inject } from 'vue';
+
+// This is a Vue composable equivalent to the React hook
+// It provides the same interface for filter editors
+
+export interface FilterEditorContext {
+ ariaLabel: string;
+ value: any;
+ setValue: (value: T) => void;
+ className: string;
+ disabled: boolean;
+ columnApi?: any;
+ operator?: any;
+ operatorName?: string;
+ column?: any;
+ filterType?: any;
+ filterTypes?: any;
+ filtered?: boolean;
+ clearValue?: () => void;
+ removeColumnFilter?: () => void;
+}
+
+export function useInfiniteColumnFilterEditor(): FilterEditorContext {
+ // In a real implementation, these would be injected from parent context
+ // For now, providing a basic structure that matches the React hook interface
+
+ const value = ref();
+ const disabled = ref(false);
+ const column = inject('column', null);
+ const filterContext = inject('filterContext', null);
+
+ const setValue = (newValue: T) => {
+ value.value = newValue;
+ // In real implementation, this would update the filter state through context
+ // filterContext?.onChange?.(newValue);
+ };
+
+ const ariaLabel = computed(() => {
+ // In real implementation, this would use the column label from context
+ return `Filter for column`;
+ });
+
+ const className = computed(() => {
+ // These classes would come from the CSS imports - matching React version
+ return 'HeaderFilterEditor InfiniteTableColumnHeaderFilter__input';
+ });
+
+ return {
+ ariaLabel: ariaLabel.value,
+ value: value.value,
+ setValue,
+ className: className.value,
+ disabled: disabled.value,
+ columnApi: null,
+ operator: null,
+ operatorName: undefined,
+ column: column,
+ filterType: null,
+ filterTypes: null,
+ filtered: false,
+ clearValue: () => {},
+ removeColumnFilter: () => {}
+ };
+}
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/LoadMask.vue b/source/src/components/InfiniteTable/components/LoadMask.vue
new file mode 100644
index 000000000..8a2c176c9
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/LoadMask.vue
@@ -0,0 +1,31 @@
+
+
+
+
+ {{ children || 'Loading' }}
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/NumberFilterEditor.vue b/source/src/components/InfiniteTable/components/NumberFilterEditor.vue
new file mode 100644
index 000000000..bebbd59d9
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/NumberFilterEditor.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/ScrollbarPlaceholder.vue b/source/src/components/InfiniteTable/components/ScrollbarPlaceholder.vue
new file mode 100644
index 000000000..8abd28e96
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/ScrollbarPlaceholder.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/StringFilterEditor.vue b/source/src/components/InfiniteTable/components/StringFilterEditor.vue
new file mode 100644
index 000000000..46769c907
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/StringFilterEditor.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/icons/ArrowDown.vue b/source/src/components/InfiniteTable/components/icons/ArrowDown.vue
new file mode 100644
index 000000000..f6e47db8c
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/icons/ArrowDown.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/icons/ArrowUp.vue b/source/src/components/InfiniteTable/components/icons/ArrowUp.vue
new file mode 100644
index 000000000..1c06955c9
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/icons/ArrowUp.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/InfiniteTable/components/icons/Icon.vue b/source/src/components/InfiniteTable/components/icons/Icon.vue
new file mode 100644
index 000000000..c30ae4141
--- /dev/null
+++ b/source/src/components/InfiniteTable/components/icons/Icon.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/Menu/MenuItem.vue b/source/src/components/Menu/MenuItem.vue
new file mode 100644
index 000000000..88b750378
--- /dev/null
+++ b/source/src/components/Menu/MenuItem.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/ResizeObserver/index.vue b/source/src/components/ResizeObserver/index.vue
new file mode 100644
index 000000000..d5ac01247
--- /dev/null
+++ b/source/src/components/ResizeObserver/index.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/source/src/components/ResizeObserver/useResizeObserver.vue.ts b/source/src/components/ResizeObserver/useResizeObserver.vue.ts
new file mode 100644
index 000000000..b1baa64d2
--- /dev/null
+++ b/source/src/components/ResizeObserver/useResizeObserver.vue.ts
@@ -0,0 +1,107 @@
+import { ref, onMounted, onUnmounted, watch, Ref } from 'vue';
+import { Size, OnResizeFn } from '../types/Size';
+import { debounce } from '../utils/debounce';
+
+// Shared utility function - same as React version
+export const setupResizeObserver = (
+ node: HTMLElement,
+ callback: OnResizeFn,
+ config: { debounce?: number } = { debounce: 0 },
+): (() => void) => {
+ const debounceTime = config.debounce ?? 0;
+ const RO = (window as any).ResizeObserver;
+
+ const onResizeCallback = debounceTime
+ ? debounce(callback, { wait: debounceTime })
+ : callback;
+
+ const observer = new RO((entries: any[]) => {
+ const entry = entries[0];
+
+ let { width, height } = entry.contentRect;
+
+ if (entry.borderBoxSize?.[0]) {
+ height = entry.borderBoxSize[0].blockSize;
+ width = entry.borderBoxSize[0].inlineSize;
+ } else {
+ if (entry.borderBoxSize && entry.borderBoxSize.blockSize) {
+ height = entry.borderBoxSize.blockSize;
+ width = entry.borderBoxSize.inlineSize;
+ }
+ }
+
+ onResizeCallback({
+ width,
+ height,
+ });
+ });
+
+ observer.observe(node);
+
+ return () => {
+ observer.disconnect();
+ };
+};
+
+export function useResizeObserver(
+ targetRef: Ref,
+ callback: OnResizeFn,
+ config: { earlyAttach?: boolean; debounce?: number } = {
+ earlyAttach: false,
+ debounce: 0,
+ },
+) {
+ const sizeRef = ref({
+ width: 0,
+ height: 0,
+ });
+
+ let disconnect: (() => void) | null = null;
+
+ const setupObserver = (callback: OnResizeFn) => {
+ if (disconnect) {
+ disconnect();
+ disconnect = null;
+ }
+
+ if (targetRef.value) {
+ disconnect = setupResizeObserver(
+ targetRef.value,
+ (size) => {
+ size = {
+ width: Math.round(size.width),
+ height: Math.round(size.height),
+ };
+ const prevSize = sizeRef.value;
+ if (
+ prevSize.width !== size.width ||
+ prevSize.height !== size.height
+ ) {
+ sizeRef.value = size;
+ callback(size);
+ }
+ },
+ { debounce: config.debounce },
+ );
+ }
+ };
+
+ // Watch for element changes
+ watch(() => targetRef.value, () => {
+ setupObserver(callback);
+ }, { immediate: config.earlyAttach });
+
+ // Setup observer on mount if not early attach
+ if (!config.earlyAttach) {
+ onMounted(() => {
+ setupObserver(callback);
+ });
+ }
+
+ // Cleanup on unmount
+ onUnmounted(() => {
+ if (disconnect) {
+ disconnect();
+ }
+ });
+}
\ No newline at end of file
diff --git a/source/src/components/hooks/useEffectWithChanges.vue.ts b/source/src/components/hooks/useEffectWithChanges.vue.ts
new file mode 100644
index 000000000..a4f71be35
--- /dev/null
+++ b/source/src/components/hooks/useEffectWithChanges.vue.ts
@@ -0,0 +1,135 @@
+import { watch, ref, onMounted, Ref } from 'vue';
+
+export function useEffectWithChanges(
+ fn: (
+ changes: Record,
+ prevValues: Record,
+ ) => void | (() => void),
+ deps: Record,
+) {
+ const prevRef = ref({});
+ const oldValuesRef = ref>({});
+ const changesRef = ref>({} as Record);
+
+ let cleanup: (() => void) | void;
+
+ const watchSources = Object.keys(deps).map(key =>
+ typeof deps[key] === 'object' && deps[key] && 'value' in deps[key]
+ ? deps[key] as Ref
+ : ref(deps[key])
+ );
+
+ watch(watchSources, () => {
+ const changes: Record = {};
+ const oldValues: Record = {};
+
+ for (const k in deps) {
+ if (deps.hasOwnProperty(k)) {
+ if (deps[k] !== (prevRef.value as any)[k]) {
+ changes[k] = deps[k];
+ oldValues[k] = (prevRef.value as any)[k];
+ }
+ }
+ }
+
+ prevRef.value = deps;
+
+ if (Object.keys(changes).length !== 0) {
+ // Clean up previous effect
+ if (cleanup) {
+ cleanup();
+ }
+
+ cleanup = fn(changes, oldValues);
+ }
+ }, { immediate: false });
+
+ // Cleanup on unmount
+ return () => {
+ if (cleanup) {
+ cleanup();
+ }
+ };
+}
+
+export function useLayoutEffectWithChanges(
+ fn: (
+ changes: Record,
+ prevValues: Record,
+ ) => void | (() => void),
+ deps: Record,
+) {
+ // In Vue, we use immediate watch to simulate layout effect behavior
+ const prevRef = ref({});
+ const oldValuesRef = ref>({});
+ const changesRef = ref>({} as Record);
+
+ let cleanup: (() => void) | void;
+
+ const watchSources = Object.keys(deps).map(key =>
+ typeof deps[key] === 'object' && deps[key] && 'value' in deps[key]
+ ? deps[key] as Ref
+ : ref(deps[key])
+ );
+
+ watch(watchSources, () => {
+ const changes: Record = {};
+ const oldValues: Record = {};
+
+ for (const k in deps) {
+ if (deps.hasOwnProperty(k)) {
+ if (deps[k] !== (prevRef.value as any)[k]) {
+ changes[k] = deps[k];
+ oldValues[k] = (prevRef.value as any)[k];
+ }
+ }
+ }
+
+ prevRef.value = deps;
+
+ if (Object.keys(changes).length !== 0) {
+ // Clean up previous effect
+ if (cleanup) {
+ cleanup();
+ }
+
+ cleanup = fn(changes, oldValues);
+ }
+ }, { immediate: false, flush: 'sync' }); // sync flush for layout effect behavior
+
+ // Cleanup on unmount
+ return () => {
+ if (cleanup) {
+ cleanup();
+ }
+ };
+}
+
+export function useEffectWithObject(
+ fn: () => void | (() => void),
+ deps: Record,
+) {
+ let cleanup: (() => void) | void;
+
+ const watchSources = Object.keys(deps).map(key =>
+ typeof deps[key] === 'object' && deps[key] && 'value' in deps[key]
+ ? deps[key] as Ref
+ : ref(deps[key])
+ );
+
+ watch(watchSources, () => {
+ // Clean up previous effect
+ if (cleanup) {
+ cleanup();
+ }
+
+ cleanup = fn();
+ }, { immediate: true });
+
+ // Cleanup on unmount
+ return () => {
+ if (cleanup) {
+ cleanup();
+ }
+ };
+}
\ No newline at end of file
diff --git a/source/src/components/hooks/useLatest.vue.ts b/source/src/components/hooks/useLatest.vue.ts
new file mode 100644
index 000000000..8dae0bec1
--- /dev/null
+++ b/source/src/components/hooks/useLatest.vue.ts
@@ -0,0 +1,8 @@
+import { ref, Ref } from 'vue';
+
+export function useLatest(value: T): () => T {
+ const valueRef: Ref = ref(value) as Ref;
+ valueRef.value = value;
+
+ return () => valueRef.value;
+}
\ No newline at end of file
diff --git a/source/src/index.vue.ts b/source/src/index.vue.ts
new file mode 100644
index 000000000..f68cfac9c
--- /dev/null
+++ b/source/src/index.vue.ts
@@ -0,0 +1,80 @@
+// Vue version exports - mirrors the React index.tsx but with Vue components
+export { debounce } from './utils/debounce';
+export * from './components/InfiniteTable';
+export * from './components/TreeGrid';
+
+export * from './components/DataSource';
+export { useDataSourceInternal } from './components/DataSource/privateHooks/useDataSource';
+export * from './components/DataSource/DataLoader/DataClient';
+
+export * from './components/Menu';
+export * from './components/Menu/MenuProps';
+
+export * from './components/hooks/useOverlay';
+export * from './components/hooks/useEffectWithChanges';
+
+// Vue components
+import LoadMaskVue from './components/InfiniteTable/components/LoadMask.vue';
+import CheckBoxVue from './components/InfiniteTable/components/CheckBox.vue';
+import StringFilterEditorVue from './components/InfiniteTable/components/StringFilterEditor.vue';
+import NumberFilterEditorVue from './components/InfiniteTable/components/NumberFilterEditor.vue';
+import ScrollbarPlaceholderVue from './components/InfiniteTable/components/ScrollbarPlaceholder.vue';
+import CSSNumericVariableWatchVue from './components/CSSNumericVariableWatch.vue';
+import ResizeObserverVue from './components/ResizeObserver/index.vue';
+import MenuItemVue from './components/Menu/MenuItem.vue';
+import IconVue from './components/InfiniteTable/components/icons/Icon.vue';
+import ArrowDownVue from './components/InfiniteTable/components/icons/ArrowDown.vue';
+import ArrowUpVue from './components/InfiniteTable/components/icons/ArrowUp.vue';
+
+// Import MenuIcon from React version for now
+import { MenuIcon } from './components/InfiniteTable/components/icons/MenuIcon';
+
+export { keyboardShortcuts } from './components/InfiniteTable/eventHandlers/keyboardShortcuts';
+export { type MenuIconProps } from './components/InfiniteTable/components/icons/MenuIcon';
+
+// Export Vue composables
+export { useResizeObserver } from './components/ResizeObserver/useResizeObserver.vue';
+export { useEffectWithChanges, useLayoutEffectWithChanges, useEffectWithObject } from './components/hooks/useEffectWithChanges.vue';
+export { useLatest } from './components/hooks/useLatest.vue';
+
+export const components = {
+ CheckBox: CheckBoxVue,
+ LoadMask: LoadMaskVue,
+ MenuIcon, // React version for now
+ StringFilterEditor: StringFilterEditorVue,
+ NumberFilterEditor: NumberFilterEditorVue,
+ ScrollbarPlaceholder: ScrollbarPlaceholderVue,
+ CSSNumericVariableWatch: CSSNumericVariableWatchVue,
+ ResizeObserver: ResizeObserverVue,
+ MenuItem: MenuItemVue,
+ Icon: IconVue,
+ ArrowDown: ArrowDownVue,
+ ArrowUp: ArrowUpVue,
+};
+
+export { group, flatten } from './utils/groupAndPivot';
+
+export {
+ useManagedComponentState as useComponentState,
+ buildManagedComponent as getComponentStateRoot,
+} from './components/hooks/useComponentState';
+
+export { interceptMap } from './components/hooks/useInterceptedMap';
+
+export { DeepMap } from './utils/DeepMap';
+export { FixedSizeSet } from './utils/FixedSizeSet';
+export { WeakFixedSizeSet } from './utils/WeakFixedSizeSet';
+export { debug, type DebugLogger } from './utils/debugPackage';
+
+export { useEffectWithChanges } from './components/hooks/useEffectWithChanges';
+
+export { useEffectWhenSameDeps } from './components/hooks/useEffectWhenSameDeps';
+export { useEffectWhen } from './components/hooks/useEffectWhen';
+export { usePrevious } from './components/hooks/usePrevious';
+
+export { defaultFilterTypes } from './components/DataSource/defaultFilterTypes';
+
+export {
+ createFlashingColumnCellComponent,
+ FlashingColumnCell,
+} from './components/InfiniteTable/components/InfiniteTableRow/FlashingColumnCell';
\ No newline at end of file