@notis_ai/cli 0.2.0-beta.139.1 → 0.2.0-beta.145.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/README.md +44 -20
- package/dist/agent-hooks/notis-agent-hook.mjs +4579 -1449
- package/dist/base-skills/notis-apps/SKILL.md +83 -55
- package/dist/base-skills/notis-cli/SKILL.md +8 -8
- package/package.json +2 -2
- package/skills/notis-apps/cli.md +38 -17
- package/src/command-specs/apps.js +871 -387
- package/src/command-specs/auth.js +47 -10
- package/src/command-specs/onboarding.js +11 -4
- package/src/runtime/app-dev-consumers.js +154 -0
- package/src/runtime/app-dev-host-lock.js +80 -0
- package/src/runtime/app-dev-roots.js +284 -0
- package/src/runtime/app-dev-server.js +212 -22
- package/src/runtime/app-dev-sessions.js +105 -145
- package/src/runtime/app-platform.js +1291 -165
- package/src/runtime/app-registry-scaffolds.js +367 -0
- package/src/runtime/login-listener.js +15 -0
- package/src/runtime/oauth.js +1506 -141
- package/src/runtime/output.js +7 -0
- package/src/runtime/ports.js +16 -0
- package/src/runtime/profiles.js +0 -5
- package/template/app/layout.tsx +5 -1
- package/template/notis.config.ts +1 -0
- package/template/packages/sdk/package.json +2 -1
- package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +37 -119
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
- package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
- package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
- package/template/packages/sdk/src/config.ts +19 -0
- package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +709 -0
- package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
- package/template/packages/sdk/src/hooks/useTool.ts +5 -4
- package/template/packages/sdk/src/index.ts +47 -2
- package/template/packages/sdk/src/interactions/actions.ts +46 -0
- package/template/packages/sdk/src/interactions/shortcuts.tsx +628 -0
- package/template/packages/sdk/src/interactions.ts +41 -0
- package/template/packages/sdk/src/provider.tsx +2 -1
- package/template/packages/sdk/src/runtime.ts +85 -3
- package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-database/app/globals.css +0 -44
- package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
- package/dist/scaffolds/notis-database/components.json +0 -20
- package/dist/scaffolds/notis-database/index.html +0 -12
- package/dist/scaffolds/notis-database/lib/types.ts +0 -132
- package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
- package/dist/scaffolds/notis-database/notis.config.ts +0 -73
- package/dist/scaffolds/notis-database/package-lock.json +0 -3935
- package/dist/scaffolds/notis-database/package.json +0 -32
- package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
- package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
- package/dist/scaffolds/notis-database/tsconfig.json +0 -23
- package/dist/scaffolds/notis-database/vite.config.ts +0 -22
- package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
- package/dist/scaffolds/notis-journal/app/globals.css +0 -37
- package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
- package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
- package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
- package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
- package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-journal/components.json +0 -20
- package/dist/scaffolds/notis-journal/index.html +0 -12
- package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
- package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
- package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
- package/dist/scaffolds/notis-journal/package.json +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
- package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
- package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
- package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
- package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
- package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-notes/app/globals.css +0 -3
- package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-notes/components.json +0 -20
- package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
- package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
- package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
- package/dist/scaffolds/notis-notes/package.json +0 -35
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
- package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
- package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
- package/dist/scaffolds/notis-random/README.md +0 -33
- package/dist/scaffolds/notis-random/app/globals.css +0 -11
- package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
- package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-random/app/page.tsx +0 -289
- package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
- package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
- package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
- package/dist/scaffolds/notis-random/components.json +0 -20
- package/dist/scaffolds/notis-random/index.html +0 -12
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
- package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
- package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
- package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
- package/dist/scaffolds/notis-random/notis.config.ts +0 -86
- package/dist/scaffolds/notis-random/package-lock.json +0 -4513
- package/dist/scaffolds/notis-random/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
- package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
- package/dist/scaffolds/notis-random/tsconfig.json +0 -23
- package/dist/scaffolds/notis-random/vite.config.ts +0 -11
- package/dist/scaffolds.json +0 -49
|
@@ -39,6 +39,9 @@ export interface UseHandoverResult {
|
|
|
39
39
|
* );
|
|
40
40
|
* ```
|
|
41
41
|
*
|
|
42
|
+
* Omit `prompt` to open a context-only composer, for example when a visible
|
|
43
|
+
* feedback button should let the user write in their own words.
|
|
44
|
+
*
|
|
42
45
|
* Pass `skill` to bind the work to a skill declared in `notis.config.ts`; the
|
|
43
46
|
* host rejects a key the app does not declare. `autoSend` is accepted for
|
|
44
47
|
* forward compatibility; today's hosts always return `drafted` and let the
|
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useCallback, type MouseEvent as ReactMouseEvent } from 'react';
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
useState,
|
|
9
|
-
type MouseEvent as ReactMouseEvent,
|
|
10
|
-
} from 'react';
|
|
5
|
+
useCollectionInteractions,
|
|
6
|
+
type CollectionInteractionController,
|
|
7
|
+
type SelectionMarqueeRect,
|
|
8
|
+
} from './useCollectionInteractions';
|
|
11
9
|
|
|
12
10
|
const ROW_ATTR = 'data-notis-row-id';
|
|
13
|
-
const INTERACTIVE_SELECTOR = 'button, a, input, textarea, select, [role="button"]';
|
|
14
11
|
|
|
15
|
-
export
|
|
16
|
-
left: number;
|
|
17
|
-
top: number;
|
|
18
|
-
width: number;
|
|
19
|
-
height: number;
|
|
20
|
-
}
|
|
12
|
+
export type DragRect = SelectionMarqueeRect;
|
|
21
13
|
|
|
22
14
|
export interface UseMultiSelectOptions<T> {
|
|
23
15
|
items: T[];
|
|
@@ -39,501 +31,71 @@ export interface MultiSelectController<T> {
|
|
|
39
31
|
selectedCount: number;
|
|
40
32
|
lastClickedId: string | null;
|
|
41
33
|
headId: string | null;
|
|
42
|
-
/** Viewport-fixed drag rectangle, or null when no drag is active. */
|
|
43
34
|
dragRect: DragRect | null;
|
|
44
|
-
|
|
45
35
|
isSelected: (id: string) => boolean;
|
|
46
36
|
getSelectedItems: () => T[];
|
|
47
|
-
|
|
48
37
|
toggle: (id: string) => void;
|
|
49
38
|
select: (ids: string[]) => void;
|
|
50
39
|
clear: () => void;
|
|
51
40
|
selectAll: () => void;
|
|
52
41
|
selectRange: (anchorId: string, targetId: string) => void;
|
|
53
42
|
setHead: (id: string | null) => void;
|
|
54
|
-
|
|
55
|
-
/** Click handler for a row's hover checkbox. Toggles, with shift-click range from lastClickedId. */
|
|
56
43
|
onCheckboxClick: (id: string) => (event: ReactMouseEvent) => void;
|
|
57
|
-
/** mousedown on a row body — handles Cmd/Ctrl+click toggle without opening detail. */
|
|
58
44
|
onRowMouseDown: (id: string) => (event: ReactMouseEvent) => void;
|
|
59
|
-
|
|
60
|
-
/** Spread on each selectable row so drag-select can find them. */
|
|
61
45
|
getRowProps: (id: string) => { [ROW_ATTR]: string };
|
|
62
|
-
/**
|
|
63
|
-
* Spread on each selectable row/card — bundles `getRowProps` (the drag-select
|
|
64
|
-
* attribute) with the Cmd/Ctrl+click `onMouseDown` handler in one spread.
|
|
65
|
-
*/
|
|
66
46
|
getItemProps: (id: string) => {
|
|
67
47
|
[ROW_ATTR]: string;
|
|
68
48
|
onMouseDown: (event: ReactMouseEvent) => void;
|
|
69
49
|
};
|
|
70
|
-
/** Spread onto `<MultiSelectCheckbox {...getCheckboxProps(id)} />`. */
|
|
71
50
|
getCheckboxProps: (id: string) => {
|
|
72
51
|
isSelected: boolean;
|
|
73
52
|
onClick: (event: ReactMouseEvent) => void;
|
|
74
53
|
};
|
|
75
|
-
|
|
76
|
-
getContainerProps: () => {
|
|
77
|
-
ref: (node: HTMLElement | null) => void;
|
|
78
|
-
onMouseDown: (event: ReactMouseEvent) => void;
|
|
79
|
-
onClickCapture: (event: ReactMouseEvent) => void;
|
|
80
|
-
style: { userSelect: 'none' };
|
|
81
|
-
};
|
|
54
|
+
getContainerProps: CollectionInteractionController<T>['getContainerProps'];
|
|
82
55
|
}
|
|
83
56
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
b: { left: number; top: number; right: number; bottom: number },
|
|
99
|
-
): boolean {
|
|
100
|
-
return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function useMultiSelect<T>(opts: UseMultiSelectOptions<T>): MultiSelectController<T> {
|
|
104
|
-
const {
|
|
105
|
-
items,
|
|
106
|
-
getId,
|
|
107
|
-
bindKeyboardShortcuts = true,
|
|
108
|
-
enableDragSelect = true,
|
|
109
|
-
dragThreshold = 5,
|
|
110
|
-
initialHeadId = null,
|
|
111
|
-
onHeadChange,
|
|
112
|
-
} = opts;
|
|
113
|
-
|
|
114
|
-
const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());
|
|
115
|
-
const [lastClickedId, setLastClickedId] = useState<string | null>(null);
|
|
116
|
-
const [headId, setHeadIdState] = useState<string | null>(initialHeadId);
|
|
117
|
-
const [dragRect, setDragRect] = useState<DragRect | null>(null);
|
|
118
|
-
|
|
119
|
-
// Latest-state refs so document listeners and stable callbacks read fresh
|
|
120
|
-
// values without retriggering the effect.
|
|
121
|
-
const itemsRef = useRef(items);
|
|
122
|
-
const getIdRef = useRef(getId);
|
|
123
|
-
const selectedIdsRef = useRef(selectedIds);
|
|
124
|
-
const lastClickedIdRef = useRef(lastClickedId);
|
|
125
|
-
const headIdRef = useRef(headId);
|
|
126
|
-
const onHeadChangeRef = useRef(onHeadChange);
|
|
127
|
-
const containerRef = useRef<HTMLElement | null>(null);
|
|
128
|
-
|
|
129
|
-
itemsRef.current = items;
|
|
130
|
-
getIdRef.current = getId;
|
|
131
|
-
selectedIdsRef.current = selectedIds;
|
|
132
|
-
lastClickedIdRef.current = lastClickedId;
|
|
133
|
-
headIdRef.current = headId;
|
|
134
|
-
onHeadChangeRef.current = onHeadChange;
|
|
135
|
-
|
|
136
|
-
const orderedIds = useMemo(() => items.map((item) => getId(item)), [items, getId]);
|
|
137
|
-
const orderedIdsRef = useRef(orderedIds);
|
|
138
|
-
orderedIdsRef.current = orderedIds;
|
|
139
|
-
|
|
140
|
-
const isSelected = useCallback((id: string) => selectedIds.has(id), [selectedIds]);
|
|
141
|
-
|
|
142
|
-
const getSelectedItems = useCallback(() => {
|
|
143
|
-
const set = selectedIdsRef.current;
|
|
144
|
-
return itemsRef.current.filter((item) => set.has(getIdRef.current(item)));
|
|
145
|
-
}, []);
|
|
146
|
-
|
|
147
|
-
const setHead = useCallback((id: string | null) => {
|
|
148
|
-
setHeadIdState((prev) => {
|
|
149
|
-
if (prev === id) return prev;
|
|
150
|
-
onHeadChangeRef.current?.(id);
|
|
151
|
-
return id;
|
|
152
|
-
});
|
|
153
|
-
}, []);
|
|
154
|
-
|
|
155
|
-
const toggle = useCallback((id: string) => {
|
|
156
|
-
setSelectedIds((prev) => {
|
|
157
|
-
const next = new Set(prev);
|
|
158
|
-
if (next.has(id)) {
|
|
159
|
-
next.delete(id);
|
|
160
|
-
} else {
|
|
161
|
-
next.add(id);
|
|
162
|
-
}
|
|
163
|
-
return next;
|
|
164
|
-
});
|
|
165
|
-
setLastClickedId(id);
|
|
166
|
-
}, []);
|
|
167
|
-
|
|
168
|
-
const select = useCallback((ids: string[]) => {
|
|
169
|
-
setSelectedIds(new Set(ids));
|
|
170
|
-
setLastClickedId(ids.length > 0 ? ids[ids.length - 1] ?? null : null);
|
|
171
|
-
}, []);
|
|
172
|
-
|
|
173
|
-
const clear = useCallback(() => {
|
|
174
|
-
setSelectedIds((prev) => (prev.size === 0 ? prev : new Set()));
|
|
175
|
-
}, []);
|
|
176
|
-
|
|
177
|
-
const selectAll = useCallback(() => {
|
|
178
|
-
const all = orderedIdsRef.current;
|
|
179
|
-
setSelectedIds(new Set(all));
|
|
180
|
-
setLastClickedId(all.length > 0 ? all[all.length - 1] ?? null : null);
|
|
181
|
-
}, []);
|
|
182
|
-
|
|
183
|
-
const selectRange = useCallback((anchorId: string, targetId: string) => {
|
|
184
|
-
const all = orderedIdsRef.current;
|
|
185
|
-
const anchorIdx = all.indexOf(anchorId);
|
|
186
|
-
const targetIdx = all.indexOf(targetId);
|
|
187
|
-
if (anchorIdx === -1 || targetIdx === -1) {
|
|
188
|
-
setSelectedIds(new Set([targetId]));
|
|
189
|
-
setLastClickedId(targetId);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const start = Math.min(anchorIdx, targetIdx);
|
|
193
|
-
const end = Math.max(anchorIdx, targetIdx);
|
|
194
|
-
const range = all.slice(start, end + 1);
|
|
195
|
-
setSelectedIds((prev) => {
|
|
196
|
-
const next = new Set(prev);
|
|
197
|
-
for (const id of range) next.add(id);
|
|
198
|
-
return next;
|
|
199
|
-
});
|
|
200
|
-
setLastClickedId(targetId);
|
|
201
|
-
}, []);
|
|
202
|
-
|
|
203
|
-
const onCheckboxClick = useCallback(
|
|
204
|
-
(id: string) =>
|
|
205
|
-
(event: ReactMouseEvent) => {
|
|
206
|
-
event.stopPropagation();
|
|
207
|
-
if (event.shiftKey && lastClickedIdRef.current && lastClickedIdRef.current !== id) {
|
|
208
|
-
selectRange(lastClickedIdRef.current, id);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
toggle(id);
|
|
212
|
-
},
|
|
213
|
-
[selectRange, toggle],
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
const onRowMouseDown = useCallback(
|
|
217
|
-
(id: string) =>
|
|
218
|
-
(event: ReactMouseEvent) => {
|
|
219
|
-
if (event.button !== 0) return;
|
|
220
|
-
if (!(event.metaKey || event.ctrlKey)) return;
|
|
221
|
-
const target = event.target as HTMLElement | null;
|
|
222
|
-
if (target && target.closest(INTERACTIVE_SELECTOR)) return;
|
|
223
|
-
event.preventDefault();
|
|
224
|
-
event.stopPropagation();
|
|
225
|
-
toggle(id);
|
|
226
|
-
// The trailing click on the row should NOT open it.
|
|
227
|
-
armSuppressNextClick();
|
|
228
|
-
},
|
|
229
|
-
[toggle],
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
const getRowProps = useCallback(
|
|
233
|
-
(id: string) => ({
|
|
234
|
-
[ROW_ATTR]: id,
|
|
235
|
-
}),
|
|
236
|
-
[],
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
const getItemProps = useCallback(
|
|
240
|
-
(id: string) => ({
|
|
241
|
-
[ROW_ATTR]: id,
|
|
242
|
-
onMouseDown: onRowMouseDown(id),
|
|
243
|
-
}),
|
|
244
|
-
[onRowMouseDown],
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
const getCheckboxProps = useCallback(
|
|
248
|
-
(id: string) => ({
|
|
249
|
-
isSelected: selectedIdsRef.current.has(id),
|
|
250
|
-
onClick: onCheckboxClick(id),
|
|
251
|
-
}),
|
|
252
|
-
[onCheckboxClick],
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
// ---------------------------------------------------------------------------
|
|
256
|
-
// Drag-select
|
|
257
|
-
// ---------------------------------------------------------------------------
|
|
258
|
-
|
|
259
|
-
const dragPendingRef = useRef(false);
|
|
260
|
-
const dragActiveRef = useRef(false);
|
|
261
|
-
const dragStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
262
|
-
const dragCurrentRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
263
|
-
// Tracks whether the most recent mousedown originated outside any row, which
|
|
264
|
-
// means a bare click should clear the selection.
|
|
265
|
-
const clickShouldClearRef = useRef(false);
|
|
266
|
-
// Set true after a gesture that should NOT trigger the row's onClick: an
|
|
267
|
-
// active drag-select, or a cmd/ctrl-click toggle. The container's
|
|
268
|
-
// onClickCapture consumes the next click before it reaches the row.
|
|
269
|
-
const suppressNextClickRef = useRef(false);
|
|
270
|
-
|
|
271
|
-
const armSuppressNextClick = () => {
|
|
272
|
-
suppressNextClickRef.current = true;
|
|
273
|
-
setTimeout(() => {
|
|
274
|
-
suppressNextClickRef.current = false;
|
|
275
|
-
}, 0);
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const computeDragRect = useCallback((): DragRect => {
|
|
279
|
-
const start = dragStartRef.current;
|
|
280
|
-
const current = dragCurrentRef.current;
|
|
281
|
-
return {
|
|
282
|
-
left: Math.min(start.x, current.x),
|
|
283
|
-
top: Math.min(start.y, current.y),
|
|
284
|
-
width: Math.abs(current.x - start.x),
|
|
285
|
-
height: Math.abs(current.y - start.y),
|
|
286
|
-
};
|
|
287
|
-
}, []);
|
|
288
|
-
|
|
289
|
-
const computeIntersectingIds = useCallback(
|
|
290
|
-
(rect: { left: number; top: number; right: number; bottom: number }): string[] => {
|
|
291
|
-
const container = containerRef.current;
|
|
292
|
-
if (!container) return [];
|
|
293
|
-
const nodes = container.querySelectorAll<HTMLElement>(`[${ROW_ATTR}]`);
|
|
294
|
-
const ids: string[] = [];
|
|
295
|
-
nodes.forEach((node) => {
|
|
296
|
-
const rowRect = node.getBoundingClientRect();
|
|
297
|
-
if (rectsIntersect(rect, rowRect)) {
|
|
298
|
-
const id = node.getAttribute(ROW_ATTR);
|
|
299
|
-
if (id) ids.push(id);
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
return ids;
|
|
303
|
-
},
|
|
304
|
-
[],
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
const setContainerRef = useCallback(
|
|
308
|
-
(node: HTMLElement | null) => {
|
|
309
|
-
containerRef.current = node;
|
|
310
|
-
},
|
|
311
|
-
[],
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
const handleContainerMouseDown = useCallback(
|
|
315
|
-
(event: ReactMouseEvent) => {
|
|
316
|
-
if (event.button !== 0) return;
|
|
317
|
-
const target = event.target as HTMLElement | null;
|
|
318
|
-
if (!target) return;
|
|
319
|
-
// Cmd/Ctrl-click on a row is handled by `onRowMouseDown` (which calls
|
|
320
|
-
// stopPropagation), so we never reach here for that case. Skip if the
|
|
321
|
-
// mousedown lands on an interactive element so its native focus/text
|
|
322
|
-
// behavior is preserved.
|
|
323
|
-
if (target.closest(INTERACTIVE_SELECTOR)) {
|
|
324
|
-
clickShouldClearRef.current = false;
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
const isOnRow = Boolean(target.closest(`[${ROW_ATTR}]`));
|
|
328
|
-
// A click on empty space (not on a row) should clear the selection.
|
|
329
|
-
clickShouldClearRef.current = !isOnRow;
|
|
330
|
-
if (!enableDragSelect) return;
|
|
331
|
-
// Arm a possible drag from anywhere in the container — including row
|
|
332
|
-
// bodies. If the user moves past the threshold we activate a rubber-band
|
|
333
|
-
// selection AND suppress the trailing click on the row.
|
|
334
|
-
dragPendingRef.current = true;
|
|
335
|
-
dragStartRef.current = { x: event.clientX, y: event.clientY };
|
|
336
|
-
dragCurrentRef.current = { x: event.clientX, y: event.clientY };
|
|
337
|
-
},
|
|
338
|
-
[enableDragSelect],
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
// Capture-phase click handler intercepts the trailing click after a drag so
|
|
342
|
-
// the row's bubble-phase onClick (which would open the row) never fires.
|
|
343
|
-
const handleContainerClickCapture = useCallback(
|
|
344
|
-
(event: ReactMouseEvent) => {
|
|
345
|
-
if (suppressNextClickRef.current) {
|
|
346
|
-
suppressNextClickRef.current = false;
|
|
347
|
-
event.stopPropagation();
|
|
348
|
-
event.preventDefault();
|
|
349
|
-
}
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated Use `useCollectionInteractions` for controlled selection,
|
|
59
|
+
* active-item navigation, scoped shortcuts, and semantic actions. This bridge
|
|
60
|
+
* is retained for the 0.2 minor and scheduled for removal in 0.3.
|
|
61
|
+
*/
|
|
62
|
+
export function useMultiSelect<T>(options: UseMultiSelectOptions<T>): MultiSelectController<T> {
|
|
63
|
+
const controller = useCollectionInteractions({
|
|
64
|
+
...options,
|
|
65
|
+
defaultActiveId: options.initialHeadId,
|
|
66
|
+
onActiveItemChange: options.onHeadChange,
|
|
67
|
+
shortcuts: {
|
|
68
|
+
next: false,
|
|
69
|
+
previous: false,
|
|
70
|
+
activate: false,
|
|
350
71
|
},
|
|
351
|
-
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
// Attach document mousemove/mouseup while a drag is pending or active.
|
|
355
|
-
useEffect(() => {
|
|
356
|
-
if (!enableDragSelect) return;
|
|
357
|
-
|
|
358
|
-
const handleMouseMove = (event: MouseEvent) => {
|
|
359
|
-
if (dragPendingRef.current) {
|
|
360
|
-
const dx = event.clientX - dragStartRef.current.x;
|
|
361
|
-
const dy = event.clientY - dragStartRef.current.y;
|
|
362
|
-
if (Math.abs(dx) > dragThreshold || Math.abs(dy) > dragThreshold) {
|
|
363
|
-
dragPendingRef.current = false;
|
|
364
|
-
dragActiveRef.current = true;
|
|
365
|
-
window.getSelection()?.removeAllRanges();
|
|
366
|
-
dragCurrentRef.current = { x: event.clientX, y: event.clientY };
|
|
367
|
-
setDragRect(computeDragRect());
|
|
368
|
-
}
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
if (!dragActiveRef.current) return;
|
|
372
|
-
event.preventDefault();
|
|
373
|
-
dragCurrentRef.current = { x: event.clientX, y: event.clientY };
|
|
374
|
-
const rect = computeDragRect();
|
|
375
|
-
setDragRect(rect);
|
|
376
|
-
const ids = computeIntersectingIds({
|
|
377
|
-
left: rect.left,
|
|
378
|
-
top: rect.top,
|
|
379
|
-
right: rect.left + rect.width,
|
|
380
|
-
bottom: rect.top + rect.height,
|
|
381
|
-
});
|
|
382
|
-
setSelectedIds(new Set(ids));
|
|
383
|
-
setLastClickedId(ids.length > 0 ? ids[ids.length - 1] ?? null : null);
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
const handleMouseUp = () => {
|
|
387
|
-
const wasPending = dragPendingRef.current;
|
|
388
|
-
const wasActive = dragActiveRef.current;
|
|
389
|
-
dragPendingRef.current = false;
|
|
390
|
-
dragActiveRef.current = false;
|
|
391
|
-
if (wasActive) {
|
|
392
|
-
setDragRect(null);
|
|
393
|
-
clickShouldClearRef.current = false;
|
|
394
|
-
// The trailing click on the row should not open it.
|
|
395
|
-
armSuppressNextClick();
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
if (wasPending && clickShouldClearRef.current) {
|
|
399
|
-
// Bare click on empty space — clear the selection.
|
|
400
|
-
clickShouldClearRef.current = false;
|
|
401
|
-
if (selectedIdsRef.current.size > 0) {
|
|
402
|
-
setSelectedIds(new Set());
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
408
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
409
|
-
return () => {
|
|
410
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
411
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
412
|
-
};
|
|
413
|
-
}, [computeDragRect, computeIntersectingIds, dragThreshold, enableDragSelect]);
|
|
414
|
-
|
|
415
|
-
// ---------------------------------------------------------------------------
|
|
416
|
-
// Keyboard shortcuts
|
|
417
|
-
// ---------------------------------------------------------------------------
|
|
418
|
-
|
|
419
|
-
useEffect(() => {
|
|
420
|
-
if (!bindKeyboardShortcuts) return;
|
|
421
|
-
|
|
422
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
423
|
-
if (isEditableEvent(event)) return;
|
|
424
|
-
|
|
425
|
-
// Esc — clear selection
|
|
426
|
-
if (event.key === 'Escape') {
|
|
427
|
-
if (selectedIdsRef.current.size > 0) {
|
|
428
|
-
event.preventDefault();
|
|
429
|
-
setSelectedIds(new Set());
|
|
430
|
-
}
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Cmd/Ctrl + A — select all
|
|
435
|
-
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey) {
|
|
436
|
-
const key = event.key.toLowerCase();
|
|
437
|
-
if (key === 'a') {
|
|
438
|
-
if (orderedIdsRef.current.length === 0) return;
|
|
439
|
-
event.preventDefault();
|
|
440
|
-
const all = orderedIdsRef.current;
|
|
441
|
-
setSelectedIds(new Set(all));
|
|
442
|
-
setLastClickedId(all[all.length - 1] ?? null);
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// X — toggle the focused/head row
|
|
448
|
-
if (
|
|
449
|
-
event.key === 'x' &&
|
|
450
|
-
!event.shiftKey &&
|
|
451
|
-
!event.metaKey &&
|
|
452
|
-
!event.ctrlKey &&
|
|
453
|
-
!event.altKey
|
|
454
|
-
) {
|
|
455
|
-
const targetId = headIdRef.current ?? lastClickedIdRef.current;
|
|
456
|
-
if (!targetId) return;
|
|
457
|
-
event.preventDefault();
|
|
458
|
-
toggle(targetId);
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Shift + ArrowUp / ArrowDown — extend range from anchor
|
|
463
|
-
if (
|
|
464
|
-
(event.key === 'ArrowDown' || event.key === 'ArrowUp') &&
|
|
465
|
-
event.shiftKey &&
|
|
466
|
-
!event.metaKey &&
|
|
467
|
-
!event.ctrlKey &&
|
|
468
|
-
!event.altKey
|
|
469
|
-
) {
|
|
470
|
-
const all = orderedIdsRef.current;
|
|
471
|
-
if (all.length === 0) return;
|
|
472
|
-
event.preventDefault();
|
|
473
|
-
const direction = event.key === 'ArrowDown' ? 1 : -1;
|
|
474
|
-
const currentHead = headIdRef.current;
|
|
475
|
-
const currentIdx = currentHead ? all.indexOf(currentHead) : -1;
|
|
476
|
-
const newIdx =
|
|
477
|
-
currentIdx === -1
|
|
478
|
-
? direction === 1
|
|
479
|
-
? 0
|
|
480
|
-
: all.length - 1
|
|
481
|
-
: Math.min(all.length - 1, Math.max(0, currentIdx + direction));
|
|
482
|
-
if (newIdx === currentIdx) return;
|
|
483
|
-
const newHead = all[newIdx];
|
|
484
|
-
if (!newHead) return;
|
|
485
|
-
const anchorId = lastClickedIdRef.current ?? currentHead ?? newHead;
|
|
486
|
-
const anchorIdx = all.indexOf(anchorId);
|
|
487
|
-
if (anchorIdx === -1) {
|
|
488
|
-
setSelectedIds(new Set([newHead]));
|
|
489
|
-
} else {
|
|
490
|
-
const start = Math.min(anchorIdx, newIdx);
|
|
491
|
-
const end = Math.max(anchorIdx, newIdx);
|
|
492
|
-
setSelectedIds(new Set(all.slice(start, end + 1)));
|
|
493
|
-
}
|
|
494
|
-
if (lastClickedIdRef.current === null) {
|
|
495
|
-
setLastClickedId(anchorId);
|
|
496
|
-
}
|
|
497
|
-
setHead(newHead);
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
503
|
-
return () => {
|
|
504
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
505
|
-
};
|
|
506
|
-
}, [bindKeyboardShortcuts, setHead, toggle]);
|
|
72
|
+
});
|
|
507
73
|
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
style: { userSelect: 'none' as const },
|
|
514
|
-
}),
|
|
515
|
-
[handleContainerClickCapture, handleContainerMouseDown, setContainerRef],
|
|
516
|
-
);
|
|
74
|
+
const getRowProps = useCallback((id: string) => ({ [ROW_ATTR]: id }), []);
|
|
75
|
+
const getItemProps = useCallback((id: string) => ({
|
|
76
|
+
[ROW_ATTR]: id,
|
|
77
|
+
onMouseDown: controller.onRowMouseDown(id),
|
|
78
|
+
}), [controller.onRowMouseDown]);
|
|
517
79
|
|
|
518
80
|
return {
|
|
519
|
-
selectedIds,
|
|
520
|
-
selectedCount:
|
|
521
|
-
lastClickedId,
|
|
522
|
-
headId,
|
|
523
|
-
dragRect,
|
|
524
|
-
isSelected,
|
|
525
|
-
getSelectedItems,
|
|
526
|
-
toggle,
|
|
527
|
-
select,
|
|
528
|
-
clear,
|
|
529
|
-
selectAll,
|
|
530
|
-
selectRange,
|
|
531
|
-
setHead,
|
|
532
|
-
onCheckboxClick,
|
|
533
|
-
onRowMouseDown,
|
|
81
|
+
selectedIds: controller.selectedIds,
|
|
82
|
+
selectedCount: controller.selectedCount,
|
|
83
|
+
lastClickedId: controller.lastClickedId,
|
|
84
|
+
headId: controller.headId,
|
|
85
|
+
dragRect: controller.dragRect,
|
|
86
|
+
isSelected: controller.isSelected,
|
|
87
|
+
getSelectedItems: controller.getSelectedItems,
|
|
88
|
+
toggle: controller.toggle,
|
|
89
|
+
select: controller.select,
|
|
90
|
+
clear: controller.clear,
|
|
91
|
+
selectAll: controller.selectAll,
|
|
92
|
+
selectRange: controller.selectRange,
|
|
93
|
+
setHead: controller.setActive,
|
|
94
|
+
onCheckboxClick: controller.onCheckboxClick,
|
|
95
|
+
onRowMouseDown: controller.onRowMouseDown,
|
|
534
96
|
getRowProps,
|
|
535
97
|
getItemProps,
|
|
536
|
-
getCheckboxProps,
|
|
537
|
-
getContainerProps,
|
|
98
|
+
getCheckboxProps: controller.getCheckboxProps,
|
|
99
|
+
getContainerProps: controller.getContainerProps,
|
|
538
100
|
};
|
|
539
101
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState } from 'react';
|
|
4
4
|
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import type { ToolCallOptions } from '../runtime';
|
|
5
6
|
|
|
6
7
|
type ToolArguments = Record<string, unknown>;
|
|
7
8
|
|
|
8
9
|
type ToolCall<TArgs extends ToolArguments | undefined, TResult> = undefined extends TArgs
|
|
9
|
-
? (args?: Exclude<TArgs, undefined
|
|
10
|
-
: (args: TArgs) => Promise<TResult>;
|
|
10
|
+
? (args?: Exclude<TArgs, undefined>, options?: ToolCallOptions) => Promise<TResult>
|
|
11
|
+
: (args: TArgs, options?: ToolCallOptions) => Promise<TResult>;
|
|
11
12
|
|
|
12
13
|
export interface ToolCallState {
|
|
13
14
|
loading: boolean;
|
|
@@ -38,7 +39,7 @@ export function useTool<
|
|
|
38
39
|
const [error, setError] = useState<Error | null>(null);
|
|
39
40
|
|
|
40
41
|
const call = useCallback(
|
|
41
|
-
async (args?: ToolArguments): Promise<TResult> => {
|
|
42
|
+
async (args?: ToolArguments, options?: ToolCallOptions): Promise<TResult> => {
|
|
42
43
|
if (!runtime) {
|
|
43
44
|
throw new Error('Notis runtime not available. Ensure NotisProvider is mounted.');
|
|
44
45
|
}
|
|
@@ -47,7 +48,7 @@ export function useTool<
|
|
|
47
48
|
setError(null);
|
|
48
49
|
|
|
49
50
|
try {
|
|
50
|
-
const result = await runtime.callTool<TResult>(toolName, args);
|
|
51
|
+
const result = await runtime.callTool<TResult>(toolName, args, options);
|
|
51
52
|
return result;
|
|
52
53
|
} catch (err) {
|
|
53
54
|
const e = err instanceof Error ? err : new Error(String(err));
|