Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ coverage
.DS_Store
test-results/
playwright-report/
.vscode
23 changes: 11 additions & 12 deletions e2e/command-palette-basic.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import { test, expect, Page } from '@playwright/test';
import { checkMac } from './testUtils/checkMac';

async function triggerCommandPaletteOpen(page: Page) {
const isMac = await checkMac(page);

if (isMac) {
await page.keyboard.press('Meta+k');
} else {
await page.keyboard.press('Control+k');
}
}
import { test, expect } from '@playwright/test';
import { triggerCommandPaletteOpen } from './testUtils/triggerCommandPaletteOpen';

test.describe('Test basic interactions of Command Palette', () => {
test('should be able to open command palette & run first action', async ({ page }) => {
Expand Down Expand Up @@ -102,4 +92,13 @@ test.describe('Test basic interactions of Command Palette', () => {

await expect(profileStatusLocator).toBeVisible();
});

test('should be able to open nested actions using keyboard', async ({ page }) => {
await page.goto('/demo');

await page.keyboard.press('p');
await page.keyboard.press('s');

await expect(page.locator('.command-palette-portal [role="combobox"]')).toBeVisible();
});
});
25 changes: 25 additions & 0 deletions e2e/command-palette-root-initial-visible-actions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { test, expect } from '@playwright/test';
import { triggerCommandPaletteOpen } from './testUtils/triggerCommandPaletteOpen';

test.describe('Test `initialVisibleActions` prop of `Root` component', () => {
test('should not show nested actions at root by default', async ({ page }) => {
await page.goto('/demo');
await triggerCommandPaletteOpen(page);

await expect(page.locator('text=Set to Personal profile')).not.toBeVisible();
});

test('should not show nested actions at root when set to `root`', async ({ page }) => {
await page.goto('/demo/InitialVisibleActions/Root');
await triggerCommandPaletteOpen(page);

await expect(page.locator('text=Configure Personal profile')).not.toBeVisible();
});

test('should show nested actions at root when set to `all`', async ({ page }) => {
await page.goto('/demo/InitialVisibleActions/All');
await triggerCommandPaletteOpen(page);

await expect(page.locator('text=Configure Personal profile')).toBeVisible();
});
});
12 changes: 12 additions & 0 deletions e2e/testUtils/triggerCommandPaletteOpen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Page } from '@playwright/test';
import { checkMac } from './checkMac';

export async function triggerCommandPaletteOpen(page: Page) {
const isMac = await checkMac(page);

if (isMac) {
await page.keyboard.press('Meta+k');
} else {
await page.keyboard.press('Control+k');
}
}
10 changes: 9 additions & 1 deletion src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ const routes: Array<RouteDefinition> = [
},
{
path: '/demo',
component: lazy(() => import('./views/demo/Demo.view')),
component: lazy(() => import('./views/demo/Default.view')),
},
{
path: '/demo/InitialVisibleActions/All',
component: lazy(() => import('./views/demo/InitialVisibleActions-All.view')),
},
{
path: '/demo/InitialVisibleActions/Root',
component: lazy(() => import('./views/demo/InitialVisibleActions-Root.view')),
},
{
path: '/',
Expand Down
8 changes: 8 additions & 0 deletions src/app/views/demo/Default.view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Component } from 'solid-js';
import BaseDemoView from './shared/Demo.view';

const DemoView: Component = () => {
return <BaseDemoView/>;
}

export default DemoView;
12 changes: 12 additions & 0 deletions src/app/views/demo/InitialVisibleActions-All.view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from 'solid-js';
import BaseDemoView from './shared/Demo.view';

const DemoView: Component = () => {
return (
<BaseDemoView
initialVisibleActions="all"
/>
);
}

export default DemoView;
12 changes: 12 additions & 0 deletions src/app/views/demo/InitialVisibleActions-Root.view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from 'solid-js';
import BaseDemoView from './shared/Demo.view';

const DemoView: Component = () => {
return (
<BaseDemoView
initialVisibleActions="root"
/>
);
}

export default DemoView;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, Show } from 'solid-js';
import { KbdShortcut, ResultContentProps } from '../../../../lib';
import utilStyles from '../../../utils.module.css';
import { KbdShortcut, ResultContentProps } from '../../../../../lib';
import utilStyles from '../../../../utils.module.css';
import styles from './CustomComponentsDemo.module.css';

export const DemoResultContent: Component<ResultContentProps> = (p) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { Component, createSignal, Show } from 'solid-js';
import { useSearchParams } from 'solid-app-router';
import { Root, CommandPalette, KbdShortcut } from '../../../lib';
import { Root, CommandPalette, KbdShortcut } from '../../../../lib';
import { RootProps } from '../../../../lib/types';
import { actions } from './actions';
import { NestedActionDemo } from './NestedActionDemo/NestedActionDemo';
import { DynamicActionContextDemo } from './DynamicActionContextDemo/DynamicActionContextDemo';
import { components } from './CustomComponentsDemo/components';
import { Profile } from './types';
import utilStyles from '../../utils.module.css';
import utilStyles from '../../../utils.module.css';
import demoStyles from './demoUtils.module.css';
import styles from './Demo.module.css';

const DemoView: Component = () => {
type DemoProps = Pick<RootProps, 'initialVisibleActions'>;

const DemoView: Component<DemoProps> = (p) => {
const [count, setCount] = createSignal(0);
const [muted, setMuted] = createSignal(false);
const [profile, setProfile] = createSignal<Profile>('personal');
Expand Down Expand Up @@ -66,6 +69,7 @@ const DemoView: Component = () => {
actions={actions}
actionsContext={actionsContext}
components={customProps.components}
initialVisibleActions={p.initialVisibleActions}
>
<div class={styles.demoWrapper}>
<CommandPalette searchPlaceholder={customProps.placeholder} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Component, createMemo, createSignal, createUniqueId, For, Show } from 'solid-js';
import { KbdShortcut, createSyncActionsContext } from '../../../../lib';
import { KbdShortcut, createSyncActionsContext } from '../../../../../lib';
import { ownContactId, contacts, contactActionId } from './data';
import { InputEventHandler, ContactItemProps, ReceiverContactDetailsProps } from './types';
import demoStyles from '../demoUtils.module.css';
import utilStyles from '../../../utils.module.css';
import utilStyles from '../../../../utils.module.css';
import styles from './DynamicActionContextDemo.module.css';

const ContactItem: Component<ContactItemProps> = (p) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineAction } from '../../../../lib';
import { defineAction } from '../../../../../lib';
import { contactActionId, contacts } from './data';

export const contactAction = defineAction({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Component } from 'solid-js';
import { KbdShortcut } from '../../../../lib';
import { KbdShortcut } from '../../../../../lib';
import { Profile } from '../types';
import demoStyles from '../demoUtils.module.css';
import utilStyles from '../../../utils.module.css';
import utilStyles from '../../../../utils.module.css';
import styles from './NestedActionDemo.module.css';

export interface Props {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { defineAction } from '../../../../lib';
import { defineAction } from '../../../../../lib';

const setProfileAction = defineAction({
id: 'set-profile',
title: 'Set profile',
subtitle: 'Select this and then choose one of the options',
shortcut: 'p s',
});

const setToPersonalProfileAction = defineAction({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineAction } from '../../../lib';
import { defineAction } from '../../../../lib';
import { contactAction } from './DynamicActionContextDemo/dynamicContextActions';
import { nestedActionsConfig } from './NestedActionDemo/nestedActions';

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/lib/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const CommandPaletteInternal: Component<CommandPaletteProps> = (p) => {
let lastFocusedElem: null | HTMLElement;

function triggerRun(action: WrappedAction) {
runAction(action, state.actionsContext, storeMethods);
runAction(action, state.actionsContext, storeMethods, 'palette');
}

function activatePrevItem() {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const RootInternal: Component = () => {
export const Root: Component<RootProps> = (p) => {
const initialActions = p.actions || {};
const initialActionsContext = p.actionsContext || {};
const initialVisibleActions = p.initialVisibleActions || 'root';

const [state, setState] = createStore<StoreState>({
visibility: 'closed',
Expand All @@ -26,6 +27,7 @@ export const Root: Component<RootProps> = (p) => {
dynamic: {},
},
components: p.components,
initialVisibleActions: initialVisibleActions,
});

const storeMethods: StoreMethods = {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/actionUtils/actionUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('Test Action Utils', () => {
const runMock = vi.fn();
const selectParentActionMock = vi.fn();
const closePaletteMock = vi.fn();
const openPaletteMock = vi.fn();

const baseAction = {
id: 'test-action',
Expand All @@ -103,6 +104,7 @@ describe('Test Action Utils', () => {
const baseStoreMethods = {
selectParentAction: selectParentActionMock,
closePalette: closePaletteMock,
openPalette: openPaletteMock,
};

afterEach(() => {
Expand Down
11 changes: 8 additions & 3 deletions src/lib/actionUtils/actionUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { KeyBindingMap } from 'tinykeys';
import { rootParentActionId } from '../constants';
import { ActionId, ActionsContext, StoreMethods, WrappedAction, WrappedActionList } from '../types';
import { ActionId, ActionsContext, InvokeBy, StoreMethods, WrappedAction, WrappedActionList } from '../types';

type RunStoreMethods = {
selectParentAction: StoreMethods['selectParentAction'];
closePalette: StoreMethods['closePalette'];
openPalette: StoreMethods['openPalette'];
};

function getActionContext(action: WrappedAction, actionsContext: ActionsContext) {
Expand All @@ -31,12 +32,16 @@ export function checkActionAllowed(action: WrappedAction, actionsContext: Action
export function runAction(
action: WrappedAction,
actionsContext: ActionsContext,
storeMethods: RunStoreMethods
storeMethods: RunStoreMethods,
invokedBy: InvokeBy
) {
const { id, run } = action;

if (!run) {
storeMethods.selectParentAction(id);
if (invokedBy === 'shortcut') {
storeMethods.openPalette();
}
return;
}

Expand Down Expand Up @@ -68,7 +73,7 @@ export function getShortcutHandlersMap(
}

event.preventDefault();
runAction(action, actionsContext, storeMethods);
runAction(action, actionsContext, storeMethods, 'shortcut');
};

const shortcut = action.shortcut;
Expand Down
9 changes: 8 additions & 1 deletion src/lib/createActionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ export function createNestedActionList() {
function nestedActionFilter(action: WrappedAction) {
const { activeId, isRoot } = getActiveParentAction(state.activeParentActionIdList);

const isAllowed = isRoot || action.parentActionId === activeId;
const isRootAction = !action.parentActionId;
const isActiveChild = action.parentActionId === activeId;

const isAllowed =
(isRoot && isRootAction)
|| isActiveChild
|| state.initialVisibleActions === 'all'

return isAllowed;
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/defineAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const defineAction = (partialAction: PartialAction): Action => {
shortcut,
cond: partialAction.cond,
run,
};
} as Action;

return normalizedAction;
};
11 changes: 11 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface RootProps {
actions: Actions;
actionsContext: ActionContext;
components?: Components;
initialVisibleActions?: InitialVisibleActions;
}

export interface StoreState {
Expand All @@ -69,6 +70,12 @@ export interface StoreState {
actions: Actions;
actionsContext: ActionsContext;
components?: Components;
/**
* `root`: nested children are hidden from the root-level palette. (*default*)
*
* `all`: nested children are shown in the root-level palette.
*/
initialVisibleActions?: InitialVisibleActions;
}

export type StoreStateWrapped = Store<StoreState>;
Expand All @@ -93,3 +100,7 @@ export type CreateSyncActionsContext = (
actionId: ActionId,
callback: CreateSyncActionsContextCallback
) => void;

export type InitialVisibleActions = 'root' | 'all'

export type InvokeBy = 'shortcut' | 'palette'