@umodeapporg/ui 0.6.1 → 0.7.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/components/Navigation/PageTitle.d.ts +13 -0
- package/dist/components/Navigation/PageTitle.js +19 -4
- package/dist/components/Navigation/PageTitle.js.map +1 -1
- package/dist/components/UI/DataTable/grid/useGridKeyboard.js +8 -2
- package/dist/components/UI/DataTable/grid/useGridKeyboard.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { SymbolCodepoints } from '../UI/MaterialSymbol';
|
|
3
|
+
export interface IPageTitleImage {
|
|
4
|
+
src: string;
|
|
5
|
+
/** The image REPLACES the icon chip, so it carries the accessible name of what it depicts. */
|
|
6
|
+
alt: string;
|
|
7
|
+
/** Escape hatch for the shape, e.g. `rounded-full` for an avatar. */
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
3
10
|
interface IPageTitleProps {
|
|
4
11
|
title?: string;
|
|
5
12
|
/** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */
|
|
@@ -20,6 +27,12 @@ interface IPageTitleProps {
|
|
|
20
27
|
/** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */
|
|
21
28
|
titleIcon?: SymbolCodepoints;
|
|
22
29
|
titleIconClassName?: string;
|
|
30
|
+
/**
|
|
31
|
+
* An image in the icon chip's place, at the same size and radius (e.g. the product cover on a
|
|
32
|
+
* datasheet header). A src that fails to load falls back to the icon instead of leaving a broken
|
|
33
|
+
* image in the header.
|
|
34
|
+
*/
|
|
35
|
+
titleImage?: IPageTitleImage;
|
|
23
36
|
/** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */
|
|
24
37
|
autoIcon?: boolean;
|
|
25
38
|
/** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
3
|
import classNames from "classnames";
|
|
4
4
|
import { useTranslation } from "react-i18next";
|
|
5
5
|
import ControlledButton from "../Form/ControlledButton.js";
|
|
@@ -25,6 +25,7 @@ const PageTitle = ({
|
|
|
25
25
|
autoBreadcrumb = true,
|
|
26
26
|
titleIcon,
|
|
27
27
|
titleIconClassName,
|
|
28
|
+
titleImage,
|
|
28
29
|
autoIcon = true,
|
|
29
30
|
compact = false,
|
|
30
31
|
capitalizeTitle = true,
|
|
@@ -49,9 +50,9 @@ const PageTitle = ({
|
|
|
49
50
|
};
|
|
50
51
|
const { resolveTrail } = usePageTitleNavigation();
|
|
51
52
|
const menuTrail = useMemo(() => {
|
|
52
|
-
const needsTrail = autoBreadcrumb && !breadcrumb && !canFloat || autoIcon && !titleIcon;
|
|
53
|
+
const needsTrail = autoBreadcrumb && !breadcrumb && !canFloat || autoIcon && !titleIcon && !titleImage;
|
|
53
54
|
return needsTrail ? resolveTrail(location.pathname) : [];
|
|
54
|
-
}, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, location.pathname, resolveTrail]);
|
|
55
|
+
}, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, titleImage, location.pathname, resolveTrail]);
|
|
55
56
|
const resolvedBreadcrumb = breadcrumb ?? (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? /* @__PURE__ */ jsx(
|
|
56
57
|
ControlledBreadcrumb,
|
|
57
58
|
{
|
|
@@ -64,6 +65,8 @@ const PageTitle = ({
|
|
|
64
65
|
}
|
|
65
66
|
) : void 0);
|
|
66
67
|
const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : void 0);
|
|
68
|
+
const [failedTitleImageSrc, setFailedTitleImageSrc] = useState(null);
|
|
69
|
+
const rendersTitleImage = Boolean(titleImage?.src) && titleImage?.src !== failedTitleImageSrc;
|
|
67
70
|
return /* @__PURE__ */ jsxs(
|
|
68
71
|
"div",
|
|
69
72
|
{
|
|
@@ -98,7 +101,19 @@ const PageTitle = ({
|
|
|
98
101
|
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "arrow_back", size: compact ? 18 : 20 })
|
|
99
102
|
}
|
|
100
103
|
) }),
|
|
101
|
-
|
|
104
|
+
rendersTitleImage && titleImage ? /* @__PURE__ */ jsx(
|
|
105
|
+
"img",
|
|
106
|
+
{
|
|
107
|
+
src: titleImage.src,
|
|
108
|
+
alt: titleImage.alt,
|
|
109
|
+
onError: () => setFailedTitleImageSrc(titleImage.src),
|
|
110
|
+
className: classNames(
|
|
111
|
+
"shrink-0 border border-border object-cover",
|
|
112
|
+
compact ? "size-8 rounded-lg" : "size-10 rounded-xl",
|
|
113
|
+
titleImage.className
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
) : resolvedTitleIcon && /* @__PURE__ */ jsx(
|
|
102
117
|
"span",
|
|
103
118
|
{
|
|
104
119
|
className: classNames(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\nimport { useUiNavigation } from './UiNavigationContext';\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const { navigate, goBack, location } = useUiNavigation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n goBack();\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n return (\n <div\n className={classNames('flex flex-col', {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n className,\n })}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && <div className=\"mb-2 shrink-0\">{resolvedBreadcrumb}</div>}\n\n <div\n className={classNames('flex w-full flex-wrap items-center justify-between gap-x-4 gap-y-2', {\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', compact ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n compact ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={compact ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {resolvedTitleIcon && (\n <span\n className={classNames(\n 'flex shrink-0 items-center justify-center',\n compact ? 'size-8 rounded-lg' : 'size-10 rounded-xl',\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={compact ? 18 : 22} fill className=\"text-inherit\" />\n </span>\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && (\n <span className=\"mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <span\n className={classNames(\n 'min-w-0 truncate',\n compact\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </span>\n )}\n\n {titleSlot}\n </div>\n\n {headline && <span className=\"mt-0.5 text-xs text-foreground-subtle\">{headline}</span>}\n\n {subtitleSlot && <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>}\n </div>\n </div>\n\n {slot && <div className=\"ml-auto flex shrink-0 flex-wrap items-center justify-end gap-2\">{slot}</div>}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n <hr className={classNames('border-border-subtle', compact ? 'mb-2 mt-2' : 'mb-2 mt-3')} />\n )}\n </div>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PageTitle.js","sources":["../../../src/components/Navigation/PageTitle.tsx"],"sourcesContent":["import { ReactNode, useMemo, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { capitalizeAllLetters } from '@/helpers/stringHelpers';\n\nimport { MaterialSymbol, SymbolCodepoints } from '../UI/MaterialSymbol';\n\nimport ControlledBreadcrumb from './ControlledBreadcrumb';\nimport { usePageTitleNavigation } from './PageTitleNavigationContext';\nimport { useUiNavigation } from './UiNavigationContext';\n\nexport interface IPageTitleImage {\n src: string;\n /** The image REPLACES the icon chip, so it carries the accessible name of what it depicts. */\n alt: string;\n /** Escape hatch for the shape, e.g. `rounded-full` for an avatar. */\n className?: string;\n}\n\ninterface IPageTitleProps {\n title?: string;\n /** Substitui o span do título por conteúdo rico (ex.: nome editável inline da ficha técnica). */\n titleContent?: ReactNode;\n /** Rótulo pequeno em caixa alta acima do título (ex.: \"FICHA TÉCNICA\"). */\n eyebrow?: string;\n /** Conteúdo abaixo da linha do título, dentro da coluna de texto (tags, steppers). */\n subtitleSlot?: ReactNode;\n slot?: ReactNode;\n /** Conteúdo renderizado à ESQUERDA, imediatamente ao lado do título (o `slot` fica à direita). */\n titleSlot?: ReactNode;\n /** Conteúdo renderizado ANTES do ícone/título (ex.: botão voltar customizado, alvo de drop). */\n leadingSlot?: ReactNode;\n /** Trilha custom acima do título (use `ControlledBreadcrumb`). Sem ela, a trilha é derivada do menu. */\n breadcrumb?: ReactNode;\n /** Deriva a trilha automaticamente da árvore de navegação quando `breadcrumb` não é passado. */\n autoBreadcrumb?: boolean;\n /** Ícone em chip à esquerda do título. Sem ele, herda o ícone do item de menu da rota atual. */\n titleIcon?: SymbolCodepoints;\n titleIconClassName?: string;\n /**\n * An image in the icon chip's place, at the same size and radius (e.g. the product cover on a\n * datasheet header). A src that fails to load falls back to the icon instead of leaving a broken\n * image in the header.\n */\n titleImage?: IPageTitleImage;\n /** Deriva o ícone automaticamente do menu quando `titleIcon` não é passado. */\n autoIcon?: boolean;\n /** Variante compacta: título menor, para headers de navegação interna (ex.: pastas). */\n compact?: boolean;\n /** Desligue para títulos digitados pelo usuário (nomes de pastas etc.), que não podem ter a caixa alterada. */\n capitalizeTitle?: boolean;\n /** Desligue quando o header é seguido imediatamente por tabs ou outra divisão própria. */\n showDivider?: boolean;\n canBack?: boolean;\n canBackTo?: string;\n canFloat?: boolean;\n headline?: string;\n className?: string;\n titleClassName?: string;\n onBackClick?: () => void;\n disableBack?: boolean;\n}\n\nconst PageTitle: React.FC<IPageTitleProps> = ({\n canBack = false,\n canFloat = false,\n canBackTo,\n title = '',\n titleContent,\n eyebrow,\n subtitleSlot,\n headline,\n slot,\n titleSlot,\n leadingSlot,\n breadcrumb,\n autoBreadcrumb = true,\n titleIcon,\n titleIconClassName,\n titleImage,\n autoIcon = true,\n compact = false,\n capitalizeTitle = true,\n showDivider = true,\n className,\n titleClassName,\n onBackClick,\n disableBack = false,\n}) => {\n const { t } = useTranslation('ui');\n const { navigate, goBack, location } = useUiNavigation();\n\n const isFirefox = () => typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox');\n\n const handleBack = () => {\n if (disableBack) return;\n\n if (onBackClick) {\n onBackClick();\n } else if (canBackTo) {\n navigate(canBackTo);\n } else {\n goBack();\n }\n };\n\n const { resolveTrail } = usePageTitleNavigation();\n\n const menuTrail = useMemo(() => {\n const needsTrail = (autoBreadcrumb && !breadcrumb && !canFloat) || (autoIcon && !titleIcon && !titleImage);\n return needsTrail ? resolveTrail(location.pathname) : [];\n }, [autoBreadcrumb, breadcrumb, canFloat, autoIcon, titleIcon, titleImage, location.pathname, resolveTrail]);\n\n const resolvedBreadcrumb =\n breadcrumb ??\n (autoBreadcrumb && !canFloat && menuTrail.length >= 2 ? (\n <ControlledBreadcrumb\n ariaLabel={t('pageTitle.breadcrumbLabel')}\n items={menuTrail.map((item, index) => ({\n key: item.key,\n label: item.label,\n to: index < menuTrail.length - 1 ? item.to : undefined,\n }))}\n />\n ) : undefined);\n\n const resolvedTitleIcon = titleIcon ?? (autoIcon ? menuTrail[menuTrail.length - 1]?.icon : undefined);\n\n // Holds the src that failed rather than a boolean, so pointing at another image retries on its own.\n const [failedTitleImageSrc, setFailedTitleImageSrc] = useState<string | null>(null);\n const rendersTitleImage = Boolean(titleImage?.src) && titleImage?.src !== failedTitleImageSrc;\n\n return (\n <div\n className={classNames('flex flex-col', {\n 'zIndexMax fixed top-16 mr-7': canFloat,\n className,\n })}\n style={{\n width: canFloat ? (isFirefox() ? '-moz-available' : '-webkit-fill-available') : '100%',\n }}\n >\n {resolvedBreadcrumb && <div className=\"mb-2 shrink-0\">{resolvedBreadcrumb}</div>}\n\n <div\n className={classNames('flex w-full flex-wrap items-center justify-between gap-x-4 gap-y-2', {\n 'bg-surface py-5': canFloat,\n })}\n >\n <div className={classNames('flex min-w-0 flex-1 basis-60 items-center', compact ? 'gap-2' : 'gap-3')}>\n {leadingSlot}\n\n {canBack && (\n <ControlledTooltip content={t('actions.back')}>\n <ControlledButton\n defaultHtmlButton\n onClick={handleBack}\n disabled={disableBack}\n className={classNames(\n 'flex shrink-0 items-center justify-center rounded-full text-foreground-subtle transition-interactive duration-base ease-enter hover:bg-muted-hover hover:text-foreground-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border active:scale-90 disabled:pointer-events-none disabled:text-foreground-faint',\n compact ? 'size-8' : 'size-9',\n )}\n >\n <MaterialSymbol icon=\"arrow_back\" size={compact ? 18 : 20} />\n </ControlledButton>\n </ControlledTooltip>\n )}\n\n {rendersTitleImage && titleImage ? (\n <img\n src={titleImage.src}\n alt={titleImage.alt}\n onError={() => setFailedTitleImageSrc(titleImage.src)}\n className={classNames(\n 'shrink-0 border border-border object-cover',\n compact ? 'size-8 rounded-lg' : 'size-10 rounded-xl',\n titleImage.className,\n )}\n />\n ) : (\n resolvedTitleIcon && (\n <span\n className={classNames(\n 'flex shrink-0 items-center justify-center',\n compact ? 'size-8 rounded-lg' : 'size-10 rounded-xl',\n titleIconClassName ?? 'bg-brand-soft text-brand-emphasis',\n )}\n >\n <MaterialSymbol icon={resolvedTitleIcon} size={compact ? 18 : 22} fill className=\"text-inherit\" />\n </span>\n )\n )}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {eyebrow && (\n <span className=\"mb-0.5 text-xs font-semibold uppercase tracking-widest text-foreground-faint\">\n {eyebrow}\n </span>\n )}\n\n <div className=\"flex min-w-0 items-center gap-2\">\n {titleContent ?? (\n <span\n className={classNames(\n 'min-w-0 truncate',\n compact\n ? 'text-sm font-semibold text-foreground'\n : 'text-lg font-bold tracking-tight text-foreground laptop:text-xl',\n titleClassName,\n )}\n >\n {capitalizeTitle ? capitalizeAllLetters(title) : title}\n </span>\n )}\n\n {titleSlot}\n </div>\n\n {headline && <span className=\"mt-0.5 text-xs text-foreground-subtle\">{headline}</span>}\n\n {subtitleSlot && <div className=\"mt-1 flex min-w-0 flex-col gap-1\">{subtitleSlot}</div>}\n </div>\n </div>\n\n {slot && <div className=\"ml-auto flex shrink-0 flex-wrap items-center justify-end gap-2\">{slot}</div>}\n </div>\n {showDivider && !canFloat && (title.length > 0 || Boolean(titleContent)) && (\n <hr className={classNames('border-border-subtle', compact ? 'mb-2 mt-2' : 'mb-2 mt-3')} />\n )}\n </div>\n );\n};\n\nexport default PageTitle;\n"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,12 @@ const isTextEntryTarget = (target) => {
|
|
|
14
14
|
if (target.isContentEditable) return true;
|
|
15
15
|
return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
16
16
|
};
|
|
17
|
+
const moveKeepingTheEditorOpen = (store, columnDelta, canEditCell) => {
|
|
18
|
+
if (!store.getState().moveActiveCell(0, columnDelta, { wrap: true })) return false;
|
|
19
|
+
const { activeCell, startEditing } = store.getState();
|
|
20
|
+
if (activeCell && canEditCell?.(activeCell)) startEditing(activeCell);
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
17
23
|
const useGridKeyboard = ({ store, canEditCell, onSave }) => useCallback(
|
|
18
24
|
(event) => {
|
|
19
25
|
if (!store) return;
|
|
@@ -40,7 +46,7 @@ const useGridKeyboard = ({ store, canEditCell, onSave }) => useCallback(
|
|
|
40
46
|
}
|
|
41
47
|
if (key === "Tab") {
|
|
42
48
|
state.stopEditing();
|
|
43
|
-
if (
|
|
49
|
+
if (moveKeepingTheEditorOpen(store, event.shiftKey ? -1 : 1, canEditCell)) event.preventDefault();
|
|
44
50
|
}
|
|
45
51
|
return;
|
|
46
52
|
}
|
|
@@ -71,7 +77,7 @@ const useGridKeyboard = ({ store, canEditCell, onSave }) => useCallback(
|
|
|
71
77
|
state.moveActiveCell(0, 1, { extend });
|
|
72
78
|
return;
|
|
73
79
|
case "Tab":
|
|
74
|
-
if (
|
|
80
|
+
if (moveKeepingTheEditorOpen(store, event.shiftKey ? -1 : 1, canEditCell)) event.preventDefault();
|
|
75
81
|
return;
|
|
76
82
|
case "Home":
|
|
77
83
|
event.preventDefault();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGridKeyboard.js","sources":["../../../../../src/components/UI/DataTable/grid/useGridKeyboard.ts"],"sourcesContent":["import React, { useCallback } from 'react';\nimport { type StoreApi } from 'zustand/vanilla';\n\nimport { type IGridState } from './gridStore';\nimport { GRID_CELL_DATA_ATTR, type IGridCellCoord } from './gridTypes';\n\ninterface IUseGridKeyboardParams {\n store: StoreApi<IGridState> | null;\n /** Whether the cell can open an editor, so Enter/F2 know what to do. */\n canEditCell?: (coord: IGridCellCoord) => boolean;\n onSave?: () => void;\n}\n\nconst CELL_CONTENT_FOCUS_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]',\n]\n .map((selector) => `${selector}:not([tabindex=\"-1\"])`)\n .join(', ');\n\n/** The `<td>` the event came from, so the handler never queries the whole document. */\nconst resolveCellElement = (target: EventTarget | null): HTMLElement | null =>\n target instanceof HTMLElement ? target.closest<HTMLElement>(`[${GRID_CELL_DATA_ATTR}]`) : null;\n\n/**\n * A control that owns the caret (input, textarea, select, contenteditable) keeps\n * every key the grid would otherwise claim: the horizontal arrows walk the text,\n * not the columns.\n */\nconst isTextEntryTarget = (target: EventTarget | null): boolean => {\n if (!(target instanceof HTMLElement)) return false;\n if (target.isContentEditable) return true;\n\n return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT';\n};\n\n/**\n * The grid's ONLY keyboard listener: one React handler on the scroll container,\n * reached by bubbling from the focused cell. No document listeners, no per-cell\n * handlers, so two tables on the same screen never fight over the same keys.\n */\nexport const useGridKeyboard = ({ store, canEditCell, onSave }: IUseGridKeyboardParams) =>\n useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (!store) return;\n\n const state = store.getState();\n const { key } = event;\n\n if ((event.metaKey || event.ctrlKey) && (key === 's' || key === 'S')) {\n if (!onSave) return;\n event.preventDefault();\n onSave();\n return;\n }\n\n const isEditing = !!state.editingCell || isTextEntryTarget(event.target);\n\n if (isEditing) {\n if (key === 'Escape') {\n // The table may live inside a dialog or drawer: leaving the editor must\n // not also close the surface around it.\n event.stopPropagation();\n state.stopEditing('cancel');\n return;\n }\n\n if (key === 'Enter') {\n event.preventDefault();\n state.stopEditing();\n state.moveActiveCell(1, 0);\n return;\n }\n\n if (key === 'Tab') {\n state.stopEditing();\n
|
|
1
|
+
{"version":3,"file":"useGridKeyboard.js","sources":["../../../../../src/components/UI/DataTable/grid/useGridKeyboard.ts"],"sourcesContent":["import React, { useCallback } from 'react';\nimport { type StoreApi } from 'zustand/vanilla';\n\nimport { type IGridState } from './gridStore';\nimport { GRID_CELL_DATA_ATTR, type IGridCellCoord } from './gridTypes';\n\ninterface IUseGridKeyboardParams {\n store: StoreApi<IGridState> | null;\n /** Whether the cell can open an editor, so Enter/F2 know what to do. */\n canEditCell?: (coord: IGridCellCoord) => boolean;\n onSave?: () => void;\n}\n\nconst CELL_CONTENT_FOCUS_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]',\n]\n .map((selector) => `${selector}:not([tabindex=\"-1\"])`)\n .join(', ');\n\n/** The `<td>` the event came from, so the handler never queries the whole document. */\nconst resolveCellElement = (target: EventTarget | null): HTMLElement | null =>\n target instanceof HTMLElement ? target.closest<HTMLElement>(`[${GRID_CELL_DATA_ATTR}]`) : null;\n\n/**\n * A control that owns the caret (input, textarea, select, contenteditable) keeps\n * every key the grid would otherwise claim: the horizontal arrows walk the text,\n * not the columns.\n */\nconst isTextEntryTarget = (target: EventTarget | null): boolean => {\n if (!(target instanceof HTMLElement)) return false;\n if (target.isContentEditable) return true;\n\n return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT';\n};\n\n/**\n * Tab walks to the neighbouring cell and OPENS its editor when the cell accepts one, so a\n * row is filled by typing and tabbing instead of reopening the editor at every stop.\n * Returns whether the active cell moved, which is what decides if the grid keeps the event.\n */\nconst moveKeepingTheEditorOpen = (\n store: StoreApi<IGridState>,\n columnDelta: 1 | -1,\n canEditCell?: (coord: IGridCellCoord) => boolean,\n): boolean => {\n // Past the last cell the grid gives the focus back to the page: trapping Tab inside a\n // table is a keyboard trap, not a spreadsheet.\n if (!store.getState().moveActiveCell(0, columnDelta, { wrap: true })) return false;\n\n const { activeCell, startEditing } = store.getState();\n if (activeCell && canEditCell?.(activeCell)) startEditing(activeCell);\n\n return true;\n};\n\n/**\n * The grid's ONLY keyboard listener: one React handler on the scroll container,\n * reached by bubbling from the focused cell. No document listeners, no per-cell\n * handlers, so two tables on the same screen never fight over the same keys.\n */\nexport const useGridKeyboard = ({ store, canEditCell, onSave }: IUseGridKeyboardParams) =>\n useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (!store) return;\n\n const state = store.getState();\n const { key } = event;\n\n if ((event.metaKey || event.ctrlKey) && (key === 's' || key === 'S')) {\n if (!onSave) return;\n event.preventDefault();\n onSave();\n return;\n }\n\n const isEditing = !!state.editingCell || isTextEntryTarget(event.target);\n\n if (isEditing) {\n if (key === 'Escape') {\n // The table may live inside a dialog or drawer: leaving the editor must\n // not also close the surface around it.\n event.stopPropagation();\n state.stopEditing('cancel');\n return;\n }\n\n if (key === 'Enter') {\n event.preventDefault();\n state.stopEditing();\n state.moveActiveCell(1, 0);\n return;\n }\n\n if (key === 'Tab') {\n state.stopEditing();\n if (moveKeepingTheEditorOpen(store, event.shiftKey ? -1 : 1, canEditCell)) event.preventDefault();\n }\n\n return;\n }\n\n const cellElement = resolveCellElement(event.target);\n const isInsideCellContent = !!cellElement && event.target !== cellElement;\n\n // Focus went into a control inside the cell (an action button, a link).\n // While it is there the cell content owns the keyboard, and Escape brings\n // the focus back to the cell.\n if (isInsideCellContent) {\n if (key !== 'Escape') return;\n event.preventDefault();\n cellElement.focus();\n return;\n }\n\n const extend = event.shiftKey;\n\n switch (key) {\n case 'ArrowUp':\n event.preventDefault();\n state.moveActiveCell(-1, 0, { extend });\n return;\n case 'ArrowDown':\n event.preventDefault();\n state.moveActiveCell(1, 0, { extend });\n return;\n case 'ArrowLeft':\n event.preventDefault();\n state.moveActiveCell(0, -1, { extend });\n return;\n case 'ArrowRight':\n event.preventDefault();\n state.moveActiveCell(0, 1, { extend });\n return;\n case 'Tab':\n if (moveKeepingTheEditorOpen(store, event.shiftKey ? -1 : 1, canEditCell)) event.preventDefault();\n return;\n case 'Home':\n event.preventDefault();\n state.moveActiveToEdge(event.ctrlKey || event.metaKey ? -1 : 0, -1, { extend });\n return;\n case 'End':\n event.preventDefault();\n state.moveActiveToEdge(event.ctrlKey || event.metaKey ? 1 : 0, 1, { extend });\n return;\n case 'Enter':\n case 'F2': {\n const active = state.activeCell;\n if (!active) return;\n\n if (canEditCell?.(active)) {\n event.preventDefault();\n state.startEditing(active);\n return;\n }\n\n // Read-only cell: Enter steps INTO whatever control it holds, which is\n // the only way a keyboard user reaches the per-row actions now that Tab\n // walks the cells.\n const focusableInsideCell = cellElement?.querySelector<HTMLElement>(CELL_CONTENT_FOCUS_SELECTOR);\n if (focusableInsideCell) {\n event.preventDefault();\n focusableInsideCell.focus();\n return;\n }\n\n if (key !== 'Enter') return;\n event.preventDefault();\n state.moveActiveCell(1, 0);\n\n return;\n }\n case 'Escape':\n state.clearSelection();\n return;\n default:\n }\n },\n [store, canEditCell, onSave],\n );\n"],"names":[],"mappings":"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"}
|