From f607891cebc32206b59bc3237d3f0b73f74d3301 Mon Sep 17 00:00:00 2001 From: heswell Date: Mon, 16 Oct 2023 21:16:55 +0100 Subject: [PATCH 1/4] remove reprecated Portal, fixes in COntextMenu --- .../vuu-data/src/connection-manager.ts | 1 + .../array-backed-moving-window.ts | 24 ++++++- .../vuu-data/src/server-proxy/viewport.ts | 3 +- vuu-ui/packages/vuu-data/src/worker.ts | 5 +- .../vuu-data/test/server-proxy.test.ts | 26 +++---- vuu-ui/packages/vuu-data/test/test-utils.ts | 5 +- .../vuu-layout/src/drag-drop/Draggable.ts | 2 +- .../packages/vuu-popups/src/dialog/Dialog.tsx | 6 +- .../vuu-popups/src/menu/ContextMenu.css | 7 -- .../vuu-popups/src/menu/ContextMenu.tsx | 51 ++++++------- .../packages/vuu-popups/src/menu/MenuList.css | 2 +- .../packages/vuu-popups/src/menu/MenuList.tsx | 26 +++++-- .../vuu-popups/src/menu/list-dom-utils.ts | 7 +- .../vuu-popups/src/menu/use-cascade.ts | 71 +++++++++++++++---- .../src/menu/use-keyboard-navigation.ts | 7 +- .../vuu-popups/src/menu/useContextMenu.tsx | 1 - vuu-ui/packages/vuu-popups/src/menu/utils.ts | 2 +- .../packages/vuu-popups/src/popup/Popup.tsx | 8 ++- .../vuu-popups/src/popup/popup-service.ts | 1 - .../src/popup/useAnchoredPosition.ts | 35 +++++---- .../portal-deprecated/PortalDeprecated.tsx | 58 --------------- .../vuu-popups/src/portal-deprecated/index.ts | 2 - .../src/portal-deprecated/portal-utils.ts | 9 --- .../packages/vuu-popups/src/portal/Portal.tsx | 10 +++ .../packages/vuu-table/src/table/useTable.ts | 4 -- .../src/drag-drop/Draggable.tsx | 33 ++++++--- vuu-ui/showcase/src/examples/Layout/index.ts | 1 - .../examples/Popups/ContextMenu.examples.tsx | 7 +- .../{Layout => Popups}/Dialog.examples.tsx | 0 vuu-ui/showcase/src/examples/Popups/index.ts | 1 + vuu-ui/showcase/src/index.css | 11 ++- vuu-ui/showcase/src/index.tsx | 2 +- 32 files changed, 236 insertions(+), 192 deletions(-) delete mode 100644 vuu-ui/packages/vuu-popups/src/menu/ContextMenu.css delete mode 100644 vuu-ui/packages/vuu-popups/src/portal-deprecated/PortalDeprecated.tsx delete mode 100644 vuu-ui/packages/vuu-popups/src/portal-deprecated/portal-utils.ts rename vuu-ui/showcase/src/examples/{Layout => Popups}/Dialog.examples.tsx (100%) diff --git a/vuu-ui/packages/vuu-data/src/connection-manager.ts b/vuu-ui/packages/vuu-data/src/connection-manager.ts index ce9d3b15b..086cc3763 100644 --- a/vuu-ui/packages/vuu-data/src/connection-manager.ts +++ b/vuu-ui/packages/vuu-data/src/connection-manager.ts @@ -164,6 +164,7 @@ function handleMessageFromWorker({ } else if (isConnectionStatusMessage(message)) { ConnectionManager.emit("connection-status", message); } else if (isConnectionQualityMetrics(message)) { + console.log({ message }); ConnectionManager.emit("connection-metrics", message); } else { const requestId = (message as VuuUIMessageInRPC).requestId; diff --git a/vuu-ui/packages/vuu-data/src/server-proxy/array-backed-moving-window.ts b/vuu-ui/packages/vuu-data/src/server-proxy/array-backed-moving-window.ts index a5357ecd5..caf95e1be 100644 --- a/vuu-ui/packages/vuu-data/src/server-proxy/array-backed-moving-window.ts +++ b/vuu-ui/packages/vuu-data/src/server-proxy/array-backed-moving-window.ts @@ -7,6 +7,23 @@ type RangeTuple = [boolean, readonly VuuRow[] /*, readonly VuuRow[]*/]; const log = logger("array-backed-moving-window"); +function dataIsUnchanged(newRow: VuuRow, existingRow?: VuuRow) { + if (!existingRow) { + return false; + } + + if (existingRow.sel !== newRow.sel) { + return false; + } + + for (let i = 0; i < existingRow.data.length; i++) { + if (existingRow.data[i] !== newRow.data[i]) { + return false; + } + } + return true; +} + export class ArrayBackedMovingWindow { #range: WindowRange; @@ -79,9 +96,14 @@ export class ArrayBackedMovingWindow { setAtIndex(row: VuuRow) { const { rowIndex: index } = row; + const internalIndex = index - this.#range.from; + //TODO measure the performance impact of this check + if (dataIsUnchanged(row, this.internalData[internalIndex])) { + console.log("data unchanged"); + return false; + } const isWithinClientRange = this.isWithinClientRange(index); if (isWithinClientRange || this.isWithinRange(index)) { - const internalIndex = index - this.#range.from; if (!this.internalData[internalIndex] && isWithinClientRange) { this.rowsWithinRange += 1; } diff --git a/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts b/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts index 4a51c0a71..80b7c2a37 100644 --- a/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts +++ b/vuu-ui/packages/vuu-data/src/server-proxy/viewport.ts @@ -258,7 +258,6 @@ export class Viewport { if (lastMode === mode) { const ts = Date.now(); - console.log(`read data now ${ts}`); this.lastUpdateStatus.count += 1; this.lastUpdateStatus.ts = ts; elapsedTime = lastTS === 0 ? 0 : ts - lastTS; @@ -930,7 +929,7 @@ export class Viewport { private throttleMessage = (mode: DataUpdateMode) => { if (this.shouldThrottleMessage(mode)) { - console.log("throttling updates setTimeout to 2000"); + info?.("throttling updates setTimeout to 2000"); if (this.updateThrottleTimer === undefined) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/vuu-ui/packages/vuu-data/src/worker.ts b/vuu-ui/packages/vuu-data/src/worker.ts index 35952e442..881746c68 100644 --- a/vuu-ui/packages/vuu-data/src/worker.ts +++ b/vuu-ui/packages/vuu-data/src/worker.ts @@ -36,9 +36,10 @@ async function connectToServer( // never be called until subscriptions have been made, so this is safe. //TODO do we need to listen in to the connection messages here so we can lock back in, in the event of a reconnenct ? (msg) => { - if (isConnectionQualityMetrics(msg)) + if (isConnectionQualityMetrics(msg)) { + console.log("post connection metrics"); postMessage({ type: "connection-metrics", messages: msg }); - else if (isConnectionStatusMessage(msg)) { + } else if (isConnectionStatusMessage(msg)) { onConnectionStatusChange(msg); if (msg.status === "reconnected") { server.reconnect(); diff --git a/vuu-ui/packages/vuu-data/test/server-proxy.test.ts b/vuu-ui/packages/vuu-data/test/server-proxy.test.ts index fc78984e7..0824c8771 100644 --- a/vuu-ui/packages/vuu-data/test/server-proxy.test.ts +++ b/vuu-ui/packages/vuu-data/test/server-proxy.test.ts @@ -2574,9 +2574,9 @@ describe("ServerProxy", () => { body: { ...COMMON_TABLE_ROW_ATTRS, rows: [ - ...createTableRows("server-vp-1", 0, 1, 100, 1, 1), + ...createTableRows("server-vp-1", 0, 1, 100, 1, 1, 2000), sizeRow("server-vp-2", 20), - ...createTableRows("server-vp-2", 0, 10), + ...createTableRows("server-vp-2", 0, 10, 100, 2, 0, 2000), ], }, }); @@ -2587,7 +2587,7 @@ describe("ServerProxy", () => { { mode: "update", rows: [ - [0,0,true,false,0,0,'key-00', 0,'key-00', 'name 00',1000,true], + [0,0,true,false,0,0,'key-00', 0,'key-00', 'name 00',2000,true], ], type: 'viewport-update', clientViewportId: 'client-vp-1' @@ -2598,16 +2598,16 @@ describe("ServerProxy", () => { { mode: "batch", rows: [ - [0,0,true,false,0,0,'key-00', 0,'key-00', 'name 00',1000,true], - [1,1,true,false,0,0,"key-01",0,"key-01","name 01",1001,true], - [2,2,true,false,0,0,"key-02",0,"key-02","name 02",1002,true], - [3,3,true,false,0,0,"key-03",0,"key-03","name 03",1003,true], - [4,4,true,false,0,0,"key-04",0,"key-04","name 04",1004,true], - [5,5,true,false,0,0,"key-05",0,"key-05","name 05",1005,true], - [6,6,true,false,0,0,"key-06",0,"key-06","name 06",1006,true], - [7,7,true,false,0,0,"key-07",0,"key-07","name 07",1007,true], - [8,8,true,false,0,0,"key-08",0,"key-08","name 08",1008,true], - [9,9,true,false,0,0,"key-09",0,"key-09","name 09",1009,true] + [0,0,true,false,0,0,'key-00', 0,'key-00', 'name 00',2000,true], + [1,1,true,false,0,0,"key-01",0,"key-01","name 01",2001,true], + [2,2,true,false,0,0,"key-02",0,"key-02","name 02",2002,true], + [3,3,true,false,0,0,"key-03",0,"key-03","name 03",2003,true], + [4,4,true,false,0,0,"key-04",0,"key-04","name 04",2004,true], + [5,5,true,false,0,0,"key-05",0,"key-05","name 05",2005,true], + [6,6,true,false,0,0,"key-06",0,"key-06","name 06",2006,true], + [7,7,true,false,0,0,"key-07",0,"key-07","name 07",2007,true], + [8,8,true,false,0,0,"key-08",0,"key-08","name 08",2008,true], + [9,9,true,false,0,0,"key-09",0,"key-09","name 09",2009,true] ], size: 100, type: 'viewport-update', diff --git a/vuu-ui/packages/vuu-data/test/test-utils.ts b/vuu-ui/packages/vuu-data/test/test-utils.ts index 44e2570fc..43a4d2e0c 100644 --- a/vuu-ui/packages/vuu-data/test/test-utils.ts +++ b/vuu-ui/packages/vuu-data/test/test-utils.ts @@ -46,14 +46,15 @@ export const createTableRows = ( to, vpSize = 100, ts = 1, - sel: 0 | 1 = 0 + sel: 0 | 1 = 0, + numericValue = 1000 ): VuuRow[] => { const results: VuuRow[] = []; for (let rowIndex = from; rowIndex < to; rowIndex++) { const key = ("0" + rowIndex).slice(-2); const rowKey = `key-${key}`; results.push({ - data: [rowKey, `name ${key}`, 1000 + rowIndex, true], + data: [rowKey, `name ${key}`, numericValue + rowIndex, true], rowIndex, rowKey, updateType: "U", diff --git a/vuu-ui/packages/vuu-layout/src/drag-drop/Draggable.ts b/vuu-ui/packages/vuu-layout/src/drag-drop/Draggable.ts index f0f84b5d8..859abf781 100644 --- a/vuu-ui/packages/vuu-layout/src/drag-drop/Draggable.ts +++ b/vuu-ui/packages/vuu-layout/src/drag-drop/Draggable.ts @@ -2,7 +2,7 @@ import { rect } from "@finos/vuu-utils"; import { ReactElement } from "react"; import { LayoutModel } from "../layout-reducer"; import { findTarget, followPath, getProps } from "../utils"; -import { BoxModel, Measurements, Position } from "./BoxModel"; +import { BoxModel, Measurements } from "./BoxModel"; import { DragDropRect } from "./dragDropTypes"; import { DragState, IntrinsicSizes } from "./DragState"; import { DropTarget, identifyDropTarget } from "./DropTarget"; diff --git a/vuu-ui/packages/vuu-popups/src/dialog/Dialog.tsx b/vuu-ui/packages/vuu-popups/src/dialog/Dialog.tsx index f3f487a4f..00613ff6a 100644 --- a/vuu-ui/packages/vuu-popups/src/dialog/Dialog.tsx +++ b/vuu-ui/packages/vuu-popups/src/dialog/Dialog.tsx @@ -1,7 +1,7 @@ import { Scrim } from "@salt-ds/lab"; import cx from "classnames"; import { HTMLAttributes, useCallback, useRef, useState } from "react"; -import { PortalDeprecated } from "../portal-deprecated"; +import { Portal } from "../portal"; import { DialogHeader } from "../dialog-header"; import "./Dialog.css"; @@ -47,7 +47,7 @@ export const Dialog = ({ } return ( - +
- + ); }; diff --git a/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.css b/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.css deleted file mode 100644 index 6856ab946..000000000 --- a/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.css +++ /dev/null @@ -1,7 +0,0 @@ -.vuuContextMenu { - border-radius:4px; - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - border-color: var(--vuuMenuList-borderColor, var(--salt-container-primary-borderColor)); - border-style: solid; - border-width: 1px; -} \ No newline at end of file diff --git a/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx b/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx index b65a1e078..1b5adc9f4 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx +++ b/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx @@ -1,14 +1,11 @@ import { useCallback, useRef } from "react"; -import { PortalDeprecated } from "../portal-deprecated"; import { MenuList, MenuListProps } from "./MenuList"; import { useCascade } from "./use-cascade"; -// import { useClickAway } from "./use-click-away"; import { useItemsWithIdsNext } from "./use-items-with-ids-next"; import { useId } from "@finos/vuu-layout"; import { PopupCloseCallback } from "../popup"; import { ContextMenuOptions } from "./useContextMenu"; - -import "./ContextMenu.css"; +import { PopupComponent as Popup, Portal } from "@finos/vuu-popups"; export interface ContextMenuProps extends Omit { onClose?: PopupCloseCallback; @@ -53,7 +50,7 @@ export const ContextMenu = ({ [actions, id, onClose] ); - const { closeMenu, listItemProps, openMenu, openMenus, handleRender } = + const { closeMenu, listItemProps, onOpenMenu, openMenus, handleRender } = useCascade({ // FIXME id: `${id}`, @@ -89,26 +86,32 @@ export const ContextMenu = ({ const childMenuId = getChildMenuId(i); // TODO don't need the portal here, vuu popup service takes care of this return ( - - + - {menus[menuId]} - - + + {menus[menuId]} + + + ); })} diff --git a/vuu-ui/packages/vuu-popups/src/menu/MenuList.css b/vuu-ui/packages/vuu-popups/src/menu/MenuList.css index e636c2163..7909a954b 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/MenuList.css +++ b/vuu-ui/packages/vuu-popups/src/menu/MenuList.css @@ -6,7 +6,7 @@ --context-menu-padding: var(--hw-list-item-padding, 0 6px); --context-menu-shadow: var(--hw-dialog-shadow, 0 6px 12px rgba(0, 0, 0, 0.175)); --focus-visible-border-color: var(--hw-focus-visible-border-color, rgb(141, 154, 179)); - --context-menu-highlight-bg: var(--vuu-color-gray-10); + --context-menu-highlight-bg: var(--salt-selectable-background-hover); --context-menu-blur-focus-bg: #e0e4e9; --menu-item-icon-color: black; --menu-item-twisty-color: black; diff --git a/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx b/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx index bf4224993..8bc759ddf 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx +++ b/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx @@ -35,8 +35,20 @@ export interface MenuItemProps extends HTMLAttributes { // Purely used as markers, props will be extracted export const MenuItemGroup: FC = () => null; // eslint-disable-next-line no-unused-vars -export const MenuItem = ({ children, idx, ...props }: MenuItemProps) => { - return
{children}
; +export const MenuItem = ({ + children, + idx, + options, + ...props +}: MenuItemProps) => { + const handleClick = () => { + console.log("click within menuItem"); + }; + return ( +
+ {children} +
+ ); }; export interface MenuItemLabelProps { @@ -62,6 +74,10 @@ export const isMenuItemLabel = ( const hasIcon = (child: ReactElement) => child.props["data-icon"]; +export type MenuOpenHandler = ( + menuItemEl: HTMLElement, + immediate?: boolean +) => void; export interface MenuListProps extends HTMLAttributes { activatedByKeyboard?: boolean; children: ReactElement[]; @@ -72,7 +88,7 @@ export interface MenuListProps extends HTMLAttributes { listItemProps?: Partial; onActivate?: (menuId: string) => void; onCloseMenu: (idx: number) => void; - onOpenMenu?: (menuItemEl: HTMLElement) => void; + onOpenMenu?: MenuOpenHandler; onHighlightMenuItem?: (idx: number) => void; } @@ -99,7 +115,7 @@ export const MenuList = ({ const mapIdxToId = useMemo(() => new Map(), []); const handleActivate = (idx: number) => { - const el = root.current?.querySelector(`:scope > [data-idx='${idx}']`); + const el = root.current?.querySelector(`:scope > [data-index='${idx}']`); el?.id && onActivate?.(el.id); }; @@ -232,7 +248,7 @@ const getMenuItemProps = ( ) => ({ id: `menuitem-${itemId}`, key: key ?? idx, - "data-idx": idx, + "data-index": idx, "data-highlighted": idx === highlightedIdx || undefined, className: cx("vuuMenuItem", className, { "vuuMenuItem-separator": hasSeparator, diff --git a/vuu-ui/packages/vuu-popups/src/menu/list-dom-utils.ts b/vuu-ui/packages/vuu-popups/src/menu/list-dom-utils.ts index e34cd4157..dbcacfcc0 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/list-dom-utils.ts +++ b/vuu-ui/packages/vuu-popups/src/menu/list-dom-utils.ts @@ -1,9 +1,6 @@ -// const listItemElement = (listEl: HTMLElement, listItemIdx: number) => -// listEl.querySelector(`:scope > [data-idx="${listItemIdx}"]`); - export function listItemIndex(listItemEl: HTMLElement) { if (listItemEl) { - const idx = listItemEl.dataset.idx; + const idx = listItemEl.dataset.index; if (idx) { return parseInt(idx, 10); // eslint-disable-next-line no-cond-assign @@ -16,7 +13,7 @@ export function listItemIndex(listItemEl: HTMLElement) { const listItemId = (el: HTMLElement | null | undefined) => el?.id; export const closestListItem = (el: HTMLElement | null | undefined) => - el?.closest("[data-idx],[aria-posinset]") as HTMLElement; + el?.closest("[data-index],[aria-posinset]") as HTMLElement; export const closestListItemId = (el: HTMLElement) => listItemId(closestListItem(el)); diff --git a/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts b/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts index 83ec46116..b92c37280 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts +++ b/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts @@ -8,7 +8,7 @@ import { } from "react"; import { closestListItem } from "./list-dom-utils"; -import { MenuItemProps } from "./MenuList"; +import { MenuItemProps, MenuOpenHandler } from "./MenuList"; // import {mousePosition} from './aim/utils'; // import {aiming} from './aim/aim'; @@ -65,9 +65,15 @@ export type RuntimeMenuDescriptor = { top: number; }; +/** menuitem-vuu-1-0 vuu-1 */ export const getHostMenuId = (id: string, rootId: string) => { + console.log(`getHostMenuId from ${id} and ${rootId}`); const pos = id.lastIndexOf("-"); - return pos > -1 ? id.slice(9, pos) : rootId; + if (id.startsWith("menuitem")) { + return pos > -1 ? id.slice(9, pos) : rootId; + } else { + return pos > -1 ? id.slice(0, pos) : rootId; + } }; const getTargetMenuId = (id: string) => id.slice(9); @@ -100,7 +106,7 @@ export interface CascadeHooksResult { closeMenu: () => void; handleRender: () => void; listItemProps: Partial; - openMenu: (menuItemEl: HTMLElement) => void; + onOpenMenu: MenuOpenHandler; openMenus: RuntimeMenuDescriptor[]; } @@ -133,6 +139,9 @@ export const useCascade = ({ }, []); const setOpenMenus = useCallback((menus: RuntimeMenuDescriptor[]) => { + console.log(`setOpenMenus`, { + menus, + }); openMenus.current = menus; forceRefresh({}); }, []); @@ -146,9 +155,13 @@ export const useCascade = ({ const openMenu = useCallback( (hostMenuId = rootId, targetMenuId: string, itemId = null) => { + console.log( + `open menu hostMenuId ${hostMenuId} targetMenuId ${targetMenuId} itemId ${itemId}` + ); if (hostMenuId === rootId && itemId === null) { setOpenMenus([{ id: rootId, left: posX, top: posY }]); } else { + console.log(`openMenu set ${hostMenuId} status to popup-open`); menuState.current[hostMenuId] = "popup-open"; const el = document.getElementById(itemId) as HTMLElement; if (el !== null) { @@ -166,7 +179,9 @@ export const useCascade = ({ const closeMenu = useCallback( (menuId?: string) => { + console.log(`closeMenu ${menuId}`); if (menuId === rootId) { + console.log("close child menu of root"); setOpenMenus([]); } else { const menus = openMenus.current.slice(); @@ -176,6 +191,9 @@ export const useCascade = ({ if (parentMenu) { menuState.current[parentMenu.id] = "no-popup"; } + console.log(`closeMenu setOpenMenus`, { + menus, + }); setOpenMenus(menus); } }, @@ -184,17 +202,27 @@ export const useCascade = ({ const closeMenus = useCallback( (menuItemId) => { + console.log(`closeMenus ${menuItemId}`); const menus = openMenus.current.slice(); const menuItemMenuId = menuItemId.slice(9); let { id: lastMenuId } = menus.at(-1) as RuntimeMenuDescriptor; while (menus.length > 1 && !menuItemMenuId.startsWith(lastMenuId)) { const parentMenuId = getHostMenuId(lastMenuId, rootId); + console.log( + `parentMenuId of lastMenuId ${lastMenuId} and rootId ${rootId} is ${parentMenuId}` + ); menus.pop(); + console.log( + `set state to no-popup for ${lastMenuId} and ${parentMenuId}` + ); menuState.current[lastMenuId] = "no-popup"; menuState.current[parentMenuId] = "no-popup"; ({ id: lastMenuId } = menus[menus.length - 1]); } if (menus.length < openMenus.current.length) { + console.log(`closeMenus setOpenMenus`, { + menus, + }); setOpenMenus(menus); } }, @@ -209,7 +237,12 @@ export const useCascade = ({ }, []); const scheduleOpen = useCallback( - (hostMenuId: string, targetMenuId: string, menuItemId: string) => { + ( + hostMenuId: string, + targetMenuId: string, + menuItemId: string, + delay = 300 + ) => { clearAnyScheduledOpenTasks(); // do we need to set target state to pending-open ?s @@ -221,7 +254,7 @@ export const useCascade = ({ menuState.current[hostMenuId] = "popup-open"; menuState.current[targetMenuId] = "no-popup"; openMenu(hostMenuId, targetMenuId, menuItemId); - }, 400); + }, delay); }, [clearAnyScheduledOpenTasks, closeMenus, openMenu] ); @@ -233,6 +266,7 @@ export const useCascade = ({ // ); menuState.current[openMenuId] = "pending-close"; menuClosePendingTimeout.current = window.setTimeout(() => { + // console.log(`call closeMenus from scheduleClose`); closeMenus(itemId); }, 400); }, @@ -266,17 +300,19 @@ export const useCascade = ({ }, [rootId, setOpenMenus]); // TODO introduce a delay parameter that allows click to requeat an immediate render - const triggerChildMenu = useCallback( - (menuItemEl: HTMLElement) => { + const triggerChildMenu = useCallback( + (menuItemEl, immediate = false) => { const { hostMenuId, targetMenuId, menuItemId, isGroup, isOpen } = getMenuItemDetails(menuItemEl, rootId); - const { current: { [hostMenuId]: state }, } = menuState; + const delay = immediate ? 0 : undefined; + // console.log( // `%ctriggerChildMenu + // rootId ${rootId} // menuItem ${menuItemId} // host menu: ${hostMenuId} // target menu: ${targetMenuId} @@ -290,14 +326,14 @@ export const useCascade = ({ if (state === "no-popup" && isGroup) { menuState.current[hostMenuId] = "popup-pending"; - scheduleOpen(hostMenuId, targetMenuId, menuItemId); + scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay); } else if (state === "popup-pending" && !isGroup) { menuState.current[hostMenuId] = "no-popup"; clearTimeout(menuOpenPendingTimeout.current); menuOpenPendingTimeout.current = undefined; } else if (state === "popup-pending" && isGroup) { clearTimeout(menuOpenPendingTimeout.current); - scheduleOpen(hostMenuId, targetMenuId, menuItemId); + scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay); } else if (state === "popup-open") { if (menuIsOpen(targetMenuId)) { const menuStatus = getOpenMenuStatus(targetMenuId); @@ -320,6 +356,11 @@ export const useCascade = ({ // TODO review the below, suspectb it's over complicating things const [parentOfLastOpenedMenu, lastOpenedMenu] = openMenus.current.slice(-2); + console.log(`about to check id on `, { + openMenus, + parentOfLastOpenedMenu, + lastOpenedMenu, + }); if ( parentOfLastOpenedMenu.id === hostMenuId && menuState.current[lastOpenedMenu.id] !== "pending-close" /*&& @@ -327,7 +368,7 @@ export const useCascade = ({ ) { scheduleClose(hostMenuId, lastOpenedMenu.id, menuItemId); if (isGroup && !isOpen) { - scheduleOpen(hostMenuId, targetMenuId, menuItemId); + scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay); } } else if ( parentOfLastOpenedMenu.id === hostMenuId && @@ -336,10 +377,10 @@ export const useCascade = ({ menuState.current[lastOpenedMenu.id] === "pending-close" ) { // if there is already an item queued for opening cancel it - scheduleOpen(hostMenuId, targetMenuId, menuItemId); + scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay); } else if (isGroup) { // closeMenus(menuId, itemId); - scheduleOpen(hostMenuId, targetMenuId, menuItemId); + scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay); } else if ( !( (menuState.current[lastOpenedMenu.id] === "pending-close") /*&& @@ -373,11 +414,13 @@ export const useCascade = ({ () => ({ onMouseEnter: (evt: MouseEvent) => { const menuItemEl = closestListItem(evt.target as HTMLElement); + console.log(`onMouseEnter ${menuItemEl?.id}`); triggerChildMenu(menuItemEl); onMouseEnterItem(evt, menuItemEl.id); }, onClick: (evt: SyntheticEvent) => { + console.log("click"); const listItemEl = closestListItem(evt.target as HTMLElement); const { isGroup, menuItemId } = getMenuItemDetails(listItemEl, rootId); if (isGroup) { @@ -394,7 +437,7 @@ export const useCascade = ({ closeMenu, handleRender, listItemProps, - openMenu: triggerChildMenu, + onOpenMenu: triggerChildMenu, openMenus: openMenus.current, }; }; diff --git a/vuu-ui/packages/vuu-popups/src/menu/use-keyboard-navigation.ts b/vuu-ui/packages/vuu-popups/src/menu/use-keyboard-navigation.ts index 5cd06a0bd..3d2ca4b7c 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/use-keyboard-navigation.ts +++ b/vuu-ui/packages/vuu-popups/src/menu/use-keyboard-navigation.ts @@ -9,6 +9,7 @@ import { import { hasPopup, isRoot } from "./utils"; import { isNavigationKey } from "./key-code"; import { isValidNumber } from "@finos/vuu-utils"; +import { MenuOpenHandler } from "./MenuList"; export interface KeyboardNavigationProps { autoHighlightFirstItem?: boolean; @@ -18,7 +19,7 @@ export interface KeyboardNavigationProps { onActivate: (idx: number) => void; onHighlight?: (idx: number) => void; onCloseMenu: (idx: number) => void; - onOpenMenu?: (menuItemEl: HTMLElement) => void; + onOpenMenu?: MenuOpenHandler; } export interface KeyboardHookListProps { @@ -121,11 +122,11 @@ export const useKeyboardNavigation = ({ ) { const menuEl = e.target as HTMLElement; const menuItemEl = menuEl.querySelector( - `:scope > [data-idx='${highlightedIndex}']` + `:scope > [data-index='${highlightedIndex}']` ) as HTMLElement; if (menuItemEl) { - onOpenMenu?.(menuItemEl); + onOpenMenu?.(menuItemEl, true); } } else if (e.key === "ArrowLeft" && !isRoot(e.target as HTMLElement)) { onCloseMenu(highlightedIndex); diff --git a/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx b/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx index 17edd2728..a92f3c4c8 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx +++ b/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx @@ -202,7 +202,6 @@ const showContextMenu = ( const component = ( diff --git a/vuu-ui/packages/vuu-popups/src/menu/utils.ts b/vuu-ui/packages/vuu-popups/src/menu/utils.ts index ee8b35a24..ced575be1 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/utils.ts +++ b/vuu-ui/packages/vuu-popups/src/menu/utils.ts @@ -3,5 +3,5 @@ export const isRoot = (el: HTMLElement) => export const hasPopup = (el: HTMLElement, idx: number) => (el.ariaHasPopup === "true" && el.dataset?.idx === `${idx}`) || - el.querySelector(`:scope > [data-idx='${idx}'][aria-haspopup='true']`) !== + el.querySelector(`:scope > [data-index='${idx}'][aria-haspopup='true']`) !== null; diff --git a/vuu-ui/packages/vuu-popups/src/popup/Popup.tsx b/vuu-ui/packages/vuu-popups/src/popup/Popup.tsx index 312ff66f7..e31cf93fa 100644 --- a/vuu-ui/packages/vuu-popups/src/popup/Popup.tsx +++ b/vuu-ui/packages/vuu-popups/src/popup/Popup.tsx @@ -1,11 +1,12 @@ import cx from "classnames"; import { useThemeAttributes } from "@finos/vuu-shell"; import { HTMLAttributes, RefObject } from "react"; -import { useAnchoredPosition } from "./useAnchoredPosition"; +import { Position, useAnchoredPosition } from "./useAnchoredPosition"; import "./Popup.css"; export type PopupPlacement = + | "absolute" | "below" | "below-center" | "below-full-width" @@ -13,11 +14,12 @@ export type PopupPlacement = | "right"; export interface PopupComponentProps extends HTMLAttributes { - placement: PopupPlacement; anchorElement: RefObject; minWidth?: number; offsetLeft?: number; offsetTop?: number; + placement: PopupPlacement; + position?: Position; } export const PopupComponent = ({ @@ -26,12 +28,14 @@ export const PopupComponent = ({ anchorElement, minWidth, placement, + position: positionProp, }: PopupComponentProps) => { const [themeClass, densityClass, dataMode] = useThemeAttributes(); const { popupRef, position } = useAnchoredPosition({ anchorElement, minWidth, placement, + position: positionProp, }); return position === undefined ? null : (
; - minWidth?: number; - offsetLeft?: number; - offsetTop?: number; - placement: PopupPlacement; -} +export type AnchoredPositionHookProps = Pick< + PopupComponentProps, + | "anchorElement" + | "minWidth" + | "offsetLeft" + | "offsetTop" + | "placement" + | "position" +>; export type Visibility = "hidden" | "visible"; @@ -82,13 +78,16 @@ export const useAnchoredPosition = ({ offsetLeft = 0, offsetTop = 0, placement, + position: positionProp, }: AnchoredPositionHookProps) => { const popupRef = useRef(null); - const [position, setPosition] = useState(); + const [position, setPosition] = useState(positionProp); // maybe better as useMemo ? useLayoutEffect(() => { - if (anchorElement.current) { + if (placement === "absolute" && positionProp) { + setPosition(positionProp); + } else if (anchorElement.current) { const dimensions = popupRef.current === null ? undefined @@ -103,7 +102,7 @@ export const useAnchoredPosition = ({ ); setPosition(position); } - }, [anchorElement, minWidth, offsetLeft, offsetTop, placement]); + }, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]); const popupCallbackRef = useCallback( (el: HTMLDivElement | null) => { diff --git a/vuu-ui/packages/vuu-popups/src/portal-deprecated/PortalDeprecated.tsx b/vuu-ui/packages/vuu-popups/src/portal-deprecated/PortalDeprecated.tsx deleted file mode 100644 index 098eb1452..000000000 --- a/vuu-ui/packages/vuu-popups/src/portal-deprecated/PortalDeprecated.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { ReactElement, useLayoutEffect, useMemo } from "react"; -import * as ReactDOM from "react-dom"; -import { createContainer, renderPortal } from "./render-portal"; -import { useThemeAttributes } from "@finos/vuu-shell"; -import cx from "classnames"; - -export interface PortalDeprecatedProps { - children: ReactElement; - onRender?: () => void; - x?: number; - y?: number; -} - -export const PortalDeprecated = function Portal({ - children, - x = 0, - y = 0, - onRender, -}: PortalDeprecatedProps) { - // Do we need to accept container here as a prop ? - const [themeClass, densityClass, dataMode] = useThemeAttributes(); - const renderContainer = useMemo(() => { - return createContainer({ - className: cx(themeClass, densityClass), - dataMode, - }); - }, [dataMode, densityClass, themeClass]); - - useLayoutEffect(() => { - renderPortal(children, renderContainer, x, y, onRender); - }, [children, onRender, renderContainer, x, y]); - - useLayoutEffect(() => { - return () => { - if (renderContainer) { - ReactDOM.unmountComponentAtNode(renderContainer); - if (renderContainer.classList.contains("vuuPopup")) { - renderContainer.parentElement?.removeChild(renderContainer); - } - } - }; - }, [renderContainer]); - - // useLayoutEffect(() => { - // renderContainer.current = renderPortal(children, x, y, container) - // return () => { - // if (renderContainer.current){ - // console.log('EXPLICIT UNMOUNT') - // ReactDOM.unmountComponentAtNode(renderContainer.current); - // if (renderContainer.current.classList.contains('hwReactPopup')){ - // renderContainer.current.parentElement.removeChild(renderContainer.current); - // renderContainer.current = null; - // } - // } - // } - // },[]) - return null; -}; diff --git a/vuu-ui/packages/vuu-popups/src/portal-deprecated/index.ts b/vuu-ui/packages/vuu-popups/src/portal-deprecated/index.ts index abef1b876..87b43b8b9 100644 --- a/vuu-ui/packages/vuu-popups/src/portal-deprecated/index.ts +++ b/vuu-ui/packages/vuu-popups/src/portal-deprecated/index.ts @@ -1,3 +1 @@ -export * from "./PortalDeprecated"; export * from "./render-portal"; -export * from "./portal-utils"; diff --git a/vuu-ui/packages/vuu-popups/src/portal-deprecated/portal-utils.ts b/vuu-ui/packages/vuu-popups/src/portal-deprecated/portal-utils.ts deleted file mode 100644 index eae63e58b..000000000 --- a/vuu-ui/packages/vuu-popups/src/portal-deprecated/portal-utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const installTheme = (themeId: string) => { - const installedThemes = getComputedStyle(document.body).getPropertyValue( - "--installed-themes" - ); - document.body.style.setProperty( - "--installed-themes", - `${installedThemes} ${themeId}` - ); -}; diff --git a/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx b/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx index 27fb38a94..ce1d218e3 100644 --- a/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx +++ b/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx @@ -21,6 +21,11 @@ export interface PortalProps { * If this node does not exist on the document, it will be created for you. */ id?: string; + /** + * Callback invoked immediately after render (in layoutEffect). Can be + * used to check position vis-a-vis viewport and adjust if out of bounds + */ + onRender?: () => void; /** * Allow conditional rendering of this Portal, if false, will render nothing. * Defaults to true @@ -42,6 +47,7 @@ export const Portal = ({ children, container: containerProp = document.body, id = DEFAULT_ID, + onRender, open = true, }: PortalProps) => { const [mounted, setMounted] = useState(false); @@ -66,6 +72,10 @@ export const Portal = ({ setMounted(true); }, [id, container, themeClass, densityClass, dataMode]); + useLayoutEffect(() => { + onRender?.(); + }, [onRender]); + if (open && mounted && portalRef.current && children) { return createPortal(children, portalRef.current); } diff --git a/vuu-ui/packages/vuu-table/src/table/useTable.ts b/vuu-ui/packages/vuu-table/src/table/useTable.ts index d0a7ed73a..f79664d63 100644 --- a/vuu-ui/packages/vuu-table/src/table/useTable.ts +++ b/vuu-ui/packages/vuu-table/src/table/useTable.ts @@ -108,10 +108,6 @@ export const useTable = ({ size: containerMeasurements.innerSize, }); - console.log( - `rowCount from viewportMeasurements ${viewportMeasurements.rowCount}` - ); - const onSubscribed = useCallback( ({ tableSchema }: DataSourceSubscribedMessage) => { if (tableSchema) { diff --git a/vuu-ui/packages/vuu-ui-controls/src/drag-drop/Draggable.tsx b/vuu-ui/packages/vuu-ui-controls/src/drag-drop/Draggable.tsx index 384fc7d36..87391b994 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/drag-drop/Draggable.tsx +++ b/vuu-ui/packages/vuu-ui-controls/src/drag-drop/Draggable.tsx @@ -6,8 +6,9 @@ import { MutableRefObject, TransitionEventHandler, useCallback, + useMemo, } from "react"; -import { PortalDeprecated } from "@finos/vuu-popups"; +import { PopupComponent as Popup, Portal } from "@finos/vuu-popups"; import "./Draggable.css"; @@ -40,15 +41,29 @@ export const Draggable = forwardRef< ); const forkedRef = useForkRef(forwardedRef, callbackRef); + const position = useMemo( + () => ({ + left: 0, + top: 0, + }), + [] + ); + return ( - -
- + + +
+ + ); }); diff --git a/vuu-ui/showcase/src/examples/Layout/index.ts b/vuu-ui/showcase/src/examples/Layout/index.ts index 4cd468a2c..3018d16ff 100644 --- a/vuu-ui/showcase/src/examples/Layout/index.ts +++ b/vuu-ui/showcase/src/examples/Layout/index.ts @@ -1,5 +1,4 @@ export * as DockLayout from "./DockLayout.examples"; -export * as Dialog from "./Dialog.examples"; export * as DraggableLayout from "./DraggableLayout.examples"; export * as DropMenu from "./DropMenu.examples"; export * as Flexbox from "./Flexbox.examples"; diff --git a/vuu-ui/showcase/src/examples/Popups/ContextMenu.examples.tsx b/vuu-ui/showcase/src/examples/Popups/ContextMenu.examples.tsx index 4fdaeb9a8..49212dad2 100644 --- a/vuu-ui/showcase/src/examples/Popups/ContextMenu.examples.tsx +++ b/vuu-ui/showcase/src/examples/Popups/ContextMenu.examples.tsx @@ -83,7 +83,12 @@ export const DefaultContextMenu = () => { return (
{position.x !== -1 && position.y !== -1 ? ( diff --git a/vuu-ui/showcase/src/examples/Layout/Dialog.examples.tsx b/vuu-ui/showcase/src/examples/Popups/Dialog.examples.tsx similarity index 100% rename from vuu-ui/showcase/src/examples/Layout/Dialog.examples.tsx rename to vuu-ui/showcase/src/examples/Popups/Dialog.examples.tsx diff --git a/vuu-ui/showcase/src/examples/Popups/index.ts b/vuu-ui/showcase/src/examples/Popups/index.ts index 669e268c0..cf962c666 100644 --- a/vuu-ui/showcase/src/examples/Popups/index.ts +++ b/vuu-ui/showcase/src/examples/Popups/index.ts @@ -1,3 +1,4 @@ export * as ContextMenu from "./ContextMenu.examples"; +export * as Dialog from "./Dialog.examples"; export * as PopupMenu from "./PopupMenu.examples"; export * as Tooltip from "./Tooltip.examples"; diff --git a/vuu-ui/showcase/src/index.css b/vuu-ui/showcase/src/index.css index 065a9d9c6..983babbe6 100644 --- a/vuu-ui/showcase/src/index.css +++ b/vuu-ui/showcase/src/index.css @@ -24,4 +24,13 @@ body { } /* .vuuTooltrayProxy { -} */ \ No newline at end of file +} */ + +.vuuShowcase-StandaloneRoot { + height: 100vh; + /** this prevents root being displaced by component margin */ + position:absolute; + left:0; + top:0; + width: 100vw; +} \ No newline at end of file diff --git a/vuu-ui/showcase/src/index.tsx b/vuu-ui/showcase/src/index.tsx index fca0c8017..7bf7af431 100644 --- a/vuu-ui/showcase/src/index.tsx +++ b/vuu-ui/showcase/src/index.tsx @@ -101,7 +101,7 @@ if (hasUrlParameter("standalone")) { density="high" themeMode="light" > -
+
, From 8d0100f45f081bf2fcfcb592bc7c7a46448a60dd Mon Sep 17 00:00:00 2001 From: heswell Date: Tue, 17 Oct 2023 16:06:54 +0100 Subject: [PATCH 2/4] use woff2 font, fix portal --- .../filter-builder-menu/FilterBuilderMenu.tsx | 2 +- .../filter-clause/useFilterClauseEditor.ts | 23 +++-- .../vuu-popups/src/menu/ContextMenu.tsx | 23 +++-- .../packages/vuu-popups/src/menu/MenuList.tsx | 13 +-- .../vuu-popups/src/menu/use-cascade.ts | 9 +- .../vuu-popups/src/menu/useContextMenu.tsx | 16 +--- .../vuu-popups/src/popup-menu/PopupMenu.tsx | 20 ++++- .../vuu-popups/src/popup/popup-service.ts | 81 +----------------- .../packages/vuu-popups/src/portal/Portal.css | 10 --- .../packages/vuu-popups/src/portal/Portal.tsx | 4 +- .../src/table-next/column-menu/ColumnMenu.css | 8 -- .../src/table-next/column-menu/ColumnMenu.tsx | 1 - vuu-ui/packages/vuu-theme/css/global.css | 12 --- .../packages/vuu-theme/fonts/NunitoSans.css | 58 +++++++++++++ .../vuu-theme/fonts/NunitoSansv15.woff2 | Bin 0 -> 22144 bytes vuu-ui/packages/vuu-theme/index.css | 1 + .../common-hooks/useKeyboardNavigation.ts | 2 + .../vuu-ui-controls/src/list/useList.ts | 10 +-- .../app-vuu-basket-trader/login.css | 1 - .../app-vuu-basket-trader/public/demo.html | 3 - .../app-vuu-basket-trader/public/index.html | 3 - .../app-vuu-basket-trader/public/login.html | 3 - vuu-ui/sample-apps/app-vuu-example/login.css | 1 - .../Filters/FilterBar/FilterBar.examples.tsx | 1 + vuu-ui/showcase/templates/index-preview.html | 3 - vuu-ui/showcase/templates/index.html | 3 - 26 files changed, 125 insertions(+), 186 deletions(-) create mode 100644 vuu-ui/packages/vuu-theme/fonts/NunitoSans.css create mode 100644 vuu-ui/packages/vuu-theme/fonts/NunitoSansv15.woff2 diff --git a/vuu-ui/packages/vuu-filters/src/filter-builder-menu/FilterBuilderMenu.tsx b/vuu-ui/packages/vuu-filters/src/filter-builder-menu/FilterBuilderMenu.tsx index 338ef654a..5311cc38b 100644 --- a/vuu-ui/packages/vuu-filters/src/filter-builder-menu/FilterBuilderMenu.tsx +++ b/vuu-ui/packages/vuu-filters/src/filter-builder-menu/FilterBuilderMenu.tsx @@ -1,6 +1,6 @@ import { ContextMenuProps } from "@finos/vuu-popups"; import { MenuActionHandler } from "packages/vuu-data-types"; -import { ReactElement, useCallback, useEffect, useRef } from "react"; +import { ReactElement, useCallback, useRef } from "react"; import { PopupComponent as Popup, Portal } from "@finos/vuu-popups"; import { List, ListItem } from "@finos/vuu-ui-controls"; diff --git a/vuu-ui/packages/vuu-filters/src/filter-clause/useFilterClauseEditor.ts b/vuu-ui/packages/vuu-filters/src/filter-clause/useFilterClauseEditor.ts index 1579b8a80..558d98afe 100644 --- a/vuu-ui/packages/vuu-filters/src/filter-clause/useFilterClauseEditor.ts +++ b/vuu-ui/packages/vuu-filters/src/filter-clause/useFilterClauseEditor.ts @@ -35,15 +35,12 @@ const getFocusedField = () => const focusNextFocusableElement = (direction: "fwd" | "bwd" = "fwd") => { const activeField = getFocusedField(); - console.log(`activeField = ${activeField?.className}`); const filterClause = activeField?.closest(".vuuFilterClause"); if (filterClause?.lastChild === activeField) { requestAnimationFrame(() => { - console.log("enmd o the line, baby, wait, then try again"); focusNextFocusableElement(); }); } else { - console.log("go ahead and focus next field"); const nextField = direction === "fwd" ? (activeField.nextElementSibling as HTMLElement) @@ -107,7 +104,6 @@ const navigateToNextInputIfAtBoundary = ( const nextField = field.nextSibling as HTMLElement; const nextInput = nextField?.querySelector("input"); evt.preventDefault(); - console.log("%cfocus nextInput", "color:green;font-weight:bold"); nextInput?.focus(); requestAnimationFrame(() => { nextInput?.select(); @@ -163,7 +159,6 @@ export const useFilterClauseEditor = ({ ); const setOperator = useCallback((op) => { - console.log(`setOperator ${op}`); _setOperator(op); }, []); @@ -173,13 +168,15 @@ export const useFilterClauseEditor = ({ const handleSelectionChangeColumn = useCallback< SingleSelectionHandler - >((evt, column) => { - console.log(`handleSelectionChangeColumn ${column.name}`); - setSelectedColumn(column ?? undefined); - setOperator(undefined); - setValue(undefined); - focusNextElement(); - }, []); + >( + (evt, column) => { + setSelectedColumn(column ?? undefined); + setOperator(undefined); + setValue(undefined); + focusNextElement(); + }, + [setOperator] + ); const handleSelectionChangeOperator = useCallback( (evt, selected) => { @@ -193,7 +190,7 @@ export const useFilterClauseEditor = ({ ); } }, - [] + [setOperator] ); const handleChangeValue = useCallback( diff --git a/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx b/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx index 1b5adc9f4..7f91f5c52 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx +++ b/vuu-ui/packages/vuu-popups/src/menu/ContextMenu.tsx @@ -50,14 +50,19 @@ export const ContextMenu = ({ [actions, id, onClose] ); - const { closeMenu, listItemProps, onOpenMenu, openMenus, handleRender } = - useCascade({ - // FIXME - id: `${id}`, - onActivate: handleActivate, - onMouseEnterItem: handleMouseEnterItem, - position, - }); + const { + closeMenu, + listItemProps, + openMenu: onOpenMenu, + openMenus, + handleRender, + } = useCascade({ + // FIXME + id: `${id}`, + onActivate: handleActivate, + onMouseEnterItem: handleMouseEnterItem, + position, + }); closeMenuRef.current = closeMenu; const handleCloseMenu = () => { @@ -104,7 +109,7 @@ export const ContextMenu = ({ onActivate={handleActivate} onHighlightMenuItem={handleHighlightMenuItem} onCloseMenu={handleCloseMenu} - onOpenMenu={onOpenMenu} + openMenu={onOpenMenu} style={style} tabIndex={i === all.length - 1 ? 0 : undefined} > diff --git a/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx b/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx index 8bc759ddf..08eb66c41 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx +++ b/vuu-ui/packages/vuu-popups/src/menu/MenuList.tsx @@ -41,14 +41,7 @@ export const MenuItem = ({ options, ...props }: MenuItemProps) => { - const handleClick = () => { - console.log("click within menuItem"); - }; - return ( -
- {children} -
- ); + return
{children}
; }; export interface MenuItemLabelProps { @@ -88,7 +81,7 @@ export interface MenuListProps extends HTMLAttributes { listItemProps?: Partial; onActivate?: (menuId: string) => void; onCloseMenu: (idx: number) => void; - onOpenMenu?: MenuOpenHandler; + openMenu?: MenuOpenHandler; onHighlightMenuItem?: (idx: number) => void; } @@ -105,7 +98,7 @@ export const MenuList = ({ onHighlightMenuItem, onActivate, onCloseMenu, - onOpenMenu, + openMenu: onOpenMenu, ...props }: MenuListProps) => { const id = useId(idProp); diff --git a/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts b/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts index b92c37280..444b6955b 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts +++ b/vuu-ui/packages/vuu-popups/src/menu/use-cascade.ts @@ -106,7 +106,7 @@ export interface CascadeHooksResult { closeMenu: () => void; handleRender: () => void; listItemProps: Partial; - onOpenMenu: MenuOpenHandler; + openMenu: MenuOpenHandler; openMenus: RuntimeMenuDescriptor[]; } @@ -275,8 +275,9 @@ export const useCascade = ({ const handleRender = useCallback(() => { const { current: menus } = openMenus; - const [menu] = menus.slice(-1); - const el = document.getElementById(menu.id); + const menu = menus.at(-1); + const el = menu ? document.getElementById(menu.id) : undefined; + console.log({ menu, el }); if (el) { const { right, bottom } = el.getBoundingClientRect(); const { clientHeight, clientWidth } = document.body; @@ -437,7 +438,7 @@ export const useCascade = ({ closeMenu, handleRender, listItemProps, - onOpenMenu: triggerChildMenu, + openMenu: triggerChildMenu, openMenus: openMenus.current, }; }; diff --git a/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx b/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx index a92f3c4c8..116a045e7 100644 --- a/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx +++ b/vuu-ui/packages/vuu-popups/src/menu/useContextMenu.tsx @@ -108,11 +108,6 @@ export const useContextMenu = ( if (menuItemDescriptors.length && menuHandler) { showContextMenu(e, menuItemDescriptors, menuHandler, { ...ContextMenuProps, - className: cx( - ContextMenuProps?.className, - themeClass, - densityClass - ), "data-mode": dataMode, }); } @@ -122,15 +117,7 @@ export const useContextMenu = ( ); } }, - [ - buildMenuOptions, - ctx, - dataMode, - densityClass, - menuActionHandler, - menuBuilder, - themeClass, - ] + [buildMenuOptions, ctx, dataMode, menuActionHandler, menuBuilder] ); const hideContextMenu = useCallback(() => { @@ -163,6 +150,7 @@ const showContextMenu = ( ...contextMenuProps }: ContextMenuOptions["ContextMenuProps"] = NO_OPTIONS ) => { + console.log(contextMenuProps); const menuItems = (menuDescriptors: ContextMenuItemDescriptor[]) => { const fromDescriptor = (menuItem: ContextMenuItemDescriptor, i: number) => isGroupMenuItemDescriptor(menuItem) ? ( diff --git a/vuu-ui/packages/vuu-popups/src/popup-menu/PopupMenu.tsx b/vuu-ui/packages/vuu-popups/src/popup-menu/PopupMenu.tsx index a36efbddd..17510290c 100644 --- a/vuu-ui/packages/vuu-popups/src/popup-menu/PopupMenu.tsx +++ b/vuu-ui/packages/vuu-popups/src/popup-menu/PopupMenu.tsx @@ -6,6 +6,7 @@ import { useState, } from "react"; import { + MenuOpenHandler, PopupCloseReason, reasonIsClickAway, useContextMenu, @@ -13,9 +14,9 @@ import { import cx from "classnames"; import { Button } from "@salt-ds/core"; import { useId } from "@finos/vuu-layout"; +import { MenuActionHandler, MenuBuilder } from "@finos/vuu-data-types"; import "./PopupMenu.css"; -import { MenuActionHandler, MenuBuilder } from "@finos/vuu-data-types"; const classBase = "vuuPopupMenu"; @@ -55,6 +56,12 @@ export const PopupMenu = ({ const id = useId(idProp); const [showContextMenu] = useContextMenu(menuBuilder, menuActionHandler); + const handleOpenMenu = useCallback((el) => { + console.log(`menu Open `, { + el, + }); + }, []); + const handleMenuClose = useCallback( (reason?: PopupCloseReason) => { setMenuOpen(false); @@ -86,16 +93,23 @@ export const PopupMenu = ({ setMenuOpen(true); showContextMenu(e, menuLocation, { ContextMenuProps: { - className: "vuuPopupMenuList", id: `${id}-menu`, onClose: handleMenuClose, + openMenu: handleOpenMenu, position: getPosition(rootRef.current), }, ...menuOptions, }); } }, - [handleMenuClose, id, menuLocation, menuOptions, showContextMenu] + [ + handleMenuClose, + handleOpenMenu, + id, + menuLocation, + menuOptions, + showContextMenu, + ] ); return ( diff --git a/vuu-ui/packages/vuu-popups/src/popup/popup-service.ts b/vuu-ui/packages/vuu-popups/src/popup/popup-service.ts index 2a556ad2b..34ede5088 100644 --- a/vuu-ui/packages/vuu-popups/src/popup/popup-service.ts +++ b/vuu-ui/packages/vuu-popups/src/popup/popup-service.ts @@ -4,8 +4,6 @@ import React, { CSSProperties, HTMLAttributes, ReactElement, - useEffect, - useRef, } from "react"; import ReactDOM from "react-dom"; import { ContextMenuOptions } from "../menu"; @@ -62,7 +60,9 @@ function specialKeyHandler(e: KeyboardEvent) { function outsideClickHandler(e: MouseEvent) { if (_popups.length) { - const popupContainers = document.body.querySelectorAll(".vuuPopup"); + const popupContainers = document.body.querySelectorAll( + ".vuuPopup,#vuu-portal-root" + ); for (let i = 0; i < popupContainers.length; i++) { if (popupContainers[i].contains(e.target as HTMLElement)) { return; @@ -292,78 +292,3 @@ export class DialogService { } } } - -export interface PopupProps { - children: ReactElement; - close?: boolean; - depth: number; - group?: string; - name: string; - position?: "above" | "below" | ""; - width: number; -} - -export const Popup = (props: PopupProps) => { - const pendingTask = useRef(); - const ref = useRef(null); - - const show = (props: PopupProps, boundingClientRect: DOMRect) => { - const { name, group, depth, width } = props; - let left: number | undefined; - let top: number | undefined; - - if (pendingTask.current) { - window.clearTimeout(pendingTask.current); - pendingTask.current = undefined; - } - - if (props.close === true) { - PopupService.hidePopup(undefined, name, group); - } else { - const { position, children: component } = props; - const { - left: targetLeft, - top: targetTop, - width: clientWidth, - bottom: targetBottom, - } = boundingClientRect; - - if (position === "below") { - left = targetLeft; - top = targetBottom; - } else if (position === "above") { - left = targetLeft; - top = targetTop; - } - - pendingTask.current = window.setTimeout(() => { - PopupService.showPopup({ - name, - group, - depth, - position, - left, - top, - width: width || clientWidth, - component, - }); - }, 10); - } - }; - - useEffect(() => { - if (ref.current) { - const el = ref.current.parentElement; - const boundingClientRect = el?.getBoundingClientRect(); - if (boundingClientRect) { - show(props, boundingClientRect); - } - } - - return () => { - PopupService.hidePopup(undefined, props.name, props.group); - }; - }, [props]); - - return React.createElement("div", { className: "popup-proxy", ref }); -}; diff --git a/vuu-ui/packages/vuu-popups/src/portal/Portal.css b/vuu-ui/packages/vuu-popups/src/portal/Portal.css index d14e21ac1..787edc4ae 100644 --- a/vuu-ui/packages/vuu-popups/src/portal/Portal.css +++ b/vuu-ui/packages/vuu-popups/src/portal/Portal.css @@ -10,16 +10,6 @@ z-index: var(--salt-zIndex-modal); } -.vuuPopupMenuList { - border-radius:4px; - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - position: absolute; - border-color: var(--vuuMenuList-borderColor, var(--salt-container-primary-borderColor)); - border-style: solid !important; - border-width: 1px; - padding: 4px 0; -} - .vuuPortal:has(.vuuDropdown-popup-component.vuuList-empty){ display: none; diff --git a/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx b/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx index ce1d218e3..bae306d3b 100644 --- a/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx +++ b/vuu-ui/packages/vuu-popups/src/portal/Portal.tsx @@ -73,7 +73,9 @@ export const Portal = ({ }, [id, container, themeClass, densityClass, dataMode]); useLayoutEffect(() => { - onRender?.(); + requestAnimationFrame(() => { + onRender?.(); + }); }, [onRender]); if (open && mounted && portalRef.current && children) { diff --git a/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.css b/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.css index adcd4565a..a42cef5d2 100644 --- a/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.css +++ b/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.css @@ -35,12 +35,4 @@ --vuu-icon-size: 14px; } - .vuuColumnMenuList { - --vuuMenuList-borderStyle: solid; - border-radius:4px; - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - border-color: var(--vuuMenuList-borderColor, var(--salt-container-primary-borderColor)); - border-style: solid; - border-width: 1px; - } \ No newline at end of file diff --git a/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.tsx b/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.tsx index c6058e2a8..dfc0e6cf6 100644 --- a/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.tsx +++ b/vuu-ui/packages/vuu-table/src/table-next/column-menu/ColumnMenu.tsx @@ -41,7 +41,6 @@ export const ColumnMenu = ({ showContextMenu(e, "column-menu", { column, ContextMenuProps: { - className: "vuuColumnMenuList", onClose: handleMenuClose, position: getPosition(rootRef.current), }, diff --git a/vuu-ui/packages/vuu-theme/css/global.css b/vuu-ui/packages/vuu-theme/css/global.css index 887585287..7c90dfa99 100644 --- a/vuu-ui/packages/vuu-theme/css/global.css +++ b/vuu-ui/packages/vuu-theme/css/global.css @@ -1,15 +1,3 @@ -/** - * Have some global styles to simulate more realistic app. Currently only for Storybook. - */ - - @font-face { - font-family: 'Nunito Sans Regular'; - src: - url('../NunitoSans-Regular.woff') - format('opentype'); - font-weight: normal; - font-style: normal; -} .vuu-theme { color: var(--salt-text-primary-foreground); diff --git a/vuu-ui/packages/vuu-theme/fonts/NunitoSans.css b/vuu-ui/packages/vuu-theme/fonts/NunitoSans.css new file mode 100644 index 000000000..0cd0a402d --- /dev/null +++ b/vuu-ui/packages/vuu-theme/fonts/NunitoSans.css @@ -0,0 +1,58 @@ +/* latin */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(./NunitoSansv15.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} /* latin */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(./NunitoSansv15.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} /* latin */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 500; + font-stretch: 100%; + font-display: swap; + src: url(./NunitoSansv15.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + /* latin */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(./NunitoSansv15.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + /* latin */ + @font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 700; + font-stretch: 100%; + font-display: swap; + src: url(./NunitoSansv15.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } + /* latin */ + @font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 800; + font-stretch: 100%; + font-display: swap; + src: url(./NunitoSansv15.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + } \ No newline at end of file diff --git a/vuu-ui/packages/vuu-theme/fonts/NunitoSansv15.woff2 b/vuu-ui/packages/vuu-theme/fonts/NunitoSansv15.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..0b28b91a80ef14e8fbb53bf880d3df46ca8cade4 GIT binary patch literal 22144 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 Hdujjxalz~D literal 0 HcmV?d00001 diff --git a/vuu-ui/packages/vuu-theme/index.css b/vuu-ui/packages/vuu-theme/index.css index b31327838..9222247cd 100644 --- a/vuu-ui/packages/vuu-theme/index.css +++ b/vuu-ui/packages/vuu-theme/index.css @@ -1,3 +1,4 @@ +@import url(fonts/NunitoSans.css); @import url(css/global.css); @import url(css/theme.css); @import url(css/components/components.css); diff --git a/vuu-ui/packages/vuu-ui-controls/src/list/common-hooks/useKeyboardNavigation.ts b/vuu-ui/packages/vuu-ui-controls/src/list/common-hooks/useKeyboardNavigation.ts index ca28c8a5e..083c09681 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/list/common-hooks/useKeyboardNavigation.ts +++ b/vuu-ui/packages/vuu-ui-controls/src/list/common-hooks/useKeyboardNavigation.ts @@ -241,6 +241,7 @@ export const useKeyboardNavigation = ({ const setIgnoreFocus = (value: boolean) => (ignoreFocus.current = value); const handleFocus = useCallback(() => { + console.trace(`List useKeyboard focus`); // Ignore focus if mouse has been used if (ignoreFocus.current) { ignoreFocus.current = false; @@ -318,6 +319,7 @@ export const useKeyboardNavigation = ({ const handleKeyDown = useCallback( (e: KeyboardEvent) => { + console.log("handleKeyDown"); if (itemCount > 0 && isNavigationKey(e)) { e.preventDefault(); e.stopPropagation(); diff --git a/vuu-ui/packages/vuu-ui-controls/src/list/useList.ts b/vuu-ui/packages/vuu-ui-controls/src/list/useList.ts index d159a8f55..e4f7742fe 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/list/useList.ts +++ b/vuu-ui/packages/vuu-ui-controls/src/list/useList.ts @@ -71,11 +71,11 @@ export const useList = ({ onKeyboardNavigation?.(evt, nextIndex); }; - console.log( - `useList - defaultSelected ${JSON.stringify(defaultSelected)} - selectedProp ${JSON.stringify(selected)} ` - ); + // console.log( + // `useList + // defaultSelected ${JSON.stringify(defaultSelected)} + // selectedProp ${JSON.stringify(selected)} ` + // ); // TODO where do these belong ? const handleSelect = useCallback( diff --git a/vuu-ui/sample-apps/app-vuu-basket-trader/login.css b/vuu-ui/sample-apps/app-vuu-basket-trader/login.css index 244112209..56812898f 100644 --- a/vuu-ui/sample-apps/app-vuu-basket-trader/login.css +++ b/vuu-ui/sample-apps/app-vuu-basket-trader/login.css @@ -4,7 +4,6 @@ body { flex-direction: column; justify-content: center; margin: 0; - font-family: "Open Sans"; } #root { diff --git a/vuu-ui/sample-apps/app-vuu-basket-trader/public/demo.html b/vuu-ui/sample-apps/app-vuu-basket-trader/public/demo.html index 9dee98ee6..5e53d03ff 100644 --- a/vuu-ui/sample-apps/app-vuu-basket-trader/public/demo.html +++ b/vuu-ui/sample-apps/app-vuu-basket-trader/public/demo.html @@ -9,9 +9,6 @@ - VUU App diff --git a/vuu-ui/sample-apps/app-vuu-basket-trader/public/index.html b/vuu-ui/sample-apps/app-vuu-basket-trader/public/index.html index ecdc5622e..155bead1d 100644 --- a/vuu-ui/sample-apps/app-vuu-basket-trader/public/index.html +++ b/vuu-ui/sample-apps/app-vuu-basket-trader/public/index.html @@ -10,9 +10,6 @@ - VUU App diff --git a/vuu-ui/sample-apps/app-vuu-basket-trader/public/login.html b/vuu-ui/sample-apps/app-vuu-basket-trader/public/login.html index 4ed8d3081..d9fcdf50c 100644 --- a/vuu-ui/sample-apps/app-vuu-basket-trader/public/login.html +++ b/vuu-ui/sample-apps/app-vuu-basket-trader/public/login.html @@ -7,9 +7,6 @@ -