Compare commits
20 Commits
dani/widge
...
docs-dates
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b071b95a02 | ||
|
|
2383ce4d27 | ||
|
|
ba75ffcf4f | ||
|
|
b5c977d3aa | ||
|
|
4029441cea | ||
|
|
cd47c609a0 | ||
|
|
ae6acdcdd5 | ||
|
|
c196db2115 | ||
|
|
a3c201e4e4 | ||
|
|
126e62957a | ||
|
|
294a651ae5 | ||
|
|
6f8a1e0da6 | ||
|
|
205ba6ee2d | ||
|
|
547b331773 | ||
|
|
db552047c8 | ||
|
|
bcbacdb0a3 | ||
|
|
d0f37854d9 | ||
|
|
99b03f70c3 | ||
|
|
3e1605a494 | ||
|
|
d6aad08e73 |
@@ -1,4 +1,5 @@
|
||||
mod test_actions;
|
||||
mod test_arabic_roman;
|
||||
mod test_binary_search;
|
||||
mod test_cell;
|
||||
mod test_cell_clear_contents;
|
||||
@@ -39,6 +40,7 @@ mod test_metadata;
|
||||
mod test_model_cell_clear_all;
|
||||
mod test_model_is_empty_cell;
|
||||
mod test_move_formula;
|
||||
mod test_mround_trunc_int;
|
||||
mod test_quote_prefix;
|
||||
mod test_row_column_styles;
|
||||
mod test_set_user_input;
|
||||
|
||||
@@ -6,8 +6,8 @@ use crate::test::util::new_empty_model;
|
||||
fn arguments() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=ARABIC()");
|
||||
model._set("A2", "=ARABIC(V)");
|
||||
model._set("A3", "=ARABIC(V, 2)");
|
||||
model._set("A2", "=ARABIC(\"V\")");
|
||||
model._set("A3", "=ARABIC(\"V\", 2)");
|
||||
|
||||
model._set("A4", "=ROMAN()");
|
||||
model._set("A5", "=ROMAN(5)");
|
||||
@@ -20,7 +20,6 @@ fn arguments() {
|
||||
model._set("A11", "=INT(10.22, 1)");
|
||||
model._set("A12", "=INT(10.22, 1, 2)");
|
||||
|
||||
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"#ERROR!");
|
||||
@@ -29,7 +28,7 @@ fn arguments() {
|
||||
assert_eq!(model._get_text("A4"), *"#ERROR!");
|
||||
|
||||
assert_eq!(model._get_text("A5"), *"#ERROR!");
|
||||
assert_eq!(model._get_text("A6"), *"#ERROR!");
|
||||
assert_eq!(model._get_text("A6"), *"10");
|
||||
assert_eq!(model._get_text("A7"), *"10.2");
|
||||
assert_eq!(model._get_text("A8"), *"#ERROR!");
|
||||
|
||||
830
docs/package-lock.json
generated
830
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,8 @@
|
||||
"preview": "vitepress preview src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"markdown-it-mathjax3": "^4.3.2",
|
||||
"vitepress": "^v2.0.0-alpha.12",
|
||||
"vue": "^3.5.17"
|
||||
"markdown-it-mathjax3": "^4",
|
||||
"vitepress": "^v2.0.0-alpha.15",
|
||||
"vue": "^3.5.25"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2050,10 +2050,10 @@ export default defineConfig({
|
||||
},
|
||||
|
||||
lastUpdated: {
|
||||
text: "Updated at",
|
||||
text: "Updated on",
|
||||
formatOptions: {
|
||||
dateStyle: "full",
|
||||
timeStyle: "medium",
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Model } from "@ironcalc/wasm";
|
||||
import { styled } from "@mui/material";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Fx } from "../../icons";
|
||||
import { theme } from "../../theme";
|
||||
import { FORMULA_BAR_HEIGHT } from "../constants";
|
||||
@@ -9,6 +11,7 @@ import {
|
||||
ROW_HEIGH_SCALE,
|
||||
} from "../WorksheetCanvas/constants";
|
||||
import type { WorkbookState } from "../workbookState";
|
||||
import FormulaBarMenu from "./FormulaBarMenu";
|
||||
|
||||
type FormulaBarProps = {
|
||||
cellAddress: string;
|
||||
@@ -17,6 +20,8 @@ type FormulaBarProps = {
|
||||
workbookState: WorkbookState;
|
||||
onChange: () => void;
|
||||
onTextUpdated: () => void;
|
||||
openDrawer: () => void;
|
||||
canEdit: boolean;
|
||||
};
|
||||
|
||||
function FormulaBar(properties: FormulaBarProps) {
|
||||
@@ -28,10 +33,27 @@ function FormulaBar(properties: FormulaBarProps) {
|
||||
onTextUpdated,
|
||||
workbookState,
|
||||
} = properties;
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
const handleMenuOpenChange = (isOpen: boolean): void => {
|
||||
setIsMenuOpen(isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AddressContainer>
|
||||
<CellBarAddress>{cellAddress}</CellBarAddress>
|
||||
<AddressContainer $active={isMenuOpen}>
|
||||
<FormulaBarMenu
|
||||
onMenuOpenChange={handleMenuOpenChange}
|
||||
openDrawer={properties.openDrawer}
|
||||
canEdit={properties.canEdit}
|
||||
model={model}
|
||||
onUpdate={onChange}
|
||||
>
|
||||
<CellBarAddress>{cellAddress}</CellBarAddress>
|
||||
<StyledIcon>
|
||||
<ChevronDown size={16} />
|
||||
</StyledIcon>
|
||||
</FormulaBarMenu>
|
||||
</AddressContainer>
|
||||
<Divider />
|
||||
<FormulaContainer>
|
||||
@@ -101,7 +123,7 @@ const Divider = styled("div")`
|
||||
background-color: ${theme.palette.grey["300"]};
|
||||
min-width: 1px;
|
||||
height: 16px;
|
||||
margin: 0px 16px;
|
||||
margin: 0px 16px 0px 8px;
|
||||
`;
|
||||
|
||||
const FormulaContainer = styled("div")`
|
||||
@@ -123,20 +145,43 @@ const Container = styled("div")`
|
||||
height: ${FORMULA_BAR_HEIGHT}px;
|
||||
`;
|
||||
|
||||
const AddressContainer = styled("div")`
|
||||
padding-left: 16px;
|
||||
const AddressContainer = styled("div")<{ $active?: boolean }>`
|
||||
color: ${theme.palette.common.black};
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
flex-grow: row;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
background-color: ${(props) =>
|
||||
props.$active ? theme.palette.action.selected : "transparent"};
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
props.$active ? theme.palette.action.selected : theme.palette.grey["100"]};
|
||||
}
|
||||
`;
|
||||
|
||||
const CellBarAddress = styled("div")`
|
||||
width: 100%;
|
||||
text-align: "center";
|
||||
box-sizing: border-box;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding-left: 8px;
|
||||
background-color: transparent;
|
||||
`;
|
||||
|
||||
const StyledIcon = styled("div")`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 2px;
|
||||
background-color: transparent;
|
||||
`;
|
||||
|
||||
const EditorWrapper = styled("div")`
|
||||
|
||||
170
webapp/IronCalc/src/components/FormulaBar/FormulaBarMenu.tsx
Normal file
170
webapp/IronCalc/src/components/FormulaBar/FormulaBarMenu.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import type { Model } from "@ironcalc/wasm";
|
||||
import { Menu, MenuItem, styled } from "@mui/material";
|
||||
import { Tag } from "lucide-react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { theme } from "../../theme";
|
||||
import { parseRangeInSheet } from "../Editor/util";
|
||||
|
||||
type FormulaBarMenuProps = {
|
||||
children: React.ReactNode;
|
||||
onMenuOpenChange: (isOpen: boolean) => void;
|
||||
openDrawer: () => void;
|
||||
canEdit: boolean;
|
||||
model: Model;
|
||||
onUpdate: () => void;
|
||||
};
|
||||
|
||||
const FormulaBarMenu = (properties: FormulaBarMenuProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [isMenuOpen, setMenuOpen] = useState(false);
|
||||
const anchorElement = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleMenuOpen = useCallback((): void => {
|
||||
setMenuOpen(true);
|
||||
properties.onMenuOpenChange(true);
|
||||
}, [properties.onMenuOpenChange]);
|
||||
|
||||
const handleMenuClose = useCallback((): void => {
|
||||
setMenuOpen(false);
|
||||
properties.onMenuOpenChange(false);
|
||||
}, [properties.onMenuOpenChange]);
|
||||
|
||||
const definedNameList = properties.model.getDefinedNameList();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChildrenWrapper onClick={handleMenuOpen} ref={anchorElement}>
|
||||
{properties.children}
|
||||
</ChildrenWrapper>
|
||||
<StyledMenu
|
||||
open={isMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
anchorEl={anchorElement.current}
|
||||
marginThreshold={0}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "left",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
{definedNameList.length > 0 ? (
|
||||
<>
|
||||
{definedNameList.map((definedName) => {
|
||||
return (
|
||||
<MenuItemWrapper
|
||||
key={`${definedName.name}-${definedName.scope}`}
|
||||
disableRipple
|
||||
onClick={() => {
|
||||
// select the area corresponding to the defined name
|
||||
const formula = definedName.formula;
|
||||
const range = parseRangeInSheet(properties.model, formula);
|
||||
if (range) {
|
||||
const [
|
||||
sheetIndex,
|
||||
rowStart,
|
||||
columnStart,
|
||||
rowEnd,
|
||||
columnEnd,
|
||||
] = range;
|
||||
properties.model.setSelectedSheet(sheetIndex);
|
||||
properties.model.setSelectedCell(rowStart, columnStart);
|
||||
properties.model.setSelectedRange(
|
||||
rowStart,
|
||||
columnStart,
|
||||
rowEnd,
|
||||
columnEnd,
|
||||
);
|
||||
}
|
||||
properties.onUpdate();
|
||||
handleMenuClose();
|
||||
}}
|
||||
>
|
||||
<Tag />
|
||||
<MenuItemText>{definedName.name}</MenuItemText>
|
||||
<MenuItemExample>{definedName.formula}</MenuItemExample>
|
||||
</MenuItemWrapper>
|
||||
);
|
||||
})}
|
||||
<MenuDivider />
|
||||
</>
|
||||
) : null}
|
||||
<MenuItemWrapper
|
||||
onClick={() => {
|
||||
properties.openDrawer();
|
||||
handleMenuClose();
|
||||
}}
|
||||
disabled={!properties.canEdit}
|
||||
disableRipple
|
||||
>
|
||||
<MenuItemText>{t("formula_bar.manage_named_ranges")}</MenuItemText>
|
||||
</MenuItemWrapper>
|
||||
</StyledMenu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledMenu = styled(Menu)`
|
||||
top: 4px;
|
||||
min-width: 260px;
|
||||
max-width: 460px;
|
||||
& .MuiPaper-root {
|
||||
border-radius: 8px;
|
||||
padding: 4px 0px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
& .MuiList-root {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const MenuItemWrapper = styled(MenuItem)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
width: calc(100% - 8px);
|
||||
min-width: 172px;
|
||||
margin: 0px 4px;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
height: 32px;
|
||||
& svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
flex-shrink: 0;
|
||||
color: ${theme.palette.grey[600]};
|
||||
}
|
||||
`;
|
||||
|
||||
const ChildrenWrapper = styled("div")`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const MenuDivider = styled("div")`
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
border-top: 1px solid ${theme.palette.grey[200]};
|
||||
`;
|
||||
|
||||
const MenuItemText = styled("div")`
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: ${theme.palette.common.black};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const MenuItemExample = styled("div")`
|
||||
color: ${theme.palette.grey[400]};
|
||||
margin-left: 12px;
|
||||
`;
|
||||
|
||||
export default FormulaBarMenu;
|
||||
@@ -398,7 +398,9 @@ const ListItem = styled("div")<{ $isSelected: boolean }>(({ $isSelected }) => ({
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
gap: "8px",
|
||||
padding: "8px 12px",
|
||||
cursor: "pointer",
|
||||
minHeight: "40px",
|
||||
boxSizing: "border-box",
|
||||
borderBottom: `1px solid ${theme.palette.grey[200]}`,
|
||||
@@ -438,6 +440,8 @@ const NameText = styled("span")({
|
||||
fontSize: "12px",
|
||||
color: theme.palette.common.black,
|
||||
fontWeight: 600,
|
||||
wordBreak: "break-all",
|
||||
overflowWrap: "break-word",
|
||||
});
|
||||
|
||||
const IconsWrapper = styled("div")({
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
Redo2,
|
||||
RemoveFormatting,
|
||||
Strikethrough,
|
||||
Tags,
|
||||
Type,
|
||||
Underline,
|
||||
Undo2,
|
||||
@@ -87,7 +86,6 @@ type ToolbarProperties = {
|
||||
numFmt: string;
|
||||
showGridLines: boolean;
|
||||
onToggleShowGridLines: (show: boolean) => void;
|
||||
openDrawer: () => void;
|
||||
};
|
||||
|
||||
function Toolbar(properties: ToolbarProperties) {
|
||||
@@ -514,18 +512,6 @@ function Toolbar(properties: ToolbarProperties) {
|
||||
{properties.showGridLines ? <Grid2x2Check /> : <Grid2x2X />}
|
||||
</StyledButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("toolbar.named_ranges")}>
|
||||
<StyledButton
|
||||
type="button"
|
||||
$pressed={false}
|
||||
onClick={() => {
|
||||
properties.openDrawer();
|
||||
}}
|
||||
disabled={!canEdit}
|
||||
>
|
||||
<Tags />
|
||||
</StyledButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("toolbar.selected_png")}>
|
||||
<StyledButton
|
||||
type="button"
|
||||
|
||||
@@ -135,76 +135,7 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
||||
};
|
||||
|
||||
const onIncreaseFontSize = (delta: number) => {
|
||||
/** Automatically adjusts row heights when font size changes. */
|
||||
const DEFAULT_ROW_HEIGHT = 28;
|
||||
|
||||
const {
|
||||
sheet,
|
||||
range: [rowStart, columnStart, rowEnd, columnEnd],
|
||||
} = model.getSelectedView();
|
||||
|
||||
/** Normalize the range (selection can be from bottom-right to top-left) */
|
||||
const actualRowStart = Math.min(rowStart, rowEnd);
|
||||
const actualRowEnd = Math.max(rowStart, rowEnd);
|
||||
const actualColumnStart = Math.min(columnStart, columnEnd);
|
||||
const actualColumnEnd = Math.max(columnStart, columnEnd);
|
||||
|
||||
/** Capture current state BEFORE updating font size */
|
||||
const rowData: Array<{
|
||||
row: number;
|
||||
oldFontSize: number;
|
||||
oldRowHeight: number;
|
||||
maxLineCount: number;
|
||||
}> = [];
|
||||
|
||||
for (let row = actualRowStart; row <= actualRowEnd; row++) {
|
||||
let maxFontSize = 0;
|
||||
let maxLineCount = 1;
|
||||
for (let col = actualColumnStart; col <= actualColumnEnd; col++) {
|
||||
const style = model.getCellStyle(sheet, row, col);
|
||||
maxFontSize = Math.max(maxFontSize, style.font.sz);
|
||||
|
||||
// Count lines in cell content (we add new lines with Alt+Enter / Option+Enter)
|
||||
const cellContent = model.getCellContent(sheet, row, col);
|
||||
const lineCount = cellContent.split("\n").length;
|
||||
maxLineCount = Math.max(maxLineCount, lineCount);
|
||||
}
|
||||
const oldRowHeight = model.getRowHeight(sheet, row);
|
||||
rowData.push({
|
||||
row,
|
||||
oldFontSize: maxFontSize,
|
||||
oldRowHeight,
|
||||
maxLineCount,
|
||||
});
|
||||
}
|
||||
|
||||
/** Update the font size in the model */
|
||||
updateRangeStyle("font.size_delta", `${delta}`);
|
||||
|
||||
/** Adjust row heights based on the new font sizes and line counts */
|
||||
for (const { row, oldFontSize, oldRowHeight, maxLineCount } of rowData) {
|
||||
const newFontSize = oldFontSize + delta;
|
||||
|
||||
if (oldRowHeight < DEFAULT_ROW_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const lineHeight = newFontSize * 1.5;
|
||||
const requiredHeight = (maxLineCount - 1) * lineHeight + 8 + newFontSize;
|
||||
|
||||
let newRowHeight: number;
|
||||
if (requiredHeight > DEFAULT_ROW_HEIGHT) {
|
||||
newRowHeight = requiredHeight;
|
||||
} else {
|
||||
newRowHeight = DEFAULT_ROW_HEIGHT;
|
||||
}
|
||||
|
||||
if (Math.abs(newRowHeight - oldRowHeight) > 0.1) {
|
||||
model.setRowsHeight(sheet, row, row, newRowHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setRedrawId((id) => id + 1);
|
||||
};
|
||||
|
||||
const onCopyStyles = () => {
|
||||
@@ -734,9 +665,6 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
||||
model.setShowGridLines(sheet, show);
|
||||
setRedrawId((id) => id + 1);
|
||||
}}
|
||||
openDrawer={() => {
|
||||
setDrawerOpen(true);
|
||||
}}
|
||||
/>
|
||||
<WorksheetAreaLeft $drawerWidth={isDrawerOpen ? drawerWidth : 0}>
|
||||
<FormulaBar
|
||||
@@ -751,6 +679,10 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
||||
}}
|
||||
model={model}
|
||||
workbookState={workbookState}
|
||||
openDrawer={() => {
|
||||
setDrawerOpen(true);
|
||||
}}
|
||||
canEdit={true}
|
||||
/>
|
||||
<Worksheet
|
||||
model={model}
|
||||
@@ -833,7 +765,7 @@ const WorksheetAreaLeft = styled("div")<WorksheetAreaLeftProps>(
|
||||
position: "absolute",
|
||||
top: `${TOOLBAR_HEIGHT + 1}px`,
|
||||
width: `calc(100% - ${$drawerWidth}px)`,
|
||||
height: `calc(100% - ${TOOLBAR_HEIGHT + 1}px)`,
|
||||
height: `calc(100% - ${TOOLBAR_HEIGHT}px)`,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -92,6 +92,9 @@
|
||||
"label": "Formula",
|
||||
"title": "Update formula"
|
||||
},
|
||||
"formula_bar": {
|
||||
"manage_named_ranges": "Manage Named Ranges"
|
||||
},
|
||||
"navigation": {
|
||||
"add_sheet": "Add sheet",
|
||||
"sheet_list": "Sheet list",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user