epiq 0.0.0
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/dist/app.d.ts +5 -0
- package/dist/app.js +12 -0
- package/dist/board/actions/board-action-map.d.ts +11 -0
- package/dist/board/actions/board-action-map.js +11 -0
- package/dist/board/actions/move-actions-routes.d.ts +4 -0
- package/dist/board/actions/move-actions-routes.js +31 -0
- package/dist/board/actions/move-actions.d.ts +5 -0
- package/dist/board/actions/move-actions.js +49 -0
- package/dist/board/board-action-map.d.ts +9 -0
- package/dist/board/board-action-map.js +7 -0
- package/dist/board/components/Board.d.ts +6 -0
- package/dist/board/components/Board.js +19 -0
- package/dist/board/components/Help.d.ts +4 -0
- package/dist/board/components/Help.js +21 -0
- package/dist/board/components/Logo.d.ts +2 -0
- package/dist/board/components/Logo.js +21 -0
- package/dist/board/components/ScrollBox.d.ts +9 -0
- package/dist/board/components/ScrollBox.js +22 -0
- package/dist/board/components/Swimlane.d.ts +8 -0
- package/dist/board/components/Swimlane.js +10 -0
- package/dist/board/components/Swimlanes.d.ts +8 -0
- package/dist/board/components/Swimlanes.js +20 -0
- package/dist/board/components/TicketListItem.d.ts +6 -0
- package/dist/board/components/TicketListItem.js +12 -0
- package/dist/board/components/TicketUI.d.ts +8 -0
- package/dist/board/components/TicketUI.js +9 -0
- package/dist/board/mock/board.d.ts +2 -0
- package/dist/board/mock/board.js +474 -0
- package/dist/board/model/board.model.d.ts +26 -0
- package/dist/board/model/board.model.js +6 -0
- package/dist/board/move-actions-routes.d.ts +4 -0
- package/dist/board/move-actions-routes.js +31 -0
- package/dist/board/move-actions.d.ts +5 -0
- package/dist/board/move-actions.js +49 -0
- package/dist/board/render/color.d.ts +7 -0
- package/dist/board/render/color.js +21 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +57 -0
- package/dist/components/Board.d.ts +6 -0
- package/dist/components/Board.js +15 -0
- package/dist/components/Help.d.ts +4 -0
- package/dist/components/Help.js +21 -0
- package/dist/components/Swimlane.d.ts +8 -0
- package/dist/components/Swimlane.js +10 -0
- package/dist/components/TicketListItem.d.ts +6 -0
- package/dist/components/TicketListItem.js +12 -0
- package/dist/lib/actions/board-action-map.d.ts +9 -0
- package/dist/lib/actions/board-action-map.js +7 -0
- package/dist/lib/actions/move-actions-utils.d.ts +5 -0
- package/dist/lib/actions/move-actions-utils.js +43 -0
- package/dist/lib/actions/move-actions.d.ts +4 -0
- package/dist/lib/actions/move-actions.js +31 -0
- package/dist/lib/board/board-action-map.d.ts +9 -0
- package/dist/lib/board/board-action-map.js +7 -0
- package/dist/lib/board/move-actions-routes.d.ts +4 -0
- package/dist/lib/board/move-actions-routes.js +31 -0
- package/dist/lib/board/move-actions.d.ts +5 -0
- package/dist/lib/board/move-actions.js +43 -0
- package/dist/lib/board-utils.d.ts +7 -0
- package/dist/lib/board-utils.js +33 -0
- package/dist/lib/default-actions-routes.d.ts +4 -0
- package/dist/lib/default-actions-routes.js +47 -0
- package/dist/lib/default-actions.d.ts +4 -0
- package/dist/lib/default-actions.js +47 -0
- package/dist/lib/navigation-context.d.ts +16 -0
- package/dist/lib/navigation-context.js +1 -0
- package/dist/lib/navigation.d.ts +10 -0
- package/dist/lib/navigation.js +74 -0
- package/dist/lib/render-utils.d.ts +10 -0
- package/dist/lib/render-utils.js +12 -0
- package/dist/lib/state.d.ts +8 -0
- package/dist/lib/state.js +8 -0
- package/dist/lib/types/action-map.model.d.ts +14 -0
- package/dist/lib/types/action-map.model.js +4 -0
- package/dist/lib/types/board.model.d.ts +21 -0
- package/dist/lib/types/board.model.js +5 -0
- package/dist/lib/types/navigation.model.d.ts +9 -0
- package/dist/lib/types/navigation.model.js +1 -0
- package/dist/lib/utils/array-utils.d.ts +5 -0
- package/dist/lib/utils/array-utils.js +7 -0
- package/dist/lib/utils/default-action-utils.d.ts +7 -0
- package/dist/lib/utils/default-action-utils.js +50 -0
- package/dist/lib/utils/default-actions.d.ts +7 -0
- package/dist/lib/utils/default-actions.js +50 -0
- package/dist/lib/utils/key-intent.d.ts +16 -0
- package/dist/lib/utils/key-intent.js +65 -0
- package/dist/lib/utils.d.ts +10 -0
- package/dist/lib/utils.js +12 -0
- package/dist/mock/board.d.ts +2 -0
- package/dist/mock/board.js +139 -0
- package/dist/navigation/actions/default-actions-routes.d.ts +4 -0
- package/dist/navigation/actions/default-actions-routes.js +47 -0
- package/dist/navigation/actions/default-actions.d.ts +7 -0
- package/dist/navigation/actions/default-actions.js +48 -0
- package/dist/navigation/model/action-map.model.d.ts +14 -0
- package/dist/navigation/model/action-map.model.js +4 -0
- package/dist/navigation/model/navigation-ctx.model.d.ts +16 -0
- package/dist/navigation/model/navigation-ctx.model.js +2 -0
- package/dist/navigation/model/navigation-tree.model.d.ts +9 -0
- package/dist/navigation/model/navigation-tree.model.js +1 -0
- package/dist/navigation/navigation.d.ts +10 -0
- package/dist/navigation/navigation.js +74 -0
- package/dist/navigation/state/state.d.ts +11 -0
- package/dist/navigation/state/state.js +10 -0
- package/dist/navigation/utils/key-intent.d.ts +16 -0
- package/dist/navigation/utils/key-intent.js +65 -0
- package/package.json +57 -0
- package/readme.md +37 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NavigateCtx } from './navigation-context.js';
|
|
2
|
+
import { ActionEntry, ModeOptions } from './types/action-map.model.js';
|
|
3
|
+
export declare let navigationState: {
|
|
4
|
+
mode: ModeOptions;
|
|
5
|
+
availableActions: ActionEntry<[NavigateCtx]>[];
|
|
6
|
+
viewHelp: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const setState: (cb: (oldState: typeof navigationState) => typeof navigationState) => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const Mode: {
|
|
2
|
+
readonly DEFAULT: "default";
|
|
3
|
+
readonly MOVE: "move";
|
|
4
|
+
};
|
|
5
|
+
export type ModeOptions = (typeof Mode)[keyof typeof Mode];
|
|
6
|
+
export type ActionEntry<TArgs extends any[] = []> = {
|
|
7
|
+
intent?: string;
|
|
8
|
+
mode: string;
|
|
9
|
+
description?: `[${string}] ${string}`;
|
|
10
|
+
action?: (...args: TArgs) => void;
|
|
11
|
+
};
|
|
12
|
+
export type ActionMap<T extends Record<string, any[]>> = {
|
|
13
|
+
[K in keyof T]: ActionEntry<T[K]>[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NavigationTree } from './navigation.model.js';
|
|
2
|
+
export declare const BoardItemTypes: {
|
|
3
|
+
readonly TICKET: "TICKET";
|
|
4
|
+
readonly SWIMLANE: "SWIMLANE";
|
|
5
|
+
readonly BOARD: "BOARD";
|
|
6
|
+
};
|
|
7
|
+
export type Ticket = NavigationTree<{
|
|
8
|
+
actionContext: (typeof BoardItemTypes)['TICKET'];
|
|
9
|
+
description?: string;
|
|
10
|
+
}>;
|
|
11
|
+
export type Swimlane = NavigationTree<{
|
|
12
|
+
actionContext: (typeof BoardItemTypes)['SWIMLANE'];
|
|
13
|
+
enableChildNavigationAcrossContainers: true;
|
|
14
|
+
}> & {
|
|
15
|
+
children: Ticket[];
|
|
16
|
+
};
|
|
17
|
+
export type Board = NavigationTree<{
|
|
18
|
+
actionContext: (typeof BoardItemTypes)['BOARD'];
|
|
19
|
+
}> & {
|
|
20
|
+
children: Swimlane[];
|
|
21
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type NavigationTree<TMeta = Record<string, unknown>> = {
|
|
2
|
+
id: string;
|
|
3
|
+
isSelected: boolean;
|
|
4
|
+
childrenRenderAxis: 'vertical' | 'horizontal';
|
|
5
|
+
name: string;
|
|
6
|
+
children: NavigationTree<unknown>[];
|
|
7
|
+
actionContext: string;
|
|
8
|
+
enableChildNavigationAcrossContainers?: boolean;
|
|
9
|
+
} & TMeta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NavigateCtx } from '../navigation-context.js';
|
|
2
|
+
export declare const navigateToNextItem: (ctx: NavigateCtx) => void;
|
|
3
|
+
export declare const navigateToPreviousItem: (ctx: NavigateCtx) => void;
|
|
4
|
+
export declare const navigateToNextContainer: (ctx: NavigateCtx) => void;
|
|
5
|
+
export declare const navigateToPreviousContainer: (ctx: NavigateCtx) => void;
|
|
6
|
+
export declare const enterChildNode: (ctx: NavigateCtx) => void;
|
|
7
|
+
export declare const exitToParentNode: (ctx: NavigateCtx) => void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// -- Basic item navigation --
|
|
2
|
+
const navigateByOffset = (ctx, offset) => {
|
|
3
|
+
const len = ctx.children.length;
|
|
4
|
+
const newIndex = (ctx.getSelectedIndex() + offset + len) % len;
|
|
5
|
+
ctx.select(newIndex);
|
|
6
|
+
};
|
|
7
|
+
export const navigateToNextItem = (ctx) => navigateByOffset(ctx, 1);
|
|
8
|
+
export const navigateToPreviousItem = (ctx) => navigateByOffset(ctx, -1);
|
|
9
|
+
// -- Cross-container navigation --
|
|
10
|
+
const navigateToSiblingContainer = (ctx, direction) => {
|
|
11
|
+
if (!ctx.navigationNode.enableChildNavigationAcrossContainers)
|
|
12
|
+
return;
|
|
13
|
+
const parent = ctx.breadCrumb.at(-1);
|
|
14
|
+
const grandParent = ctx.breadCrumb.at(-2);
|
|
15
|
+
if (!parent || !grandParent)
|
|
16
|
+
return;
|
|
17
|
+
const parentIndex = grandParent.children.findIndex(x => x.id === parent.id);
|
|
18
|
+
const sliced = direction === -1
|
|
19
|
+
? grandParent.children.toSpliced(parentIndex).toReversed()
|
|
20
|
+
: grandParent.children.slice(parentIndex + 1);
|
|
21
|
+
const nextSibling = sliced.find(x => x.children.length);
|
|
22
|
+
if (!nextSibling?.children?.length)
|
|
23
|
+
return;
|
|
24
|
+
const prevIndex = ctx.getSelectedIndex();
|
|
25
|
+
const boundedIndex = Math.min(prevIndex, nextSibling.children.length - 1);
|
|
26
|
+
ctx.selectNone();
|
|
27
|
+
ctx.reInvokeNavigate(boundedIndex, [
|
|
28
|
+
...ctx.breadCrumb.slice(0, -1),
|
|
29
|
+
nextSibling,
|
|
30
|
+
]);
|
|
31
|
+
};
|
|
32
|
+
export const navigateToNextContainer = (ctx) => navigateToSiblingContainer(ctx, 1);
|
|
33
|
+
export const navigateToPreviousContainer = (ctx) => navigateToSiblingContainer(ctx, -1);
|
|
34
|
+
export const enterChildNode = (ctx) => {
|
|
35
|
+
const current = ctx.navigationNode.children[ctx._selectedIndex];
|
|
36
|
+
if (!current)
|
|
37
|
+
return;
|
|
38
|
+
if (!current.children?.length) {
|
|
39
|
+
ctx.confirm(current);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
ctx.select(-1);
|
|
43
|
+
ctx.enterChildNode(current);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
export const exitToParentNode = (ctx) => {
|
|
47
|
+
if (ctx.breadCrumb.length >= 2) {
|
|
48
|
+
ctx.enterParentNode();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NavigateCtx } from '../navigation-context.js';
|
|
2
|
+
export declare const navigateToNextItem: (ctx: NavigateCtx) => void;
|
|
3
|
+
export declare const navigateToPreviousItem: (ctx: NavigateCtx) => void;
|
|
4
|
+
export declare const navigateToNextContainer: (ctx: NavigateCtx) => void;
|
|
5
|
+
export declare const navigateToPreviousContainer: (ctx: NavigateCtx) => void;
|
|
6
|
+
export declare const enterChildNode: (ctx: NavigateCtx) => void;
|
|
7
|
+
export declare const exitToParentNode: (ctx: NavigateCtx) => void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// -- Basic item navigation --
|
|
2
|
+
const navigateByOffset = (ctx, offset) => {
|
|
3
|
+
const len = ctx.children.length;
|
|
4
|
+
const newIndex = (ctx.getSelectedIndex() + offset + len) % len;
|
|
5
|
+
ctx.select(newIndex);
|
|
6
|
+
};
|
|
7
|
+
export const navigateToNextItem = (ctx) => navigateByOffset(ctx, 1);
|
|
8
|
+
export const navigateToPreviousItem = (ctx) => navigateByOffset(ctx, -1);
|
|
9
|
+
// -- Cross-container navigation --
|
|
10
|
+
const navigateToSiblingContainer = (ctx, direction) => {
|
|
11
|
+
if (!ctx.navigationNode.enableChildNavigationAcrossContainers)
|
|
12
|
+
return;
|
|
13
|
+
const parent = ctx.breadCrumb.at(-1);
|
|
14
|
+
const grandParent = ctx.breadCrumb.at(-2);
|
|
15
|
+
if (!parent || !grandParent)
|
|
16
|
+
return;
|
|
17
|
+
const parentIndex = grandParent.children.findIndex(x => x.id === parent.id);
|
|
18
|
+
const sliced = direction === -1
|
|
19
|
+
? grandParent.children.toSpliced(parentIndex).toReversed()
|
|
20
|
+
: grandParent.children.slice(parentIndex + 1);
|
|
21
|
+
const nextSibling = sliced.find(x => x.children.length);
|
|
22
|
+
if (!nextSibling?.children?.length)
|
|
23
|
+
return;
|
|
24
|
+
const prevIndex = ctx.getSelectedIndex();
|
|
25
|
+
const boundedIndex = Math.min(prevIndex, nextSibling.children.length - 1);
|
|
26
|
+
ctx.selectNone();
|
|
27
|
+
ctx.reInvokeNavigate(boundedIndex, [
|
|
28
|
+
...ctx.breadCrumb.slice(0, -1),
|
|
29
|
+
nextSibling,
|
|
30
|
+
]);
|
|
31
|
+
};
|
|
32
|
+
export const navigateToNextContainer = (ctx) => navigateToSiblingContainer(ctx, 1);
|
|
33
|
+
export const navigateToPreviousContainer = (ctx) => navigateToSiblingContainer(ctx, -1);
|
|
34
|
+
export const enterChildNode = (ctx) => {
|
|
35
|
+
const current = ctx.navigationNode.children[ctx._selectedIndex];
|
|
36
|
+
if (!current)
|
|
37
|
+
return;
|
|
38
|
+
if (!current.children?.length) {
|
|
39
|
+
ctx.confirm(current);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
ctx.select(-1);
|
|
43
|
+
ctx.enterChildNode(current);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
export const exitToParentNode = (ctx) => {
|
|
47
|
+
if (ctx.breadCrumb.length >= 2) {
|
|
48
|
+
ctx.enterParentNode();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import { NavigateCtx } from '../navigation-context.js';
|
|
3
|
+
export declare enum KeyIntent {
|
|
4
|
+
NavPreviousItem = "nav-previous-item",
|
|
5
|
+
NavNextItem = "nav-next-item",
|
|
6
|
+
NavToPreviousContainer = "nav-to-previous-container",
|
|
7
|
+
NavToNextContainer = "nav-to-next-container",
|
|
8
|
+
MovePreviousItem = "move-previous-item",
|
|
9
|
+
MoveNextItem = "move-next-item",
|
|
10
|
+
MoveToPreviousContainer = "move-to-previous-container",
|
|
11
|
+
MoveToNextContainer = "move-to-next-container",
|
|
12
|
+
Confirm = "confirm",
|
|
13
|
+
Exit = "exit",
|
|
14
|
+
ToggleHelp = "toggle-help"
|
|
15
|
+
}
|
|
16
|
+
export declare function getKeyIntent(key: readline.Key, ctx: NavigateCtx): KeyIntent | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export var KeyIntent;
|
|
2
|
+
(function (KeyIntent) {
|
|
3
|
+
KeyIntent["NavPreviousItem"] = "nav-previous-item";
|
|
4
|
+
KeyIntent["NavNextItem"] = "nav-next-item";
|
|
5
|
+
KeyIntent["NavToPreviousContainer"] = "nav-to-previous-container";
|
|
6
|
+
KeyIntent["NavToNextContainer"] = "nav-to-next-container";
|
|
7
|
+
KeyIntent["MovePreviousItem"] = "move-previous-item";
|
|
8
|
+
KeyIntent["MoveNextItem"] = "move-next-item";
|
|
9
|
+
KeyIntent["MoveToPreviousContainer"] = "move-to-previous-container";
|
|
10
|
+
KeyIntent["MoveToNextContainer"] = "move-to-next-container";
|
|
11
|
+
KeyIntent["Confirm"] = "confirm";
|
|
12
|
+
KeyIntent["Exit"] = "exit";
|
|
13
|
+
KeyIntent["ToggleHelp"] = "toggle-help";
|
|
14
|
+
})(KeyIntent || (KeyIntent = {}));
|
|
15
|
+
export function getKeyIntent(key, ctx) {
|
|
16
|
+
const axis = ctx.navigationNode.childrenRenderAxis;
|
|
17
|
+
if (key.shift) {
|
|
18
|
+
switch (key.name) {
|
|
19
|
+
case 'up':
|
|
20
|
+
return axis === 'vertical' ? KeyIntent.MovePreviousItem : null;
|
|
21
|
+
case 'down':
|
|
22
|
+
return axis === 'vertical' ? KeyIntent.MoveNextItem : null;
|
|
23
|
+
case 'left':
|
|
24
|
+
return axis === 'horizontal'
|
|
25
|
+
? KeyIntent.MovePreviousItem
|
|
26
|
+
: ctx.navigationNode.enableChildNavigationAcrossContainers
|
|
27
|
+
? KeyIntent.MoveToPreviousContainer
|
|
28
|
+
: null;
|
|
29
|
+
case 'right':
|
|
30
|
+
return axis === 'horizontal'
|
|
31
|
+
? KeyIntent.MoveNextItem
|
|
32
|
+
: ctx.navigationNode.enableChildNavigationAcrossContainers
|
|
33
|
+
? KeyIntent.MoveToNextContainer
|
|
34
|
+
: null;
|
|
35
|
+
default:
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
switch (key.name) {
|
|
40
|
+
case 'h':
|
|
41
|
+
return KeyIntent.ToggleHelp;
|
|
42
|
+
case 'up':
|
|
43
|
+
return axis === 'vertical' ? KeyIntent.NavPreviousItem : null;
|
|
44
|
+
case 'down':
|
|
45
|
+
return axis === 'vertical' ? KeyIntent.NavNextItem : null;
|
|
46
|
+
case 'left':
|
|
47
|
+
return axis === 'horizontal'
|
|
48
|
+
? KeyIntent.NavPreviousItem
|
|
49
|
+
: ctx.navigationNode.enableChildNavigationAcrossContainers
|
|
50
|
+
? KeyIntent.NavToPreviousContainer
|
|
51
|
+
: null;
|
|
52
|
+
case 'right':
|
|
53
|
+
return axis === 'horizontal'
|
|
54
|
+
? KeyIntent.NavNextItem
|
|
55
|
+
: ctx.navigationNode.enableChildNavigationAcrossContainers
|
|
56
|
+
? KeyIntent.NavToNextContainer
|
|
57
|
+
: null;
|
|
58
|
+
case 'return':
|
|
59
|
+
return KeyIntent.Confirm;
|
|
60
|
+
case 'e':
|
|
61
|
+
return KeyIntent.Exit;
|
|
62
|
+
default:
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const clearScreen: () => void;
|
|
2
|
+
export declare const keys: {
|
|
3
|
+
readonly CTRL_C: "\u0003";
|
|
4
|
+
readonly ENTER: "\r";
|
|
5
|
+
readonly ARROW_UP: "\u001B[A";
|
|
6
|
+
readonly ARROW_DOWN: "\u001B[B";
|
|
7
|
+
readonly ARROW_LEFT: "\u001B[D";
|
|
8
|
+
readonly ARROW_RIGHT: "\u001B[C";
|
|
9
|
+
readonly ESCAPE: "\u001B";
|
|
10
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const clearScreen = () => {
|
|
2
|
+
process.stdout.write('\x1B[2J\x1B[0f');
|
|
3
|
+
};
|
|
4
|
+
export const keys = {
|
|
5
|
+
CTRL_C: '\u0003',
|
|
6
|
+
ENTER: '\r',
|
|
7
|
+
ARROW_UP: '\u001b[A',
|
|
8
|
+
ARROW_DOWN: '\u001b[B',
|
|
9
|
+
ARROW_LEFT: '\u001b[D',
|
|
10
|
+
ARROW_RIGHT: '\u001b[C',
|
|
11
|
+
ESCAPE: '\u001b',
|
|
12
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { BoardItemTypes } from '../lib/types/board.model.js';
|
|
2
|
+
export const board = {
|
|
3
|
+
id: 'b1',
|
|
4
|
+
isSelected: false,
|
|
5
|
+
name: 'Product Development Board',
|
|
6
|
+
actionContext: 'BOARD',
|
|
7
|
+
childrenRenderAxis: 'horizontal',
|
|
8
|
+
children: [
|
|
9
|
+
{
|
|
10
|
+
isSelected: false,
|
|
11
|
+
id: 's1',
|
|
12
|
+
name: 'To Do',
|
|
13
|
+
childrenRenderAxis: 'vertical',
|
|
14
|
+
actionContext: BoardItemTypes.SWIMLANE,
|
|
15
|
+
enableChildNavigationAcrossContainers: true,
|
|
16
|
+
children: [
|
|
17
|
+
{
|
|
18
|
+
isSelected: false,
|
|
19
|
+
id: 't1',
|
|
20
|
+
name: 'Setup CI/CD pipeline',
|
|
21
|
+
actionContext: 'TICKET',
|
|
22
|
+
childrenRenderAxis: 'vertical',
|
|
23
|
+
description: 'Configure GitHub Actions for test, build, and deploy stages.',
|
|
24
|
+
children: [],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
isSelected: false,
|
|
28
|
+
id: 't2',
|
|
29
|
+
name: 'Create API contract for auth service',
|
|
30
|
+
actionContext: 'TICKET',
|
|
31
|
+
childrenRenderAxis: 'vertical',
|
|
32
|
+
description: 'Define request/response formats, error handling, and versioning.',
|
|
33
|
+
children: [],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
isSelected: false,
|
|
37
|
+
id: 't3',
|
|
38
|
+
name: 'Draft OKRs for Q4',
|
|
39
|
+
actionContext: 'TICKET',
|
|
40
|
+
childrenRenderAxis: 'vertical',
|
|
41
|
+
description: 'Collaborate with leads to define team goals and key results.',
|
|
42
|
+
children: [],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
isSelected: false,
|
|
48
|
+
id: 's2',
|
|
49
|
+
name: 'In Progress',
|
|
50
|
+
childrenRenderAxis: 'vertical',
|
|
51
|
+
actionContext: 'SWIMLANE',
|
|
52
|
+
enableChildNavigationAcrossContainers: true,
|
|
53
|
+
children: [],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
isSelected: false,
|
|
57
|
+
id: 's3',
|
|
58
|
+
name: 'Review',
|
|
59
|
+
childrenRenderAxis: 'vertical',
|
|
60
|
+
actionContext: 'SWIMLANE',
|
|
61
|
+
enableChildNavigationAcrossContainers: true,
|
|
62
|
+
children: [
|
|
63
|
+
{
|
|
64
|
+
isSelected: false,
|
|
65
|
+
id: 't4',
|
|
66
|
+
name: 'Implement user login flow',
|
|
67
|
+
actionContext: 'TICKET',
|
|
68
|
+
childrenRenderAxis: 'vertical',
|
|
69
|
+
description: 'Integrate frontend with backend auth API and handle edge cases.',
|
|
70
|
+
children: [],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
isSelected: false,
|
|
74
|
+
id: 't5',
|
|
75
|
+
name: 'Design new dashboard layout',
|
|
76
|
+
actionContext: 'TICKET',
|
|
77
|
+
childrenRenderAxis: 'vertical',
|
|
78
|
+
description: 'Collaborate with UX to create a grid-based responsive dashboard.',
|
|
79
|
+
children: [],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
isSelected: false,
|
|
83
|
+
id: 't6',
|
|
84
|
+
name: 'Accessibility audit',
|
|
85
|
+
actionContext: 'TICKET',
|
|
86
|
+
childrenRenderAxis: 'vertical',
|
|
87
|
+
description: 'Evaluate color contrast, ARIA usage, and keyboard nav.',
|
|
88
|
+
children: [],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
isSelected: false,
|
|
92
|
+
id: 't7',
|
|
93
|
+
name: 'Code review for analytics module',
|
|
94
|
+
actionContext: 'TICKET',
|
|
95
|
+
childrenRenderAxis: 'vertical',
|
|
96
|
+
description: 'Ensure test coverage, types, and data validation are in place.',
|
|
97
|
+
children: [],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
isSelected: false,
|
|
101
|
+
id: 't8',
|
|
102
|
+
name: 'Review UI polish for mobile view',
|
|
103
|
+
actionContext: 'TICKET',
|
|
104
|
+
childrenRenderAxis: 'vertical',
|
|
105
|
+
description: 'Verify spacing, responsiveness, and visual hierarchy.',
|
|
106
|
+
children: [],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
isSelected: false,
|
|
112
|
+
id: 's4',
|
|
113
|
+
name: 'Done',
|
|
114
|
+
childrenRenderAxis: 'vertical',
|
|
115
|
+
actionContext: 'SWIMLANE',
|
|
116
|
+
enableChildNavigationAcrossContainers: true,
|
|
117
|
+
children: [
|
|
118
|
+
{
|
|
119
|
+
isSelected: false,
|
|
120
|
+
id: 't9',
|
|
121
|
+
name: 'Setup Storybook',
|
|
122
|
+
actionContext: 'TICKET',
|
|
123
|
+
childrenRenderAxis: 'vertical',
|
|
124
|
+
description: 'Document reusable components and tokens in Storybook.',
|
|
125
|
+
children: [],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
isSelected: false,
|
|
129
|
+
id: 't10',
|
|
130
|
+
name: 'Deploy v1.2 to staging',
|
|
131
|
+
actionContext: 'TICKET',
|
|
132
|
+
childrenRenderAxis: 'vertical',
|
|
133
|
+
description: 'All pipelines green. Ready for product team QA.',
|
|
134
|
+
children: [],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { setState } from '../state/state.js';
|
|
2
|
+
import { Mode } from '../model/action-map.model.js';
|
|
3
|
+
import { enterChildNode, exitToParentNode, navigateToNextContainer, navigateToNextItem, navigateToPreviousContainer, navigateToPreviousItem, } from './default-actions.js';
|
|
4
|
+
import { KeyIntent } from '../utils/key-intent.js';
|
|
5
|
+
export const buildDefaultActions = () => [
|
|
6
|
+
{
|
|
7
|
+
intent: KeyIntent.Confirm,
|
|
8
|
+
mode: Mode.DEFAULT,
|
|
9
|
+
description: '[ENTER] Confirm',
|
|
10
|
+
action: enterChildNode,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
intent: KeyIntent.Exit,
|
|
14
|
+
mode: Mode.DEFAULT,
|
|
15
|
+
description: '[E] Exit container',
|
|
16
|
+
action: exitToParentNode,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
mode: Mode.DEFAULT,
|
|
20
|
+
description: '[ARROW KEYS] Navigate',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
intent: KeyIntent.ToggleHelp,
|
|
24
|
+
mode: Mode.DEFAULT,
|
|
25
|
+
action: () => setState(state => ({ ...state, viewHelp: !state.viewHelp })),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
intent: KeyIntent.NavPreviousItem,
|
|
29
|
+
mode: Mode.DEFAULT,
|
|
30
|
+
action: navigateToPreviousItem,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
intent: KeyIntent.NavNextItem,
|
|
34
|
+
mode: Mode.DEFAULT,
|
|
35
|
+
action: navigateToNextItem,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
intent: KeyIntent.NavToPreviousContainer,
|
|
39
|
+
mode: Mode.DEFAULT,
|
|
40
|
+
action: navigateToPreviousContainer,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
intent: KeyIntent.NavToNextContainer,
|
|
44
|
+
mode: Mode.DEFAULT,
|
|
45
|
+
action: navigateToNextContainer,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NavigateCtx } from '../model/navigation-ctx.model.js';
|
|
2
|
+
export declare const navigateToNextItem: (ctx: NavigateCtx) => void;
|
|
3
|
+
export declare const navigateToPreviousItem: (ctx: NavigateCtx) => void;
|
|
4
|
+
export declare const navigateToNextContainer: (ctx: NavigateCtx) => void;
|
|
5
|
+
export declare const navigateToPreviousContainer: (ctx: NavigateCtx) => void;
|
|
6
|
+
export declare const enterChildNode: (ctx: NavigateCtx) => void;
|
|
7
|
+
export declare const exitToParentNode: (ctx: NavigateCtx) => void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const navigateByOffset = (ctx, offset) => {
|
|
2
|
+
const len = ctx.children.length;
|
|
3
|
+
const newIndex = (ctx.getSelectedIndex() + offset + len) % len;
|
|
4
|
+
ctx.select(newIndex);
|
|
5
|
+
};
|
|
6
|
+
export const navigateToNextItem = (ctx) => navigateByOffset(ctx, 1);
|
|
7
|
+
export const navigateToPreviousItem = (ctx) => navigateByOffset(ctx, -1);
|
|
8
|
+
const navigateToSiblingContainer = (ctx, direction) => {
|
|
9
|
+
if (!ctx.navigationNode.enableChildNavigationAcrossContainers)
|
|
10
|
+
return;
|
|
11
|
+
const parent = ctx.breadCrumb.at(-1);
|
|
12
|
+
const grandParent = ctx.breadCrumb.at(-2);
|
|
13
|
+
if (!parent || !grandParent)
|
|
14
|
+
return;
|
|
15
|
+
const parentIndex = grandParent.children.findIndex(x => x.id === parent.id);
|
|
16
|
+
const sliced = direction === -1
|
|
17
|
+
? grandParent.children.toSpliced(parentIndex).toReversed()
|
|
18
|
+
: grandParent.children.slice(parentIndex + 1);
|
|
19
|
+
const nextSibling = sliced.find(x => x.children.length);
|
|
20
|
+
if (!nextSibling?.children?.length)
|
|
21
|
+
return;
|
|
22
|
+
const prevIndex = ctx.getSelectedIndex();
|
|
23
|
+
const boundedIndex = Math.min(prevIndex, nextSibling.children.length - 1);
|
|
24
|
+
ctx.selectNone();
|
|
25
|
+
ctx.reInvokeNavigate(boundedIndex, [
|
|
26
|
+
...ctx.breadCrumb.slice(0, -1),
|
|
27
|
+
nextSibling,
|
|
28
|
+
]);
|
|
29
|
+
};
|
|
30
|
+
export const navigateToNextContainer = (ctx) => navigateToSiblingContainer(ctx, 1);
|
|
31
|
+
export const navigateToPreviousContainer = (ctx) => navigateToSiblingContainer(ctx, -1);
|
|
32
|
+
export const enterChildNode = (ctx) => {
|
|
33
|
+
const current = ctx.navigationNode.children[ctx._selectedIndex];
|
|
34
|
+
if (!current)
|
|
35
|
+
return;
|
|
36
|
+
if (!current.children?.length) {
|
|
37
|
+
ctx.confirm(current);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
ctx.select(-1);
|
|
41
|
+
ctx.enterChildNode(current);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export const exitToParentNode = (ctx) => {
|
|
45
|
+
if (ctx.breadCrumb.length >= 2) {
|
|
46
|
+
ctx.enterParentNode();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const Mode: {
|
|
2
|
+
readonly DEFAULT: "default";
|
|
3
|
+
readonly MOVE: "move";
|
|
4
|
+
};
|
|
5
|
+
export type ModeOptions = (typeof Mode)[keyof typeof Mode];
|
|
6
|
+
export type ActionEntry<TArgs extends any[] = []> = {
|
|
7
|
+
intent?: string;
|
|
8
|
+
mode: string;
|
|
9
|
+
description?: `[${string}] ${string}`;
|
|
10
|
+
action?: (...args: TArgs) => void;
|
|
11
|
+
};
|
|
12
|
+
export type ActionMap<T extends Record<string, any[]>> = {
|
|
13
|
+
[K in keyof T]: ActionEntry<T[K]>[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NavigationTree } from './navigation-tree.model.js';
|
|
2
|
+
export interface NavigateCtx {
|
|
3
|
+
_selectedIndex: number;
|
|
4
|
+
breadCrumb: Array<NavigationTree<NavigationTree>>;
|
|
5
|
+
navigationNode: NavigationTree<NavigationTree>;
|
|
6
|
+
children: NavigationTree[];
|
|
7
|
+
getSelectedIndex(): number;
|
|
8
|
+
select(index: number): void;
|
|
9
|
+
selectNone(): void;
|
|
10
|
+
reInvokeNavigate(index: number, breadCrumb: NavigationTree<NavigationTree>[]): void;
|
|
11
|
+
render(): void;
|
|
12
|
+
confirm(selected: NavigationTree<NavigationTree>): void;
|
|
13
|
+
exit(): void;
|
|
14
|
+
enterChildNode(node: NavigationTree<NavigationTree>): void;
|
|
15
|
+
enterParentNode(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type NavigationTree<TMeta = Record<string, unknown>> = {
|
|
2
|
+
id: string;
|
|
3
|
+
isSelected: boolean;
|
|
4
|
+
childrenRenderAxis: 'vertical' | 'horizontal';
|
|
5
|
+
name: string;
|
|
6
|
+
children: NavigationTree<unknown>[];
|
|
7
|
+
actionContext: string;
|
|
8
|
+
enableChildNavigationAcrossContainers?: boolean;
|
|
9
|
+
} & TMeta;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NavigationTree } from './model/navigation-tree.model.js';
|
|
2
|
+
export declare function navigate<T extends NavigationTree>({ index, breadCrumb, callbacks, }: {
|
|
3
|
+
index: number;
|
|
4
|
+
breadCrumb: Array<NavigationTree<NavigationTree>>;
|
|
5
|
+
callbacks: Partial<{
|
|
6
|
+
render: () => void;
|
|
7
|
+
onSelectChange: (s: T['children'][number], breadCrumb: T['children']) => void;
|
|
8
|
+
onConfirm: (s: T['children'][number]) => void;
|
|
9
|
+
}>;
|
|
10
|
+
}): void;
|