Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/solid/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export { useAskableStorageSource } from './useAskableStorageSource.js';
export { useAskableNotificationSource } from './useAskableNotificationSource.js';
export { useAskableCartSource } from './useAskableCartSource.js';
export { useAskableMultistepSource } from './useAskableMultistepSource.js';
export { useAskableRegionCapture } from './useAskableRegionCapture.js';
export { useAskableTextSelectionCapture } from './useAskableTextSelectionCapture.js';
4 changes: 4 additions & 0 deletions packages/solid/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export { useAskableTimeSource } from './useAskableTimeSource.js';
export { useAskableFocusSource } from './useAskableFocusSource.js';
export { useAskableMultistepSource } from './useAskableMultistepSource.js';
export { useAskableCartSource } from './useAskableCartSource.js';
export { useAskableRegionCapture } from './useAskableRegionCapture.js';
export { useAskableTextSelectionCapture } from './useAskableTextSelectionCapture.js';
export { useAskableStream } from './useAskableStream.js';
export { useAskableChat } from './useAskableChat.js';
// Re-export typed meta utility from core for convenience
Expand Down Expand Up @@ -86,6 +88,8 @@ export type { UseAskableTimeSourceOptions, UseAskableTimeSourceResult, AskableBu
export type { UseAskableFocusSourceOptions, UseAskableFocusSourceResult, AskableFocusedElementSnapshot, AskableFocusSourceSnapshot } from './useAskableFocusSource.js';
export type { UseAskableMultistepSourceOptions, UseAskableMultistepSourceResult, AskableMultistepStep, AskableMultistepSourceSnapshot } from './useAskableMultistepSource.js';
export type { UseAskableCartSourceOptions, UseAskableCartSourceResult, AskableCartItem, AskableCartSourceSnapshot, AskableCartTotals } from './useAskableCartSource.js';
export type { UseAskableRegionCaptureOptions, UseAskableRegionCaptureResult, AskableRegionCaptureSelection, AskableRegionCaptureState } from './useAskableRegionCapture.js';
export type { UseAskableTextSelectionCaptureOptions, UseAskableTextSelectionCaptureResult, AskableTextSelectionCaptureSelection, AskableTextSelectionCaptureState } from './useAskableTextSelectionCapture.js';
export type { UseAskableViewportOptions, UseAskableViewportResult } from './useAskableViewport.js';
export type { UseAskableHistoryOptions, UseAskableHistoryResult } from './useAskableHistory.js';
export type {
Expand Down
127 changes: 127 additions & 0 deletions packages/solid/src/useAskableRegionCapture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { createSignal, createEffect, onCleanup } from 'solid-js';
import { createAskableRegionCapture } from '@askable-ui/core';
import type {
AskableRegionCaptureHandle,
AskableRegionCaptureOptions,
AskableRegionCaptureSelection,
AskableRegionCaptureState,
WebContextPacket,
} from '@askable-ui/core';
import { useAskable, type UseAskableOptions } from './useAskable.js';

export type { AskableRegionCaptureOptions, AskableRegionCaptureSelection, AskableRegionCaptureState };

export interface UseAskableRegionCaptureOptions
extends AskableRegionCaptureOptions,
Omit<UseAskableOptions, 'inspector'> {}

export interface UseAskableRegionCaptureResult {
ctx: ReturnType<typeof useAskable>['ctx'];
active: () => boolean;
lastPacket: () => WebContextPacket | null;
lastSelection: () => AskableRegionCaptureSelection | null;
selectionState: () => AskableRegionCaptureState | null;
start(overrides?: Partial<AskableRegionCaptureOptions>): void;
cancel(): void;
clearSelection(): void;
getSelection(): AskableRegionCaptureState | null;
destroy(): void;
isActive(): boolean;
}

/**
* SolidJS primitive for rectangular / circle / lasso region capture.
*
* @example
* ```tsx
* const region = useAskableRegionCapture({ shape: 'rect' });
*
* return (
* <>
* <button onClick={() => region.start()}>Select region</button>
* <Show when={region.lastPacket()}>
* {(packet) => <p>Captured: {packet().text}</p>}
* </Show>
* </>
* );
* ```
*/
export function useAskableRegionCapture(
options: UseAskableRegionCaptureOptions = {},
): UseAskableRegionCaptureResult {
const { ctx } = useAskable(options);

let handle: AskableRegionCaptureHandle | null = null;
const [active, setActive] = createSignal(false);
const [lastPacket, setLastPacket] = createSignal<WebContextPacket | null>(null);
const [lastSelection, setLastSelection] = createSignal<AskableRegionCaptureSelection | null>(null);
const [selectionState, setSelectionState] = createSignal<AskableRegionCaptureState | null>(null);

createEffect(() => {
onCleanup(() => { handle?.destroy(); handle = null; });
});

function start(overrides?: Partial<AskableRegionCaptureOptions>): void {
handle?.destroy();
const merged = { ...options, ...overrides };
handle = createAskableRegionCapture(ctx, {
...merged,
onCapture(packet, selection) {
setLastPacket(() => packet);
setLastSelection(() => selection);
setActive(merged.once === false);
options.onCapture?.(packet, selection);
},
onSelectionChange(state) {
setSelectionState(() => state);
options.onSelectionChange?.(state);
},
onCancel() {
setActive(false);
options.onCancel?.();
},
});
handle.start();
setActive(true);
}

function cancel(): void {
handle?.cancel();
handle = null;
setActive(false);
setSelectionState(null);
}

function clearSelection(): void {
handle?.clearSelection();
}

function getSelection(): AskableRegionCaptureState | null {
return handle?.getSelection() ?? null;
}

function destroy(): void {
handle?.destroy();
handle = null;
setActive(false);
setSelectionState(null);
}

function isActive(): boolean {
return handle?.isActive() ?? active();
}

return {
ctx,
active,
lastPacket,
lastSelection,
selectionState,
start,
cancel,
clearSelection,
getSelection,
destroy,
isActive,
};
}
144 changes: 144 additions & 0 deletions packages/solid/src/useAskableTextSelectionCapture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { createSignal, createEffect, onCleanup } from 'solid-js';
import { createAskableTextSelectionCapture } from '@askable-ui/core';
import type {
AskableTextSelectionCaptureHandle,
AskableTextSelectionCaptureOptions,
AskableTextSelectionCaptureSelection,
AskableTextSelectionCaptureState,
WebContextPacket,
} from '@askable-ui/core';
import { useAskable, type UseAskableOptions } from './useAskable.js';

export type {
AskableTextSelectionCaptureOptions,
AskableTextSelectionCaptureSelection,
AskableTextSelectionCaptureState,
};

export interface UseAskableTextSelectionCaptureOptions
extends AskableTextSelectionCaptureOptions,
Omit<UseAskableOptions, 'inspector'> {}

export interface UseAskableTextSelectionCaptureResult {
ctx: ReturnType<typeof useAskable>['ctx'];
active: () => boolean;
lastPacket: () => WebContextPacket | null;
lastSelection: () => AskableTextSelectionCaptureSelection | null;
selectionState: () => AskableTextSelectionCaptureState | null;
start(overrides?: Partial<AskableTextSelectionCaptureOptions>): void;
captureNow(overrides?: Partial<AskableTextSelectionCaptureOptions>): WebContextPacket | null;
cancel(): void;
clearSelection(): void;
getSelection(): AskableTextSelectionCaptureState | null;
destroy(): void;
isActive(): boolean;
}

/**
* SolidJS primitive for capturing highlighted / selected text.
*
* @example
* ```tsx
* const sel = useAskableTextSelectionCapture();
* sel.start();
*
* return (
* <Show when={sel.lastPacket()}>
* {(packet) => <p>"{packet().text}"</p>}
* </Show>
* );
* ```
*/
export function useAskableTextSelectionCapture(
options: UseAskableTextSelectionCaptureOptions = {},
): UseAskableTextSelectionCaptureResult {
const { ctx } = useAskable(options);

let handle: AskableTextSelectionCaptureHandle | null = null;
const [active, setActive] = createSignal(false);
const [lastPacket, setLastPacket] = createSignal<WebContextPacket | null>(null);
const [lastSelection, setLastSelection] = createSignal<AskableTextSelectionCaptureSelection | null>(null);
const [selectionState, setSelectionState] = createSignal<AskableTextSelectionCaptureState | null>(null);

createEffect(() => {
onCleanup(() => { handle?.destroy(); handle = null; });
});

function ensureHandle(overrides?: Partial<AskableTextSelectionCaptureOptions>): AskableTextSelectionCaptureHandle {
handle?.destroy();
const merged = { ...options, ...overrides };
const h = createAskableTextSelectionCapture(ctx, {
...merged,
onCapture(packet, selection) {
setLastPacket(() => packet);
setLastSelection(() => selection);
if (merged.once) setActive(false);
options.onCapture?.(packet, selection);
},
onSelectionChange(state) {
setSelectionState(() => state);
options.onSelectionChange?.(state);
},
onCancel() {
handle = null;
setActive(false);
options.onCancel?.();
},
});
handle = h;
return h;
}

function start(overrides?: Partial<AskableTextSelectionCaptureOptions>): void {
ensureHandle(overrides).start();
setActive(true);
}

function captureNow(overrides?: Partial<AskableTextSelectionCaptureOptions>): WebContextPacket | null {
const h = handle ?? ensureHandle(overrides);
const packet = h.captureNow(overrides);
if (packet && (options.once || overrides?.once)) setActive(false);
return packet;
}

function cancel(): void {
handle?.cancel();
handle = null;
setActive(false);
setSelectionState(null);
}

function clearSelection(): void {
handle?.clearSelection();
}

function getSelection(): AskableTextSelectionCaptureState | null {
return handle?.getSelection() ?? null;
}

function destroy(): void {
handle?.destroy();
handle = null;
setActive(false);
setSelectionState(null);
}

function isActive(): boolean {
return handle?.isActive() ?? active();
}

return {
ctx,
active,
lastPacket,
lastSelection,
selectionState,
start,
captureNow,
cancel,
clearSelection,
getSelection,
destroy,
isActive,
};
}
4 changes: 4 additions & 0 deletions packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
"./useAskableFocusSource.svelte": "./src/useAskableFocusSource.svelte.ts",
"./useAskableMultistepSource.svelte": "./src/useAskableMultistepSource.svelte.ts",
"./useAskableCartSource.svelte": "./src/useAskableCartSource.svelte.ts",
"./useAskableRegionCapture.svelte": "./src/useAskableRegionCapture.svelte.ts",
"./useAskableTextSelectionCapture.svelte": "./src/useAskableTextSelectionCapture.svelte.ts",
"./core": "./src/core.svelte.ts",
"./extended": "./src/extended.svelte.ts"
},
Expand Down Expand Up @@ -103,6 +105,8 @@
"src/useAskableFocusSource.svelte.ts",
"src/useAskableMultistepSource.svelte.ts",
"src/useAskableCartSource.svelte.ts",
"src/useAskableRegionCapture.svelte.ts",
"src/useAskableTextSelectionCapture.svelte.ts",
"README.md",
"src/core.svelte.ts",
"src/extended.svelte.ts"
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/core.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export { useAskableStorageSource } from './useAskableStorageSource.svelte.ts';
export { useAskableNotificationSource } from './useAskableNotificationSource.svelte.ts';
export { useAskableCartSource } from './useAskableCartSource.svelte.ts';
export { useAskableMultistepSource } from './useAskableMultistepSource.svelte.ts';
export { useAskableRegionCapture } from './useAskableRegionCapture.svelte.ts';
export { useAskableTextSelectionCapture } from './useAskableTextSelectionCapture.svelte.ts';
Loading
Loading