diff --git a/mathesar_ui/src/component-library/common/actions/clickOffBounds.ts b/mathesar_ui/src/component-library/common/actions/clickOffBounds.ts index 3cb3d27353..ba60df3d8f 100644 --- a/mathesar_ui/src/component-library/common/actions/clickOffBounds.ts +++ b/mathesar_ui/src/component-library/common/actions/clickOffBounds.ts @@ -25,7 +25,20 @@ export default function clickOffBounds( } } - document.body.addEventListener('click', outOfBoundsListener, true); + /** + * When the browser supports pointer events, we use the pointerdown event + * which is fired for all mouse buttons and touches. However, older Safari + * versions don't have pointer events, so we fallback to mouse events. Touches + * should fire a mousedown event too. + */ + const events = + 'onpointerdown' in document.body + ? ['pointerdown'] + : ['mousedown', 'contextmenu']; + + events.forEach((event) => { + document.body.addEventListener(event, outOfBoundsListener, true); + }); function update(opts: Options) { callback = opts.callback; @@ -33,7 +46,9 @@ export default function clickOffBounds( } function destroy() { - document.body.removeEventListener('click', outOfBoundsListener, true); + events.forEach((event) => { + document.body.removeEventListener(event, outOfBoundsListener, true); + }); } return { diff --git a/mathesar_ui/src/component-library/common/actions/popper.ts b/mathesar_ui/src/component-library/common/actions/popper.ts index ed92898192..956a95cc45 100644 --- a/mathesar_ui/src/component-library/common/actions/popper.ts +++ b/mathesar_ui/src/component-library/common/actions/popper.ts @@ -4,55 +4,57 @@ import type { ModifierArguments, Options, Instance, + VirtualElement, } from '@popperjs/core/lib/types'; import type { Action } from './types'; export default function popper( node: HTMLElement, actionOpts: { - reference: HTMLElement; + reference: VirtualElement; options?: Partial; }, ): Action { let popperInstance: Instance; let prevReference: HTMLElement | undefined; - function create(reference: HTMLElement, options?: Partial) { - if (reference) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - popperInstance = createPopper(reference, node, { - placement: options?.placement || 'bottom-start', - modifiers: [ - { - name: 'setMinWidth', - enabled: true, - phase: 'beforeWrite', - requires: ['computeStyles'], - // @ts-ignore: https://github.com/centerofci/mathesar/issues/1055 - fn: (obj: ModifierArguments): void => { - // eslint-disable-next-line no-param-reassign - obj.state.styles.popper.minWidth = `${obj.state.rects.reference.width}px`; - }, - // @ts-ignore: https://github.com/centerofci/mathesar/issues/1055 - effect: (obj: ModifierArguments): void => { - const width = (obj.state.elements.reference as HTMLElement) - .offsetWidth; - // eslint-disable-next-line no-param-reassign - obj.state.elements.popper.style.minWidth = `${width}px`; - }, + function create(reference?: VirtualElement, options?: Partial) { + if (!reference) { + return; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + popperInstance = createPopper(reference, node, { + placement: options?.placement || 'bottom-start', + modifiers: [ + { + name: 'setMinWidth', + enabled: true, + phase: 'beforeWrite', + requires: ['computeStyles'], + // @ts-ignore: https://github.com/centerofci/mathesar/issues/1055 + fn: (obj: ModifierArguments): void => { + // eslint-disable-next-line no-param-reassign + obj.state.styles.popper.minWidth = `${obj.state.rects.reference.width}px`; }, - { - name: 'flip', + // @ts-ignore: https://github.com/centerofci/mathesar/issues/1055 + effect: (obj: ModifierArguments): void => { + const width = (obj.state.elements.reference as HTMLElement) + .offsetWidth; + // eslint-disable-next-line no-param-reassign + obj.state.elements.popper.style.minWidth = `${width}px`; }, - { - name: 'offset', - options: { - offset: [0, 0], - }, + }, + { + name: 'flip', + }, + { + name: 'offset', + options: { + offset: [0, 0], }, - ], - }) as Instance; - } + }, + ], + }) as Instance; } function destroy() { diff --git a/mathesar_ui/src/component-library/context-menu/ContextMenu.svelte b/mathesar_ui/src/component-library/context-menu/ContextMenu.svelte new file mode 100644 index 0000000000..60378b3b55 --- /dev/null +++ b/mathesar_ui/src/component-library/context-menu/ContextMenu.svelte @@ -0,0 +1,90 @@ + + +
+ {#if isVisible} + + {/if} +
diff --git a/mathesar_ui/src/component-library/context-menu/__meta__/ContextMenu.stories.svelte b/mathesar_ui/src/component-library/context-menu/__meta__/ContextMenu.stories.svelte new file mode 100644 index 0000000000..b380a47bcd --- /dev/null +++ b/mathesar_ui/src/component-library/context-menu/__meta__/ContextMenu.stories.svelte @@ -0,0 +1,75 @@ + + + + + +
+

This box does not have a context menu.

+
+ +
+

This box has a context menu.

+

The count is: {count}.

+ + + + Use Background + + + Increment Counter + + +
+ +
+

This box also has a context menu.

+ + I don't do anything + +
+ +
+

This box does not have a context menu.

+
+
+ + diff --git a/mathesar_ui/src/component-library/index.ts b/mathesar_ui/src/component-library/index.ts index 7d26293397..51f02a5e33 100644 --- a/mathesar_ui/src/component-library/index.ts +++ b/mathesar_ui/src/component-library/index.ts @@ -10,6 +10,7 @@ export { default as Button } from './button/Button.svelte'; export { default as CancelOrProceedButtonPair } from './cancel-or-proceed-button-pair/CancelOrProceedButtonPair.svelte'; export { default as Checkbox } from './checkbox/Checkbox.svelte'; export { default as CheckboxGroup } from './checkbox-group/CheckboxGroup.svelte'; +export { default as ContextMenu } from './context-menu/ContextMenu.svelte'; export { default as Help } from './help/Help.svelte'; export { default as Icon } from './icon/Icon.svelte'; export { InputGroup, InputGroupText } from './input-group';