From 9e0f2fc211f4e12cfe6c6cdb3b113de5af2dbfe6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 25 Mar 2025 22:58:35 -0400
Subject: fixed sizing and background of collection filter button.  fixed
 exception in getDescription for text labels.

---
 .../views/collections/FlashcardPracticeUI.tsx      | 46 ++++++++++++++--------
 1 file changed, 29 insertions(+), 17 deletions(-)

(limited to 'src/client/views/collections/FlashcardPracticeUI.tsx')

diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index c071c5fb8..3bcdd843e 100644
--- a/src/client/views/collections/FlashcardPracticeUI.tsx
+++ b/src/client/views/collections/FlashcardPracticeUI.tsx
@@ -7,13 +7,15 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnFalse, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import './FlashcardPracticeUI.scss';
+import { StyleProp } from '../StyleProp';
+import { FieldViewProps } from '../nodes/FieldView';
 
 export enum practiceMode {
     PRACTICE = 'practice',
@@ -131,6 +133,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
             <div
                 className="FlashcardPracticeUI-practiceModes"
                 style={{
+                    background: SnappingManager.userVariantColor,
                     transform: this._props.ScreenToLocalBoxXf().Scale >= 1 ? undefined : `translateY(${this.btnHeight() / this._props.ScreenToLocalBoxXf().Scale - this.btnHeight()}px)`,
                 }}>
                 <MultiToggle
@@ -179,6 +182,12 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
             </div>
         );
     }
+    childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
+        if (doc instanceof Doc && property === StyleProp.BackgroundColor) {
+            return SnappingManager.userVariantColor;
+        }
+        return this._props.docViewProps().styleProvider?.(doc, props, property);
+    };
     tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct
     render() {
         return (
@@ -188,22 +197,25 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
                 {this._props.layoutDoc._chromeHidden ? null : (
                     <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnHeight(), transform: `scale(${this._props.uiBtnScaling})` }}>
                         {!this.filterDoc ? null : (
-                            <DocumentView
-                                {...this._props.docViewProps()}
-                                Document={this.filterDoc}
-                                TemplateDataDocument={undefined}
-                                PanelWidth={this.btnWidth}
-                                PanelHeight={this.btnHeight}
-                                NativeWidth={returnZero}
-                                NativeHeight={returnZero}
-                                hideDecorations={BoolCast(this._props.layoutDoc.layout_hideDecorations)}
-                                hideCaptions={true}
-                                hideFilterStatus={true}
-                                renderDepth={this._props.renderDepth + 1}
-                                fitWidth={undefined}
-                                showTags={false}
-                                setContentViewBox={undefined}
-                            />
+                            <div style={{ background: SnappingManager.userVariantColor }}>
+                                <DocumentView
+                                    {...this._props.docViewProps()}
+                                    Document={this.filterDoc}
+                                    TemplateDataDocument={undefined}
+                                    PanelWidth={this.btnWidth}
+                                    PanelHeight={this.btnHeight}
+                                    NativeWidth={returnZero}
+                                    NativeHeight={returnZero}
+                                    hideDecorations={BoolCast(this._props.layoutDoc.layout_hideDecorations)}
+                                    hideCaptions={true}
+                                    hideFilterStatus={true}
+                                    renderDepth={this._props.renderDepth + 1}
+                                    styleProvider={this.childStyleProvider}
+                                    fitWidth={undefined}
+                                    showTags={false}
+                                    setContentViewBox={undefined}
+                                />
+                            </div>
                         )}
                         {this.practiceModesMenu}
                     </div>
-- 
cgit v1.2.3-70-g09d2


From a3d6fae1482c61ca725d0a103f13b621aa32b3e3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 5 Apr 2025 14:29:03 -0400
Subject:  fixed multitoggle behavior to only toggle submenu closed.  switched
 to hard-light for masking ink.  fixed to make menu toggle take effect
 immediately.

---
 packages/components/src/components/Group/Group.tsx |  68 +++++------
 .../components/MultiToggle/MultiToggle.stories.tsx | 125 ++++++++++-----------
 .../src/components/MultiToggle/MultiToggle.tsx     |  36 +++---
 packages/components/src/components/Popup/Popup.tsx |  22 ++--
 src/client/documents/Documents.ts                  |  37 +++---
 src/client/views/collections/CollectionSubView.tsx |   7 +-
 .../views/collections/FlashcardPracticeUI.tsx      |   5 +-
 .../collectionFreeForm/CollectionFreeFormView.scss |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   5 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   1 -
 10 files changed, 139 insertions(+), 169 deletions(-)

(limited to 'src/client/views/collections/FlashcardPracticeUI.tsx')

diff --git a/packages/components/src/components/Group/Group.tsx b/packages/components/src/components/Group/Group.tsx
index 7abe4a1c7..6275a09dd 100644
--- a/packages/components/src/components/Group/Group.tsx
+++ b/packages/components/src/components/Group/Group.tsx
@@ -1,49 +1,33 @@
-import React from 'react'
-import './Group.scss'
-import { Colors, IGlobalProps, getFontSize, isDark , getFormLabelSize } from '../../global';
+import React from 'react';
+import './Group.scss';
+import { Colors, IGlobalProps, getFontSize, isDark, getFormLabelSize } from '../../global';
 
 export interface IGroupProps extends IGlobalProps {
-  children: any
-  rowGap?: number;
-  columnGap?: number;
-  padding?: number | string;
+    children: any;
+    rowGap?: number;
+    columnGap?: number;
+    padding?: number | string;
 }
 
 export const Group = (props: IGroupProps) => {
-  const { 
-    children, 
-    width = '100%',
-    rowGap = 5,
-    columnGap = 5,
-    padding = 0,
-    formLabel,
-    formLabelPlacement,
-    size,
-    style,
-    color,
-    fillWidth
-  } = props
+    const { children, width = '100%', rowGap = 5, columnGap = 5, padding = 0, formLabel, formLabelPlacement, size, style, fillWidth } = props;
 
-  const group: JSX.Element = 
-  (
-    <div
-      className="group-wrapper"
-      style={{ width, padding: padding, ...style }}
-    >
-      <div className={`group-container`}
-        style={{ rowGap, columnGap }}
-      >{children}</div>
-    </div>
-  )
+    const group: JSX.Element = (
+        <div className="group-wrapper" style={{ width, padding: padding, ...style }}>
+            <div className={`group-container`} style={{ rowGap, columnGap }}>
+                {children}
+            </div>
+        </div>
+    );
 
-  return (
-    formLabel ? 
-      <div className={`form-wrapper ${formLabelPlacement}`}
-          style={{ width: fillWidth ? '100%' : undefined}}>
-        <div className={'formLabel'} style={{fontSize: getFormLabelSize(size)}}>{formLabel}</div>
-        {group}
-      </div>
-    :
-      group
-  )
-}
+    return formLabel ? (
+        <div className={`form-wrapper ${formLabelPlacement}`} style={{ width: fillWidth ? '100%' : undefined }}>
+            <div className={'formLabel'} style={{ fontSize: getFormLabelSize(size) }}>
+                {formLabel}
+            </div>
+            {group}
+        </div>
+    ) : (
+        group
+    );
+};
diff --git a/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx b/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx
index e71423d7a..b9ba45f72 100644
--- a/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx
+++ b/packages/components/src/components/MultiToggle/MultiToggle.stories.tsx
@@ -1,69 +1,68 @@
-import { Meta, Story } from '@storybook/react'
-import React from 'react'
-import { IMultiToggleProps, MultiToggle } from './MultiToggle'
-import { FaAlignLeft, FaAlignCenter, FaAlignJustify, FaAlignRight } from 'react-icons/fa'
+import { Meta, Story } from '@storybook/react';
+import React from 'react';
+import { IMultiToggleProps, MultiToggle } from './MultiToggle';
+import { FaAlignLeft, FaAlignCenter, FaAlignJustify, FaAlignRight } from 'react-icons/fa';
 
 export default {
-  title: 'Dash/MultiToggle',
-  component: MultiToggle,
-  argTypes: {},
-} as Meta<typeof MultiToggle>
+    title: 'Dash/MultiToggle',
+    component: MultiToggle,
+    argTypes: {},
+} as Meta<typeof MultiToggle>;
 
-const MultiToggleStory: Story<IMultiToggleProps> = (args) => <MultiToggle {...args} />
-export const MultiToggleOne = MultiToggleStory.bind({})
+const MultiToggleStory: Story<IMultiToggleProps> = args => <MultiToggle {...args} />;
+export const MultiToggleOne = MultiToggleStory.bind({});
 MultiToggleOne.args = {
-  tooltip: "Text alignment",
-  label: "Alignment",
-  defaultSelectedItems: "center",
-  toggleStatus: true,
-  isToggle: false,
-  items: [
-    {
-      icon: <FaAlignLeft/>,
-      tooltip: 'Align left',
-      val: "left"
-    },
-    {
-      icon: <FaAlignCenter/>,
-      tooltip: 'Align center',
-      val: "center"
-    },
-    {
-      icon: <FaAlignRight/>,
-      tooltip: 'Align right',
-      val: "right"
-    },
-    {
-      icon: <FaAlignJustify/>,
-      tooltip: 'Justify',
-      val: "justify"
-    },
-  ]
-}
+    tooltip: 'Text alignment',
+    label: 'Alignment',
+    defaultSelectedItems: 'center',
+    toggleStatus: true,
+    items: [
+        {
+            icon: <FaAlignLeft />,
+            tooltip: 'Align left',
+            val: 'left',
+        },
+        {
+            icon: <FaAlignCenter />,
+            tooltip: 'Align center',
+            val: 'center',
+        },
+        {
+            icon: <FaAlignRight />,
+            tooltip: 'Align right',
+            val: 'right',
+        },
+        {
+            icon: <FaAlignJustify />,
+            tooltip: 'Justify',
+            val: 'justify',
+        },
+    ],
+};
 
-export const MultiToggleTwo = MultiToggleStory.bind({})
+export const MultiToggleTwo = MultiToggleStory.bind({});
 MultiToggleTwo.args = {
-  tooltip: "Text Tags",
-  label: "Tags",
-  defaultSelectedItems : ["left"],
-  background: "green",
-  color: 'white',
-  multiSelect: true,
-  items: [
-    {
-      icon: <FaAlignLeft/>,
-      tooltip: 'Like',
-      val: "left"
-    },
-    {
-      icon: <FaAlignCenter/>,
-      tooltip: 'Todo',
-      val: "center"
-    },
-    {
-      icon: <FaAlignRight/>,
-      tooltip: 'Idea',
-      val: "right"
-    },
-  ]
-}
+    tooltip: 'Text Tags',
+    label: 'Tags',
+    defaultSelectedItems: ['left'],
+    background: 'green',
+    color: 'white',
+    multiSelect: true,
+    items: [
+        {
+            icon: <FaAlignLeft />,
+            tooltip: 'Like',
+            val: 'left',
+        },
+        {
+            icon: <FaAlignCenter />,
+            tooltip: 'Todo',
+            val: 'center',
+        },
+        {
+            icon: <FaAlignRight />,
+            tooltip: 'Idea',
+            val: 'right',
+        },
+    ],
+};
diff --git a/packages/components/src/components/MultiToggle/MultiToggle.tsx b/packages/components/src/components/MultiToggle/MultiToggle.tsx
index 0f659c5ca..0a6fb81c9 100644
--- a/packages/components/src/components/MultiToggle/MultiToggle.tsx
+++ b/packages/components/src/components/MultiToggle/MultiToggle.tsx
@@ -16,7 +16,6 @@ export interface IMultiToggleProps extends IGlobalProps {
     defaultSelectedItems?: (string | number) | (string | number)[];
     selectedItems?: (string | number) | (string | number)[];
     onSelectionChange?: (val: (string | number) | (string | number)[], added: boolean) => unknown;
-    isToggle?: boolean;
     toggleStatus?: boolean;
 }
 
@@ -37,33 +36,30 @@ export const MultiToggle = (props: IMultiToggleProps) => {
     const itemsMap = new Map();
     items.forEach(item => itemsMap.set(item.val, item));
     return (
-        <div className={`multiToggle-container`}>
+        <div className="multiToggle-container">
             <Popup
                 toggle={
-                    props.isToggle ? undefined : (
-                        <div style={{ position: 'relative' }}>
-                            <IconButton
-                                color={color}
-                                borderColor={background ? color : undefined}
-                                label={props.label}
-                                active={props.toggleStatus}
-                                background={color}
-                                {...(itemsMap.get(promoteToArray(selectedItems)[0]) ?? {})}
-                                tooltip={tooltip}
-                                tooltipPlacement={tooltipPlacement}
-                            />
-                            {promoteToArray(selectedItems).length < 2 ? null : <div style={{ position: 'absolute', top: '0', left: '0', color: color ?? Colors.MEDIUM_BLUE }}>+</div>}
-                        </div>
-                    )
+                    <div style={{ position: 'relative' }}>
+                        <IconButton
+                            color={color}
+                            borderColor={background ? color : undefined}
+                            label={props.label}
+                            active={props.toggleStatus}
+                            background={color}
+                            {...(itemsMap.get(promoteToArray(selectedItems)[0]) ?? {})}
+                            tooltip={tooltip}
+                            tooltipPlacement={tooltipPlacement}
+                        />
+                        {promoteToArray(selectedItems).length < 2 ? null : <div style={{ position: 'absolute', top: '0', left: '0', color: color ?? Colors.MEDIUM_BLUE }}>+</div>}
+                    </div>
                 }
-                isToggle={props.isToggle}
+                isToggle={true}
                 toggleFunc={() => {
                     const selItem = items.find(item => promoteToArray(selectedItems).includes(item.val));
                     selItem && setSelectedItemsLocal([selItem.val]);
                 }}
                 type={props.type}
-                label={props.isToggle ? props.label : undefined}
-                toggleStatus={props.isToggle ? props.toggleStatus : undefined}
+                label={undefined}
                 color={color}
                 background={background}
                 popup={
diff --git a/packages/components/src/components/Popup/Popup.tsx b/packages/components/src/components/Popup/Popup.tsx
index 82e60f343..9e72ece87 100644
--- a/packages/components/src/components/Popup/Popup.tsx
+++ b/packages/components/src/components/Popup/Popup.tsx
@@ -20,11 +20,10 @@ export interface IPopupProps extends IGlobalProps {
     toggle?: JSX.Element;
     popup: JSX.Element | string | (() => JSX.Element);
     trigger?: PopupTrigger;
-    toggleStatus?: boolean;
     isOpen?: boolean;
     setOpen?: (b: boolean) => void;
     background?: string;
-    isToggle?: boolean;
+    isToggle?: boolean; // whether popup stays open when background is clicked.  muyst click toggle button tp close it.
     toggleFunc?: () => void;
     popupContainsPt?: (x: number, y: number) => boolean;
 }
@@ -71,6 +70,7 @@ export const Popup = (props: IPopupProps) => {
         const rect = popperRef.current?.getBoundingClientRect();
         const rect2 = toggleRef.current?.getBoundingClientRect();
         if (
+            !props.isToggle &&
             (!rect2 || !(rect2.left < e.clientX && rect2.top < e.clientY && rect2.right > e.clientX && rect2.bottom > e.clientY)) &&
             rect &&
             !(rect.left < e.clientX && rect.top < e.clientY && rect.right > e.clientX && rect.bottom > e.clientY) &&
@@ -109,8 +109,8 @@ export const Popup = (props: IPopupProps) => {
                         timeout = setTimeout(() => setOpen(false), 1000);
                     }
                 }}>
-                {toggle ?? (
-                    <div ref={toggleRef}>
+                <div ref={toggleRef}>
+                    {toggle ?? (
                         <Toggle
                             tooltip={tooltip}
                             size={size}
@@ -122,19 +122,17 @@ export const Popup = (props: IPopupProps) => {
                             iconPlacement={iconPlacement}
                             text={text}
                             label={props.label}
-                            toggleStatus={isOpen || props.toggleStatus}
+                            toggleStatus={isOpen}
                             onClick={() => {
                                 if (trigger === PopupTrigger.CLICK) {
-                                    if (!props.isToggle || props.toggleStatus) {
-                                        setOpen(!isOpen);
-                                    }
+                                    setOpen(!isOpen);
                                     props.toggleFunc?.();
                                 }
                             }}
                             fillWidth={fillWidth}
                         />
-                    </div>
-                )}
+                    )}
+                </div>
             </div>
             <Popper open={isOpen} style={{ zIndex: 20000 }} anchorEl={triggerRef.current} placement={placement} modifiers={[]}>
                 <div
@@ -142,9 +140,7 @@ export const Popup = (props: IPopupProps) => {
                     ref={popperRef}
                     style={{ width, height, background }}
                     tabIndex={-1}
-                    onPointerDown={e => {
-                        e.stopPropagation();
-                    }}
+                    onPointerDown={e => e.stopPropagation()}
                     onPointerEnter={() => {
                         if (trigger === PopupTrigger.HOVER || trigger === PopupTrigger.HOVER_DELAY) {
                             clearTimeout(timeout);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8458699fa..f1655e3cf 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -4,7 +4,6 @@ import { basename } from 'path';
 import { ClientUtils, OmitKeys } from '../../ClientUtils';
 import { DateField } from '../../fields/DateField';
 import { CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc';
-import { Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
 import { InkField } from '../../fields/InkField';
@@ -983,26 +982,22 @@ export namespace Docs {
 
         export function InkDocument(points: PointData[], options: DocumentOptions = {}, strokeWidth: number, color: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, isInkMask: boolean) {
             const ink = InstanceFromProto(Prototypes.get(DocumentType.INK), '', { title: 'ink', ...options });
-            const I = Doc.GetProto(ink);
-            // I.layout_hideOpenButton = true; // don't show open full screen button when selected
-            I.color = color;
-            I.fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined;
-            I.stroke = new InkField(points);
-            I.stroke_width = strokeWidth;
-            I.stroke_bezier = strokeBezier;
-            I.stroke_startMarker = arrowStart;
-            I.stroke_endMarker = arrowEnd;
-            I.stroke_dash = dash;
-            I.stroke_isInkMask = isInkMask;
-            I.text_align = 'center';
-            I.rotation = 0;
-            I.width_min = 1;
-            I.height_min = 1;
-            I.defaultDoubleClick = 'ignore';
-            I.author_date = new DateField();
-            I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
-            // I.acl_Override = SharingPermissions.Unset;
-            I[Initializing] = false;
+            ink.$color = color;
+            ink.$fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined;
+            ink.$stroke = new InkField(points);
+            ink.$stroke_width = strokeWidth;
+            ink.$stroke_bezier = strokeBezier;
+            ink.$stroke_startMarker = arrowStart;
+            ink.$stroke_endMarker = arrowEnd;
+            ink.$stroke_dash = dash;
+            ink.$stroke_isInkMask = isInkMask;
+            ink.$text_align = 'center';
+            ink.$rotation = 0;
+            ink.$width_min = 1;
+            ink.$height_min = 1;
+            ink.$defaultDoubleClick = 'ignore';
+            ink.$author_date = new DateField();
+            ink.$acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
 
             return ink;
         }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index cfd52f9ee..a88707c6f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -13,6 +13,7 @@ import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { Upload } from '../../../server/SharedMediaTypes';
 import { DocServer } from '../../DocServer';
 import { Networking } from '../../Network';
 import { DocUtils } from '../../documents/DocUtils';
@@ -24,11 +25,11 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { SnappingManager } from '../../util/SnappingManager';
 import { UndoManager } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
+import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { FlashcardPracticeUI } from './FlashcardPracticeUI';
 import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
-import { Upload } from '../../../server/SharedMediaTypes';
+import { FlashcardPracticeUI } from './FlashcardPracticeUI';
 
 export enum docSortings {
     Time = 'time',
diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index 3bcdd843e..8cd9c5452 100644
--- a/src/client/views/collections/FlashcardPracticeUI.tsx
+++ b/src/client/views/collections/FlashcardPracticeUI.tsx
@@ -12,10 +12,11 @@ import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
 import './FlashcardPracticeUI.scss';
 import { StyleProp } from '../StyleProp';
 import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
 
 export enum practiceMode {
     PRACTICE = 'practice',
@@ -142,7 +143,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
                     color={SnappingManager.userColor}
                     background={SnappingManager.userVariantColor}
                     multiSelect={false}
-                    isToggle={false}
                     toggleStatus={!!this.practiceMode}
                     label="Practice"
                     items={[
@@ -162,7 +162,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
                     color={SnappingManager.userColor}
                     background={SnappingManager.userVariantColor}
                     multiSelect={false}
-                    isToggle={false}
                     toggleStatus={!!this.practiceMode}
                     label={StrCast(this._props.layoutDoc.revealOp, flashcardRevealOp.FLIP)}
                     items={[
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index cce0ff684..6c47a71b0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -41,7 +41,7 @@
     transition: background-color 1s ease 0s;
 }
 .collectionfreeformview-mask {
-    mix-blend-mode: multiply;
+    mix-blend-mode: hard-light;
     background-color: rgba(0, 0, 0, 0.8);
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f01fb8fc7..1f789432d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1601,7 +1601,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childDoc = pair.layout;
         const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
         const contentFrameNumber = Cast(childDoc._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
-        const { z, zIndex } = childDoc;
+        // eslint-disable-next-line @typescript-eslint/no-unused-vars
+        const { z, zIndex, stroke_isInkMask } = childDoc;
         const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
         const { x, y, autoDim, _width, _height, opacity, _rotation } =
             layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value)
@@ -1717,7 +1718,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
                     bounds: entry[1].opacity === 0 ? { payload: undefined, type: '', ...entry[1], width: 0, height: 0 } : { payload: undefined, type: '', ...entry[1] },
-                    inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
+                    inkMask: BoolCast(entry[1].pair.layout?.$stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
                 })
             );
 
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 966d8d7a0..f83b5e351 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -301,7 +301,6 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                 background={background}
                 multiSelect={true}
                 onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
-                isToggle={false}
                 toggleStatus={toggleStatus}
                 label={selectedItems.length === 1 ? selectedItems[0] : this.label}
                 items={items.map(item => ({
-- 
cgit v1.2.3-70-g09d2


From 40b8e5c30e7251db6258cac5b4ed5fb4a6a2418d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 7 Apr 2025 15:20:36 -0400
Subject: added multiToggle option to allow popup to stay up until explicitly
 untoggled. fixed multitoggle to honor toggleStatus.  fixed popup to follow
 target.   fixed setting text highlight background fro popup menu. fixed
 flashcardui buttons to have background sized properly.

---
 package-lock.json                                  | 24 ++++++++++++
 package.json                                       |  1 +
 .../src/components/MultiToggle/MultiToggle.tsx     | 16 +++++++-
 packages/components/src/components/Popup/Popup.tsx | 43 +++++++++++++++-------
 src/client/util/CurrentUserUtils.ts                |  5 ++-
 .../views/collections/CollectionCardDeckView.tsx   |  3 +-
 .../views/collections/FlashcardPracticeUI.tsx      |  7 ++--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  3 +-
 10 files changed, 81 insertions(+), 24 deletions(-)

(limited to 'src/client/views/collections/FlashcardPracticeUI.tsx')

diff --git a/package-lock.json b/package-lock.json
index 3b11eda7c..9acca90ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -40,6 +40,7 @@
                 "@pinecone-database/pinecone": "^2.2.2",
                 "@react-google-maps/api": "^2.19.2",
                 "@react-spring/web": "^9.7.5",
+                "@thednp/position-observer": "^1.0.7",
                 "@turf/turf": "^7.1.0",
                 "@types/bezier-js": "^4.1.3",
                 "@types/brotli": "^1.3.4",
@@ -12081,6 +12082,29 @@
                 "@testing-library/dom": ">=7.21.4"
             }
         },
+        "node_modules/@thednp/position-observer": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/@thednp/position-observer/-/position-observer-1.0.7.tgz",
+            "integrity": "sha512-MkUAMMgqZPxy71hpcrKr9ZtedMk+oIFbFs5B8uKD857iuYKRJxgJtC1Itus14EEM4qMyeN0x47AUZJmZJQyXbQ==",
+            "license": "MIT",
+            "dependencies": {
+                "@thednp/shorty": "^2.0.10"
+            },
+            "engines": {
+                "node": ">=16",
+                "pnpm": ">=8.6.0"
+            }
+        },
+        "node_modules/@thednp/shorty": {
+            "version": "2.0.10",
+            "resolved": "https://registry.npmjs.org/@thednp/shorty/-/shorty-2.0.10.tgz",
+            "integrity": "sha512-H+hs1lw3Yc1NfwG0b7F7YmVjxQZ31NO2+6zx+I+9XabHxdwPKjvYJnkKKXr7bSItgm2AFrfOn5+3veB6W4iauw==",
+            "license": "MIT",
+            "engines": {
+                "node": ">=16",
+                "pnpm": ">=8.6.0"
+            }
+        },
         "node_modules/@tokenizer/token": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
diff --git a/package.json b/package.json
index fe76189bf..1038c470e 100644
--- a/package.json
+++ b/package.json
@@ -123,6 +123,7 @@
         "@pinecone-database/pinecone": "^2.2.2",
         "@react-google-maps/api": "^2.19.2",
         "@react-spring/web": "^9.7.5",
+        "@thednp/position-observer": "^1.0.7",
         "@turf/turf": "^7.1.0",
         "@types/bezier-js": "^4.1.3",
         "@types/brotli": "^1.3.4",
diff --git a/packages/components/src/components/MultiToggle/MultiToggle.tsx b/packages/components/src/components/MultiToggle/MultiToggle.tsx
index 0a6fb81c9..c1d610c34 100644
--- a/packages/components/src/components/MultiToggle/MultiToggle.tsx
+++ b/packages/components/src/components/MultiToggle/MultiToggle.tsx
@@ -17,6 +17,7 @@ export interface IMultiToggleProps extends IGlobalProps {
     selectedItems?: (string | number) | (string | number)[];
     onSelectionChange?: (val: (string | number) | (string | number)[], added: boolean) => unknown;
     toggleStatus?: boolean;
+    showUntilToggle?: boolean; // whether popup stays open when background is clicked.  muyst click toggle button tp close it.
 }
 
 function promoteToArrayOrUndefined(d: (string | number)[] | (string | number) | undefined) {
@@ -32,12 +33,23 @@ export const MultiToggle = (props: IMultiToggleProps) => {
     init = false;
 
     const [selectedItemsLocal, setSelectedItemsLocal] = useState(initVal as (string | number) | (string | number)[]);
-    const { items, selectedItems = selectedItemsLocal, tooltip, tooltipPlacement = 'top', onSelectionChange, color, background } = props;
+    const {
+        items, //
+        selectedItems = selectedItemsLocal,
+        tooltip,
+        toggleStatus,
+        tooltipPlacement = 'top',
+        onSelectionChange,
+        color,
+        background,
+    } = props;
     const itemsMap = new Map();
     items.forEach(item => itemsMap.set(item.val, item));
     return (
         <div className="multiToggle-container">
             <Popup
+                isOpen={toggleStatus}
+                multitoggle={true} // this is used to indicate that this is a multi toggle, so it can be styled differently in the popup
                 toggle={
                     <div style={{ position: 'relative' }}>
                         <IconButton
@@ -53,7 +65,7 @@ export const MultiToggle = (props: IMultiToggleProps) => {
                         {promoteToArray(selectedItems).length < 2 ? null : <div style={{ position: 'absolute', top: '0', left: '0', color: color ?? Colors.MEDIUM_BLUE }}>+</div>}
                     </div>
                 }
-                isToggle={true}
+                showUntilToggle={props.showUntilToggle ?? true}
                 toggleFunc={() => {
                     const selItem = items.find(item => promoteToArray(selectedItems).includes(item.val));
                     selItem && setSelectedItemsLocal([selItem.val]);
diff --git a/packages/components/src/components/Popup/Popup.tsx b/packages/components/src/components/Popup/Popup.tsx
index 9e72ece87..9e91a75cf 100644
--- a/packages/components/src/components/Popup/Popup.tsx
+++ b/packages/components/src/components/Popup/Popup.tsx
@@ -3,6 +3,7 @@ import { IGlobalProps, Placement, Size } from '../../global';
 import { Toggle, ToggleType } from '../Toggle';
 import './Popup.scss';
 import { Popper } from '@mui/material';
+import PositionObserver from '@thednp/position-observer';
 
 export enum PopupTrigger {
     CLICK = 'click',
@@ -23,9 +24,10 @@ export interface IPopupProps extends IGlobalProps {
     isOpen?: boolean;
     setOpen?: (b: boolean) => void;
     background?: string;
-    isToggle?: boolean; // whether popup stays open when background is clicked.  muyst click toggle button tp close it.
+    showUntilToggle?: boolean; // whether popup stays open when background is clicked.  muyst click toggle button tp close it.
     toggleFunc?: () => void;
     popupContainsPt?: (x: number, y: number) => boolean;
+    multitoggle?: boolean;
 }
 
 /**
@@ -57,20 +59,21 @@ export const Popup = (props: IPopupProps) => {
         fillWidth,
         iconPlacement = 'left',
         background,
+        multitoggle,
         popupContainsPt,
     } = props;
 
     const triggerRef = useRef(null);
     const popperRef = useRef<HTMLDivElement | null>(null);
-    const toggleRef = useRef<HTMLDivElement | null>(null);
+    const [toggleRef, setToggleRef] = useState<HTMLDivElement | null>(null);
 
     let timeout = setTimeout(() => {});
 
     const handlePointerAwayDown = (e: PointerEvent) => {
         const rect = popperRef.current?.getBoundingClientRect();
-        const rect2 = toggleRef.current?.getBoundingClientRect();
+        const rect2 = toggleRef?.getBoundingClientRect();
         if (
-            !props.isToggle &&
+            !props.showUntilToggle &&
             (!rect2 || !(rect2.left < e.clientX && rect2.top < e.clientY && rect2.right > e.clientX && rect2.bottom > e.clientY)) &&
             rect &&
             !(rect.left < e.clientX && rect.top < e.clientY && rect.right > e.clientX && rect.bottom > e.clientY) &&
@@ -82,22 +85,36 @@ export const Popup = (props: IPopupProps) => {
         }
     };
 
+    let observer: PositionObserver | undefined = undefined;
+    const [previousPosition, setPreviousPosition] = useState<DOMRect | undefined>(toggleRef?.getBoundingClientRect());
+
     useEffect(() => {
         if (isOpen) {
             window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true });
             window.addEventListener('pointerdown', handlePointerAwayDown, { capture: true });
-            return () => window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true });
-        }
-    }, [isOpen, popupContainsPt]);
-
+            if (toggleRef && multitoggle) {
+                (observer = new PositionObserver(entries => {
+                    entries.forEach(entry => {
+                        const currentPosition = entry.boundingClientRect;
+                        if (Math.floor(currentPosition.top) !== Math.floor(previousPosition?.top ?? 0) || Math.floor(currentPosition.left) !== Math.floor(previousPosition?.left ?? 0)) {
+                            // Perform actions when position changes
+                            setPreviousPosition(currentPosition); // Update previous position
+                        }
+                    });
+                })).observe(toggleRef);
+            }
+            return () => {
+                window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true });
+                observer?.disconnect();
+            };
+        } else observer?.disconnect();
+    }, [isOpen, toggleRef, popupContainsPt]);
     return (
         <div className={`popup-wrapper ${fillWidth && 'fillWidth'}`}>
             <div
-                className={`trigger-container ${fillWidth && 'fillWidth'}`}
                 ref={triggerRef}
-                onClick={() => {
-                    if (trigger === PopupTrigger.CLICK) setOpen(!isOpen);
-                }}
+                className={`trigger-container ${fillWidth && 'fillWidth'}`}
+                onClick={() => trigger === PopupTrigger.CLICK && setOpen(!isOpen)}
                 onPointerEnter={() => {
                     if (trigger === PopupTrigger.HOVER || trigger === PopupTrigger.HOVER_DELAY) {
                         clearTimeout(timeout);
@@ -109,7 +126,7 @@ export const Popup = (props: IPopupProps) => {
                         timeout = setTimeout(() => setOpen(false), 1000);
                     }
                 }}>
-                <div ref={toggleRef}>
+                <div className="special" ref={R => setToggleRef(R)}>
                     {toggle ?? (
                         <Toggle
                             tooltip={tooltip}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f1cc81c88..ca94791d0 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -57,6 +57,7 @@ export interface Button {
     expertMode?: boolean;// available only in expert mode
     btnList?: List<string>;
     ignoreClick?: boolean;
+    showUntilToggle?: boolean; // whether the popup should stay open when the background is clicked.
     buttonText?: string;
     backgroundColor?: string;
     waitForDoubleClickToClick?: boolean;
@@ -778,7 +779,7 @@ pie title Minerals in my tap water
             { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",      toolType: Gestures.Circle,                           scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
             { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",      toolType: Gestures.Rectangle,                        scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
             { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",       toolType: Gestures.Line,                             scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
-            { title: "Ink",    toolTip: "Ink",                              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Ink,                               scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, 
+            { title: "Ink",    toolTip: "Ink",                              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Ink,        showUntilToggle: true,  scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, 
                 subMenu: [
                     { title: "Pen",      toolTip: "Pen (Ctrl+P)",           btnType: ButtonType.ToggleButton, icon: "pen-nib",     toolType: InkInkTool.Pen,         ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
                     { title: "Highlight",toolTip: "Highlight (Ctrl+H)",     btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight,   ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
@@ -786,7 +787,7 @@ pie title Minerals in my tap water
                 ]},
             { title: "Width",      toolTip: "Stroke width",                 btnType: ButtonType.NumberSliderButton,                toolType: InkProperty.StrokeWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40},
             { title: "Color",      toolTip: "Stroke color",                 btnType: ButtonType.ColorButton,  icon: "pen",         toolType: InkProperty.StrokeColor,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}},
-            { title: "Eraser",     toolTip: "Eraser (Ctrl+E)",              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Eraser,                            scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
+            { title: "Eraser",     toolTip: "Eraser (Ctrl+E)",              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Eraser,     showUntilToggle: true,  scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
               subMenu: [
                     { title: "Stroke",   toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser",      toolType:InkEraserTool.Stroke,    ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
                     { title: "Segment",  toolTip: "Erase between intersections",btnType:ButtonType.ToggleButton,icon:"xmark",      toolType:InkEraserTool.Segment,   ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 756b37f99..50de7c601 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -23,7 +23,8 @@ import { undoable, UndoManager } from '../../util/UndoManager';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { StyleProp } from '../StyleProp';
 import { TagItem } from '../TagsView';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
+import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index 8cd9c5452..f24a8acb7 100644
--- a/src/client/views/collections/FlashcardPracticeUI.tsx
+++ b/src/client/views/collections/FlashcardPracticeUI.tsx
@@ -63,8 +63,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
     @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore
     @computed get practiceMode() {  return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore
 
-    btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale);
-    btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height));
+    btnHeight = () => NumCast(this.filterDoc?.height);
+    btnWidth = () => (!this.filterDoc ? 1 : NumCast(this.filterDoc._width));
 
     /**
      * Sets the practice mode answer style for flashcards
@@ -135,7 +135,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
                 className="FlashcardPracticeUI-practiceModes"
                 style={{
                     background: SnappingManager.userVariantColor,
-                    transform: this._props.ScreenToLocalBoxXf().Scale >= 1 ? undefined : `translateY(${this.btnHeight() / this._props.ScreenToLocalBoxXf().Scale - this.btnHeight()}px)`,
                 }}>
                 <MultiToggle
                     tooltip="Practice flashcards one at a time"
@@ -194,7 +193,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
                 {this.emptyMessage}
                 {this.practiceButtons}
                 {this._props.layoutDoc._chromeHidden ? null : (
-                    <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnHeight(), transform: `scale(${this._props.uiBtnScaling})` }}>
+                    <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnWidth(), transform: `scale(${this._props.uiBtnScaling})` }}>
                         {!this.filterDoc ? null : (
                             <div style={{ background: SnappingManager.userVariantColor }}>
                                 <DocumentView
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index f83b5e351..d14def9aa 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -302,6 +302,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                 multiSelect={true}
                 onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
                 toggleStatus={toggleStatus}
+                showUntilToggle={BoolCast(this.Document.showUntilToggle)} // allow the toggle to be clickable unless ignoreClick is set on the Document
                 label={selectedItems.length === 1 ? selectedItems[0] : this.label}
                 items={items.map(item => ({
                     icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d90be007f..341340363 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1761,7 +1761,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     tryKeepingFocus = (target: Element | null) => {
         for (let newFocusEle = target instanceof HTMLElement ? target : null; newFocusEle; newFocusEle = newFocusEle?.parentElement) {
             // test if parent of new focused element is a UI button (should be more specific than testing className)
-            if (newFocusEle?.className === 'fonticonbox' || newFocusEle?.className === 'popup-container') {
+            if (['fonticonbox', 'antimodeMenu-cont', 'popup-container'].includes(newFocusEle?.className ?? '')) {
                 return this.EditorView?.focus(); // keep focus on text box
             }
         }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 758b4035e..10c95f1e1 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -367,7 +367,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
         if (this.TextView && this.view && fontField !== 'fitBox') {
-            if (this.view.hasFocus()) {
+            const anchorNode = window.getSelection()?.anchorNode;
+            if (this.view.hasFocus() || (anchorNode && this.TextView.ProseRef?.contains(anchorNode))) {
                 const attrs: { [key: string]: string } = {};
                 attrs[fontField] = value;
                 const fmark = this.view.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
-- 
cgit v1.2.3-70-g09d2

