react-konva-grid
Advanced tools
Comparing version 2.8.20 to 3.0.0
@@ -76,2 +76,6 @@ import React, { Key } from "react"; | ||
/** | ||
* Fill selection | ||
*/ | ||
fillSelection?: SelectionArea | null; | ||
/** | ||
* Array of merged cells | ||
@@ -121,2 +125,6 @@ */ | ||
/** | ||
* Mousedown event on fillhandle | ||
*/ | ||
onFillHandleMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void; | ||
/** | ||
* Fired when scroll viewport changes | ||
@@ -142,2 +150,6 @@ */ | ||
stageProps?: Omit<StageConfig, "container">; | ||
/** | ||
* Show fillhandle | ||
*/ | ||
showFillHandle?: boolean; | ||
} | ||
@@ -152,2 +164,3 @@ export interface CellRangeArea extends CellInterface { | ||
export interface SelectionProps extends ShapeConfig { | ||
onFillHandleMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void; | ||
} | ||
@@ -172,5 +185,9 @@ export declare type ScrollCoords = { | ||
export declare type ItemSizer = (index: number) => number; | ||
export interface SelectionArea { | ||
export interface SelectionArea extends AreaStyle { | ||
bounds: AreaProps; | ||
inProgress?: boolean; | ||
/** | ||
* When user drags the fill handle | ||
*/ | ||
isFilling?: boolean; | ||
} | ||
@@ -243,3 +260,3 @@ export interface AreaProps { | ||
bounds: AreaProps; | ||
style: ShapeConfig; | ||
style?: ShapeConfig; | ||
} | ||
@@ -246,0 +263,0 @@ /** |
263
dist/Grid.js
@@ -40,2 +40,3 @@ "use strict"; | ||
const Cell_1 = require("./Cell"); | ||
const Selection_1 = __importDefault(require("./Selection")); | ||
const utils_1 = require("./utils"); | ||
@@ -54,3 +55,3 @@ const tiny_invariant_1 = __importDefault(require("tiny-invariant")); | ||
const defaultSelectionRenderer = (props) => { | ||
return utils_1.createBox(Object.assign({ strokeWidth: 1, strokeBoxWidth: 0 }, props)); | ||
return react_1.default.createElement(Selection_1.default, Object.assign({}, props)); | ||
}; | ||
@@ -63,3 +64,3 @@ const RESET_SCROLL_EVENTS_DEBOUNCE_INTERVAL = 150; | ||
const Grid = react_1.memo(react_1.forwardRef((props, forwardedRef) => { | ||
const { width: containerWidth = 800, height: containerHeight = 600, estimatedColumnWidth, estimatedRowHeight, rowHeight = defaultRowHeight, columnWidth = defaultColumnWidth, rowCount = 0, columnCount = 0, scrollbarSize = 13, onScroll, showScrollbar = true, selectionBackgroundColor = "rgb(14, 101, 235, 0.1)", selectionBorderColor = "#1a73e8", selectionStrokeWidth = 1, activeCellStrokeWidth = 2, activeCell, selections = [], frozenRows = 0, frozenColumns = 0, itemRenderer = Cell_1.CellRenderer, mergedCells = [], snap = false, scrollThrottleTimeout = 100, onViewChange, selectionRenderer = defaultSelectionRenderer, onBeforeRenderRow, showFrozenShadow = false, shadowSettings = defaultShadowSettings, borderStyles = [], children, stageProps, wrapper = (children) => children, cellAreas = [] } = props, rest = __rest(props, ["width", "height", "estimatedColumnWidth", "estimatedRowHeight", "rowHeight", "columnWidth", "rowCount", "columnCount", "scrollbarSize", "onScroll", "showScrollbar", "selectionBackgroundColor", "selectionBorderColor", "selectionStrokeWidth", "activeCellStrokeWidth", "activeCell", "selections", "frozenRows", "frozenColumns", "itemRenderer", "mergedCells", "snap", "scrollThrottleTimeout", "onViewChange", "selectionRenderer", "onBeforeRenderRow", "showFrozenShadow", "shadowSettings", "borderStyles", "children", "stageProps", "wrapper", "cellAreas"]); | ||
const { width: containerWidth = 800, height: containerHeight = 600, estimatedColumnWidth, estimatedRowHeight, rowHeight = defaultRowHeight, columnWidth = defaultColumnWidth, rowCount = 0, columnCount = 0, scrollbarSize = 13, onScroll, showScrollbar = true, selectionBackgroundColor = "rgb(14, 101, 235, 0.1)", selectionBorderColor = "#1a73e8", selectionStrokeWidth = 1, activeCellStrokeWidth = 2, activeCell, selections = [], frozenRows = 0, frozenColumns = 0, itemRenderer = Cell_1.CellRenderer, mergedCells = [], snap = false, scrollThrottleTimeout = 100, onViewChange, selectionRenderer = defaultSelectionRenderer, onBeforeRenderRow, showFrozenShadow = false, shadowSettings = defaultShadowSettings, borderStyles = [], children, stageProps, wrapper = (children) => children, cellAreas = [], showFillHandle = true, onFillHandleMouseDown, fillSelection } = props, rest = __rest(props, ["width", "height", "estimatedColumnWidth", "estimatedRowHeight", "rowHeight", "columnWidth", "rowCount", "columnCount", "scrollbarSize", "onScroll", "showScrollbar", "selectionBackgroundColor", "selectionBorderColor", "selectionStrokeWidth", "activeCellStrokeWidth", "activeCell", "selections", "frozenRows", "frozenColumns", "itemRenderer", "mergedCells", "snap", "scrollThrottleTimeout", "onViewChange", "selectionRenderer", "onBeforeRenderRow", "showFrozenShadow", "shadowSettings", "borderStyles", "children", "stageProps", "wrapper", "cellAreas", "showFillHandle", "onFillHandleMouseDown", "fillSelection"]); | ||
tiny_invariant_1.default(!(children && typeof children !== "function"), "Children should be a function"); | ||
@@ -234,23 +235,25 @@ /* Expose some methods in ref */ | ||
/* Find frozen column boundary */ | ||
const isWithinFrozenColumnBoundary = (x) => { | ||
return (frozenColumns > 0 && | ||
x < | ||
helpers_1.getColumnOffset({ | ||
index: frozenColumns, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
})); | ||
}; | ||
const frozenColumnWidth = react_1.useMemo(() => { | ||
return helpers_1.getColumnOffset({ | ||
index: frozenColumns, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}); | ||
}, [frozenColumns]); | ||
const frozenRowHeight = react_1.useMemo(() => { | ||
return helpers_1.getRowOffset({ | ||
index: frozenRows, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}); | ||
}, [frozenRows]); | ||
const isWithinFrozenColumnBoundary = react_1.useCallback((x) => { | ||
return frozenColumns > 0 && x < frozenColumnWidth; | ||
}, [frozenColumns, frozenColumnWidth]); | ||
/* Find frozen row boundary */ | ||
const isWithinFrozenRowBoundary = (y) => { | ||
return (frozenRows > 0 && | ||
y < | ||
helpers_1.getRowOffset({ | ||
index: frozenRows, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
})); | ||
}; | ||
const isWithinFrozenRowBoundary = react_1.useCallback((y) => { | ||
return frozenRows > 0 && y < frozenRowHeight; | ||
}, [frozenRows, frozenRowHeight]); | ||
/** | ||
@@ -796,2 +799,72 @@ * Get cell cordinates from current mouse x/y positions | ||
/** | ||
* Renders active cell | ||
*/ | ||
let fillHandleDimension = {}; | ||
let activeCellSelection = null; | ||
let activeCellSelectionFrozenColumn = null; | ||
let activeCellSelectionFrozenRow = null; | ||
let activeCellSelectionFrozenIntersection = null; | ||
if (activeCell) { | ||
const bounds = getCellBounds(activeCell); | ||
const { top, left, right, bottom } = bounds; | ||
const actualBottom = Math.min(rowStopIndex, bottom); | ||
const actualRight = Math.min(columnStopIndex, right); | ||
const isInFrozenColumn = left < frozenColumns; | ||
const isInFrozenRow = top < frozenRows; | ||
const isInFrozenIntersection = isInFrozenRow && isInFrozenColumn; | ||
const y = helpers_1.getRowOffset({ | ||
index: top, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}); | ||
const height = helpers_1.getRowOffset({ | ||
index: actualBottom, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}) - | ||
y + | ||
helpers_1.getRowHeight(actualBottom, instanceProps.current); | ||
const x = helpers_1.getColumnOffset({ | ||
index: left, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}); | ||
const width = helpers_1.getColumnOffset({ | ||
index: actualRight, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}) - | ||
x + | ||
helpers_1.getColumnWidth(actualRight, instanceProps.current); | ||
const cell = selectionRenderer({ | ||
stroke: selectionBorderColor, | ||
strokeWidth: activeCellStrokeWidth, | ||
fill: "transparent", | ||
x: x, | ||
y: y, | ||
width: width, | ||
height: height, | ||
}); | ||
if (isInFrozenIntersection) { | ||
activeCellSelectionFrozenIntersection = cell; | ||
} | ||
else if (isInFrozenRow) { | ||
activeCellSelectionFrozenRow = cell; | ||
} | ||
else if (isInFrozenColumn) { | ||
activeCellSelectionFrozenColumn = cell; | ||
} | ||
else { | ||
activeCellSelection = cell; | ||
} | ||
fillHandleDimension = { | ||
x: x + width, | ||
y: y + height, | ||
}; | ||
} | ||
/** | ||
* Convert selections to area | ||
@@ -802,2 +875,3 @@ * Removed useMemo as changes to lastMeasureRowIndex, lastMeasuredColumnIndex, | ||
*/ | ||
let isSelectionInProgress = false; | ||
const selectionAreas = []; | ||
@@ -808,3 +882,3 @@ const selectionAreasFrozenColumns = []; | ||
for (let i = 0; i < selections.length; i++) { | ||
const { bounds, inProgress } = selections[i]; | ||
const { bounds, inProgress, style } = selections[i]; | ||
const { top, left, right, bottom } = bounds; | ||
@@ -817,6 +891,6 @@ const selectionBounds = { x: 0, y: 0, width: 0, height: 0 }; | ||
const isIntersectionFrozen = top < frozenRows && left < frozenColumns; | ||
const styles = { | ||
stroke: inProgress ? "transparent" : selectionBorderColor, | ||
fill: selectionBackgroundColor, | ||
}; | ||
const isLast = i === selections.length - 1; | ||
const styles = Object.assign({ stroke: inProgress ? selectionBackgroundColor : selectionBorderColor, fill: selectionBackgroundColor }, style); | ||
if (inProgress) | ||
isSelectionInProgress = true; | ||
selectionBounds.y = helpers_1.getRowOffset({ | ||
@@ -894,18 +968,24 @@ index: top, | ||
selectionAreas.push(selectionRenderer(Object.assign(Object.assign({}, styles), { key: i, x: selectionBounds.x, y: selectionBounds.y, width: selectionBounds.width, height: selectionBounds.height }))); | ||
if (isLast) { | ||
fillHandleDimension = { | ||
x: selectionBounds.x + selectionBounds.width, | ||
y: selectionBounds.y + selectionBounds.height, | ||
}; | ||
} | ||
} | ||
/** | ||
* Renders active cell | ||
* Fillselection | ||
*/ | ||
let activeCellSelection = null; | ||
let activeCellSelectionFrozenColumn = null; | ||
let activeCellSelectionFrozenRow = null; | ||
let activeCellSelectionFrozenIntersection = null; | ||
if (activeCell) { | ||
const bounds = getCellBounds(activeCell); | ||
let fillSelections = null; | ||
if (fillSelection) { | ||
const { bounds } = fillSelection; | ||
const { top, left, right, bottom } = bounds; | ||
const actualBottom = Math.min(rowStopIndex, bottom); | ||
const actualRight = Math.min(columnStopIndex, right); | ||
const isInFrozenColumn = left < frozenColumns; | ||
const isInFrozenRow = top < frozenRows; | ||
const isInFrozenIntersection = isInFrozenRow && isInFrozenColumn; | ||
const x = helpers_1.getColumnOffset({ | ||
index: left, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}); | ||
const y = helpers_1.getRowOffset({ | ||
@@ -925,8 +1005,2 @@ index: top, | ||
helpers_1.getRowHeight(actualBottom, instanceProps.current); | ||
const x = helpers_1.getColumnOffset({ | ||
index: left, | ||
rowHeight, | ||
columnWidth, | ||
instanceProps: instanceProps.current, | ||
}); | ||
const width = helpers_1.getColumnOffset({ | ||
@@ -940,23 +1014,10 @@ index: actualRight, | ||
helpers_1.getColumnWidth(actualRight, instanceProps.current); | ||
const cell = selectionRenderer({ | ||
stroke: selectionBorderColor, | ||
strokeWidth: activeCellStrokeWidth, | ||
fill: "transparent", | ||
x: x, | ||
y: y, | ||
width: width, | ||
height: height, | ||
fillSelections = selectionRenderer({ | ||
x, | ||
y, | ||
width, | ||
height, | ||
stroke: "gray", | ||
strokeStyle: "dashed", | ||
}); | ||
if (isInFrozenIntersection) { | ||
activeCellSelectionFrozenIntersection = cell; | ||
} | ||
else if (isInFrozenRow) { | ||
activeCellSelectionFrozenRow = cell; | ||
} | ||
else if (isInFrozenColumn) { | ||
activeCellSelectionFrozenColumn = cell; | ||
} | ||
else { | ||
activeCellSelection = cell; | ||
} | ||
} | ||
@@ -1017,6 +1078,3 @@ const borderStylesCells = react_1.useMemo(() => { | ||
react_1.default.createElement(ReactKonvaCore_1.Layer, null, | ||
react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: scrollLeft, listening: false }, | ||
borderStylesCells, | ||
selectionAreas, | ||
activeCellSelection), | ||
react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: scrollLeft, listening: false }, borderStylesCells), | ||
frozenRowShadowComponent, | ||
@@ -1027,17 +1085,8 @@ frozenColumnShadowComponent, | ||
frozenRowMergedCellAreas), | ||
react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: 0, offsetX: scrollLeft, listening: false }, | ||
selectionAreasFrozenRows, | ||
activeCellSelectionFrozenRow), | ||
react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: 0 }, | ||
frozenColumnCells, | ||
frozenColumnMergedCellAreas), | ||
react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: 0, listening: false }, | ||
selectionAreasFrozenColumns, | ||
activeCellSelectionFrozenColumn), | ||
react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: 0, offsetX: 0 }, | ||
frozenIntersectionCells, | ||
frozenIntersectionMergedCells), | ||
react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: 0, offsetX: 0, listening: false }, | ||
selectionAreasIntersection, | ||
activeCellSelectionFrozenIntersection)), | ||
frozenIntersectionMergedCells)), | ||
children && typeof children === "function" | ||
@@ -1049,5 +1098,63 @@ ? children({ | ||
: null)); | ||
const fillhandleComponent = showFillHandle && !isSelectionInProgress ? (react_1.default.createElement(utils_1.FillHandle, Object.assign({}, fillHandleDimension, { stroke: selectionBorderColor, onMouseDown: onFillHandleMouseDown }))) : null; | ||
const fillHandleWidth = 8; | ||
const selectionChildren = (react_1.default.createElement(react_1.default.Fragment, null, | ||
react_1.default.createElement("div", { style: { | ||
position: "absolute", | ||
left: frozenColumnWidth, | ||
top: frozenRowHeight, | ||
right: 0, | ||
bottom: 0, | ||
overflow: "hidden", | ||
} }, | ||
react_1.default.createElement("div", { style: { | ||
transform: `translate(-${scrollLeft + frozenColumnWidth}px, -${scrollTop + frozenRowHeight}px)`, | ||
} }, | ||
fillSelections, | ||
selectionAreas, | ||
activeCellSelection, | ||
fillhandleComponent)), | ||
react_1.default.createElement("div", { style: { | ||
position: "absolute", | ||
width: frozenColumnWidth + fillHandleWidth, | ||
top: frozenRowHeight, | ||
left: 0, | ||
bottom: 0, | ||
overflow: "hidden", | ||
} }, | ||
react_1.default.createElement("div", { style: { | ||
transform: `translate(0, -${scrollTop + frozenRowHeight}px)`, | ||
} }, | ||
selectionAreasFrozenColumns, | ||
activeCellSelectionFrozenColumn, | ||
fillhandleComponent)), | ||
react_1.default.createElement("div", { style: { | ||
position: "absolute", | ||
height: frozenRowHeight + fillHandleWidth, | ||
left: frozenColumnWidth, | ||
right: 0, | ||
top: 0, | ||
overflow: "hidden", | ||
} }, | ||
react_1.default.createElement("div", { style: { | ||
transform: `translate(-${scrollLeft + frozenColumnWidth}px, 0)`, | ||
} }, | ||
selectionAreasFrozenRows, | ||
activeCellSelectionFrozenRow, | ||
fillhandleComponent)), | ||
react_1.default.createElement("div", { style: { | ||
position: "absolute", | ||
height: frozenRowHeight + fillHandleWidth, | ||
width: frozenColumnWidth + fillHandleWidth, | ||
left: 0, | ||
top: 0, | ||
overflow: "hidden", | ||
} }, | ||
selectionAreasIntersection, | ||
activeCellSelectionFrozenIntersection, | ||
fillhandleComponent))); | ||
return (react_1.default.createElement("div", { style: { position: "relative", width: containerWidth } }, | ||
react_1.default.createElement("div", Object.assign({ onWheel: handleWheel, tabIndex: 0, ref: containerRef }, rest), | ||
react_1.default.createElement(ReactKonvaCore_1.Stage, Object.assign({ width: containerWidth, height: containerHeight, ref: stageRef, listening: listenToEvents }, stageProps), wrapper(stageChildren))), | ||
react_1.default.createElement(ReactKonvaCore_1.Stage, Object.assign({ width: containerWidth, height: containerHeight, ref: stageRef, listening: listenToEvents }, stageProps), wrapper(stageChildren)), | ||
selectionChildren), | ||
showScrollbar ? (react_1.default.createElement(react_1.default.Fragment, null, | ||
@@ -1054,0 +1161,0 @@ react_1.default.createElement("div", { tabIndex: -1, style: { |
@@ -71,2 +71,6 @@ import React from "react"; | ||
nextFocusableCell: (currentCell: CellInterface, direction: Direction) => CellInterface; | ||
/** | ||
* Is editing in progress | ||
*/ | ||
isEditInProgress: boolean; | ||
} | ||
@@ -73,0 +77,0 @@ export interface EditorProps extends CellInterface { |
@@ -43,3 +43,3 @@ "use strict"; | ||
const borderWidth = 2; | ||
const padding = 16; | ||
const padding = 10; /* 2 + 1 + 1 + 2 + 2 */ | ||
const textSizer = react_1.useRef(helpers_1.AutoSizerCanvas("12px Arial")); | ||
@@ -51,3 +51,3 @@ const inputRef = react_1.useRef(null); | ||
const textWidth = ((_a = textSizer.current.measureText(text)) === null || _a === void 0 ? void 0 : _a.width) || 0; | ||
return Math.max(textWidth + padding, width + borderWidth); | ||
return Math.max(textWidth + padding, width); | ||
}, []); | ||
@@ -62,42 +62,48 @@ const [inputWidth, setInputWidth] = react_1.useState(() => getWidth(value)); | ||
}, []); | ||
const inputHeight = height + borderWidth; | ||
return (react_1.default.createElement("textarea", Object.assign({ rows: 1, cols: 1, ref: inputRef, defaultValue: value, style: { | ||
font: "12px Arial", | ||
lineHeight: 1.5, | ||
const inputHeight = height; | ||
return (react_1.default.createElement("div", { style: { | ||
top: y - borderWidth / 2, | ||
left: x, | ||
position: "absolute", | ||
top: y - borderWidth / 2, | ||
left: x - borderWidth / 2, | ||
width: inputWidth, | ||
height: inputHeight, | ||
height: inputHeight + borderWidth, | ||
padding: borderWidth, | ||
boxShadow: "0 2px 6px 2px rgba(60,64,67,.15)", | ||
border: "2px #1a73e8 solid", | ||
background: "white", | ||
padding: "0 4px", | ||
margin: 0, | ||
boxSizing: "border-box", | ||
border: "2px #1a73e8 solid", | ||
boxShadow: "0 2px 6px 2px rgba(60,64,67,.15)", | ||
outline: "none", | ||
resize: "none", | ||
overflow: "hidden", | ||
}, onChange: (e) => { | ||
setInputWidth(getWidth(e.target.value)); | ||
onChange(e.target.value, cell); | ||
}, onKeyDown: (e) => { | ||
if (!inputRef.current) | ||
return; | ||
const isShiftKey = e.nativeEvent.shiftKey; | ||
const value = inputRef.current.value; | ||
// Enter key | ||
if (e.which === types_1.KeyCodes.Enter) { | ||
onSubmit && | ||
onSubmit(value, cell, nextFocusableCell(cell, isShiftKey ? types_1.Direction.Up : types_1.Direction.Down)); | ||
} | ||
if (e.which === types_1.KeyCodes.Escape) { | ||
onCancel && onCancel(e); | ||
} | ||
if (e.which === types_1.KeyCodes.Tab) { | ||
// e.preventDefault(); | ||
onSubmit && | ||
onSubmit(value, cell, nextFocusableCell(cell, isShiftKey ? types_1.Direction.Left : types_1.Direction.Right)); | ||
} | ||
} }, rest))); | ||
} }, | ||
react_1.default.createElement("textarea", Object.assign({ rows: 1, cols: 1, ref: inputRef, defaultValue: value, style: { | ||
font: "12px Arial", | ||
lineHeight: 1.2, | ||
width: "100%", | ||
height: "100%", | ||
padding: "0 1px", | ||
margin: 0, | ||
boxSizing: "border-box", | ||
borderWidth: 0, | ||
outline: "none", | ||
resize: "none", | ||
overflow: "hidden", | ||
}, onChange: (e) => { | ||
setInputWidth(getWidth(e.target.value)); | ||
onChange(e.target.value, cell); | ||
}, onKeyDown: (e) => { | ||
if (!inputRef.current) | ||
return; | ||
const isShiftKey = e.nativeEvent.shiftKey; | ||
const value = inputRef.current.value; | ||
// Enter key | ||
if (e.which === types_1.KeyCodes.Enter) { | ||
onSubmit && | ||
onSubmit(value, cell, nextFocusableCell(cell, isShiftKey ? types_1.Direction.Up : types_1.Direction.Down)); | ||
} | ||
if (e.which === types_1.KeyCodes.Escape) { | ||
onCancel && onCancel(e); | ||
} | ||
if (e.which === types_1.KeyCodes.Tab) { | ||
// e.preventDefault(); | ||
onSubmit && | ||
onSubmit(value, cell, nextFocusableCell(cell, isShiftKey ? types_1.Direction.Left : types_1.Direction.Right)); | ||
} | ||
} }, rest)))); | ||
}; | ||
@@ -317,2 +323,3 @@ const getDefaultEditor = (cell) => DefaultEditor; | ||
nextFocusableCell, | ||
isEditInProgress: !!editingCell, | ||
}; | ||
@@ -319,0 +326,0 @@ }; |
@@ -37,2 +37,6 @@ import React from "react"; | ||
persistantSelectionMode?: boolean; | ||
/** | ||
* onFill | ||
*/ | ||
onFill?: (activeCell: CellInterface, selection: SelectionArea | null, selections: SelectionArea[]) => void; | ||
} | ||
@@ -68,2 +72,11 @@ export interface SelectionResults { | ||
onKeyDown: (e: React.KeyboardEvent<HTMLElement>) => void; | ||
/** | ||
* Mousedown event on fillhandle | ||
*/ | ||
onFillHandleMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void; | ||
/** | ||
* | ||
* Fill selections | ||
*/ | ||
fillSelection: SelectionArea | null; | ||
} | ||
@@ -70,0 +83,0 @@ /** |
@@ -12,8 +12,10 @@ "use strict"; | ||
const useSelection = (options) => { | ||
const { gridRef, initialActiveCell = null, initialSelections = [], columnCount = 0, rowCount = 0, allowMultipleSelection = true, persistantSelectionMode = false, allowDeselectSelection = true, } = options || {}; | ||
const { gridRef, initialActiveCell = null, initialSelections = [], columnCount = 0, rowCount = 0, allowMultipleSelection = true, persistantSelectionMode = false, allowDeselectSelection = true, onFill, } = options || {}; | ||
const [activeCell, setActiveCell] = react_1.useState(initialActiveCell); | ||
const [selections, setSelections] = react_1.useState(initialSelections); | ||
const [fillSelection, setFillSelection] = react_1.useState(null); | ||
const selectionStart = react_1.useRef(null); | ||
const selectionEnd = react_1.useRef(null); | ||
const isSelecting = react_1.useRef(); | ||
const isFilling = react_1.useRef(); | ||
const firstActiveCell = react_1.useRef(null); | ||
@@ -63,3 +65,3 @@ /** | ||
/* Modify current selection */ | ||
const modifySelection = (coords, setInProgress) => { | ||
const modifySelection = (coords, setInProgress, isFilling) => { | ||
if (!selectionStart.current) | ||
@@ -78,7 +80,9 @@ return; | ||
if (!len) { | ||
return [{ bounds, inProgress: setInProgress ? true : false }]; | ||
return [ | ||
{ bounds, inProgress: setInProgress ? true : false, isFilling }, | ||
]; | ||
} | ||
return prevSelection.map((sel, i) => { | ||
if (len - 1 === i) { | ||
return Object.assign(Object.assign({}, sel), { bounds, inProgress: setInProgress ? true : false }); | ||
return Object.assign(Object.assign({}, sel), { bounds, inProgress: setInProgress ? true : false, isFilling }); | ||
} | ||
@@ -507,2 +511,40 @@ return sel; | ||
}, []); | ||
const handleFillHandleMouseDown = react_1.useCallback((e) => { | ||
e.stopPropagation(); | ||
isFilling.current = true; | ||
document.addEventListener("mousemove", handleFillHandleMouseMove); | ||
document.addEventListener("mouseup", handleFillHandleMouseUp); | ||
}, [activeCell, selections]); | ||
const handleFillHandleMouseMove = react_1.useCallback((e) => { | ||
/* Exit if user is not in selection mode */ | ||
if (!isFilling.current || !gridRef || !activeCell) | ||
return; | ||
const coords = gridRef.current.getCellCoordsFromOffset(e.clientX, e.clientY); | ||
const bounds = selectionFromStartEnd(activeCell, coords); | ||
if (!bounds) | ||
return; | ||
setFillSelection({ bounds }); | ||
gridRef.current.scrollToItem(coords); | ||
}, [activeCell, selections]); | ||
const handleFillHandleMouseUp = react_1.useCallback((e) => { | ||
isFilling.current = false; | ||
/* Remove listener */ | ||
document.removeEventListener("mousemove", handleFillHandleMouseMove); | ||
document.removeEventListener("mouseup", handleFillHandleMouseUp); | ||
/* Exit early */ | ||
if (!gridRef) | ||
return; | ||
/* Update last selection */ | ||
let fillSelection = null; | ||
const coords = gridRef.current.getCellCoordsFromOffset(e.clientX, e.clientY); | ||
setFillSelection((prev) => { | ||
fillSelection = prev; | ||
return null; | ||
}); | ||
if (!activeCell) | ||
return; | ||
onFill && onFill(activeCell, fillSelection, selections); | ||
/* Modify last selection */ | ||
modifySelection(coords); | ||
}, [activeCell, selections]); | ||
return { | ||
@@ -516,2 +558,4 @@ activeCell, | ||
setActiveCell: handleSetActiveCell, | ||
onFillHandleMouseDown: handleFillHandleMouseDown, | ||
fillSelection, | ||
}; | ||
@@ -518,0 +562,0 @@ }; |
@@ -1,4 +0,7 @@ | ||
/// <reference types="react" /> | ||
import React from "react"; | ||
import { ShapeConfig } from "konva/types/Shape"; | ||
interface BoxProps extends ShapeConfig { | ||
onCrosshairMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void; | ||
onCrosshairMouseMove?: (e: React.MouseEvent<HTMLDivElement>) => void; | ||
onCrosshairMouseUp?: (e: React.MouseEvent<HTMLDivElement>) => void; | ||
} | ||
@@ -10,2 +13,4 @@ /** | ||
export declare const createBox: ({ x, y, width, height, fill, stroke, strokeLeftColor, strokeTopColor, strokeRightColor, strokeBottomColor, strokeWidth, strokeTopWidth, strokeRightWidth, strokeBottomWidth, strokeLeftWidth, key, }: BoxProps) => JSX.Element; | ||
export declare const FillHandle: React.FC<BoxProps>; | ||
export declare const createHTMLBox: ({ x, y, width, height, fill, stroke, strokeLeftColor, strokeTopColor, strokeRightColor, strokeBottomColor, strokeWidth, strokeTopWidth, strokeRightWidth, strokeBottomWidth, strokeLeftWidth, key, strokeStyle, }: BoxProps) => JSX.Element; | ||
export {}; |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createBox = void 0; | ||
exports.createHTMLBox = exports.FillHandle = exports.createBox = void 0; | ||
const react_1 = __importDefault(require("react")); | ||
@@ -32,2 +32,82 @@ const react_konva_1 = require("react-konva"); | ||
}; | ||
exports.FillHandle = ({ x = 0, y = 0, stroke, onMouseDown, }) => { | ||
if (x === 0 || y === 0) | ||
return null; | ||
const crossHairSize = 8; | ||
const crossHairBorderSize = 1; | ||
return (react_1.default.createElement("div", { style: { | ||
position: "absolute", | ||
left: x - crossHairSize / 2, | ||
top: y - crossHairSize / 2, | ||
width: crossHairSize, | ||
height: crossHairSize, | ||
border: `${crossHairBorderSize}px white solid`, | ||
background: stroke, | ||
cursor: "crosshair", | ||
}, onMouseDown: onMouseDown })); | ||
}; | ||
exports.createHTMLBox = ({ x = 0, y = 0, width = 0, height = 0, fill, stroke, strokeLeftColor = stroke, strokeTopColor = stroke, strokeRightColor = stroke, strokeBottomColor = stroke, strokeWidth = 0, strokeTopWidth = strokeWidth, strokeRightWidth = strokeWidth, strokeBottomWidth = strokeWidth, strokeLeftWidth = strokeWidth, key, strokeStyle = "solid", }) => { | ||
const commonProps = {}; | ||
width = width - Math.floor(strokeWidth); | ||
y = y - Math.ceil(strokeWidth / 2); | ||
const lines = [ | ||
react_1.default.createElement("div", Object.assign({ style: { | ||
position: "absolute", | ||
left: x, | ||
top: y, | ||
width: width, | ||
height: strokeTopWidth, | ||
borderWidth: 0, | ||
borderColor: strokeTopColor, | ||
borderTopWidth: strokeTopWidth, | ||
borderStyle: strokeStyle, | ||
}, key: "top" }, commonProps)), | ||
react_1.default.createElement("div", Object.assign({ style: { | ||
position: "absolute", | ||
left: x + width, | ||
top: y, | ||
width: strokeRightWidth, | ||
height: height, | ||
borderWidth: 0, | ||
borderColor: strokeRightColor, | ||
borderRightWidth: strokeRightWidth, | ||
borderStyle: strokeStyle, | ||
}, key: "right" }, commonProps)), | ||
, | ||
react_1.default.createElement("div", Object.assign({ style: { | ||
position: "absolute", | ||
left: x, | ||
top: y + height, | ||
width: width + strokeTopWidth, | ||
height: strokeBottomWidth, | ||
borderWidth: 0, | ||
borderColor: strokeBottomColor, | ||
borderBottomWidth: strokeBottomWidth, | ||
borderStyle: strokeStyle, | ||
}, key: "bottom" }, commonProps)), | ||
react_1.default.createElement("div", Object.assign({ style: { | ||
position: "absolute", | ||
left: x, | ||
top: y, | ||
width: strokeLeftWidth, | ||
height: height, | ||
borderWidth: 0, | ||
borderColor: strokeLeftColor, | ||
borderLeftWidth: strokeLeftWidth, | ||
borderStyle: strokeStyle, | ||
}, key: "left" }, commonProps)), | ||
]; | ||
return (react_1.default.createElement(react_1.default.Fragment, { key: key }, | ||
lines, | ||
fill && (react_1.default.createElement("div", Object.assign({ style: { | ||
position: "absolute", | ||
top: y, | ||
left: x, | ||
height, | ||
width, | ||
backgroundColor: fill, | ||
userSelect: "none", | ||
pointerEvents: "none", | ||
} }, commonProps))))); | ||
}; | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "react-konva-grid", | ||
"description": "Declarative React Canvas Grid primitive for Data table, Pivot table, Excel Worksheets", | ||
"version": "2.8.20", | ||
"version": "3.0.0", | ||
"main": "dist/index.js", | ||
@@ -62,3 +62,3 @@ "license": "MIT", | ||
"@testing-library/react-hooks": "^3.2.1", | ||
"@types/jest": "^25.2.3", | ||
"@types/jest": "^26.0.0", | ||
"@types/react": "^16.9.35", | ||
@@ -65,0 +65,0 @@ "@types/react-dom": "^16.9.8", |
@@ -34,2 +34,3 @@ ## Declarative Canvas Grid with React Konva | ||
- :rainbow: Full Theming and Context Support | ||
- :feet: Fill handle support | ||
- :muscle: Highly customizable using [react-konva](https://github.com/konvajs/react-konva/) | ||
@@ -36,0 +37,0 @@ |
@@ -46,2 +46,10 @@ import React, { useState, useCallback, useRef, useEffect } from "react"; | ||
persistantSelectionMode?: boolean; | ||
/** | ||
* onFill | ||
*/ | ||
onFill?: ( | ||
activeCell: CellInterface, | ||
selection: SelectionArea | null, | ||
selections: SelectionArea[] | ||
) => void; | ||
} | ||
@@ -78,2 +86,11 @@ | ||
onKeyDown: (e: React.KeyboardEvent<HTMLElement>) => void; | ||
/** | ||
* Mousedown event on fillhandle | ||
*/ | ||
onFillHandleMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void; | ||
/** | ||
* | ||
* Fill selections | ||
*/ | ||
fillSelection: SelectionArea | null; | ||
} | ||
@@ -97,2 +114,3 @@ | ||
allowDeselectSelection = true, | ||
onFill, | ||
} = options || {}; | ||
@@ -105,5 +123,9 @@ const [activeCell, setActiveCell] = useState<CellInterface | null>( | ||
); | ||
const [fillSelection, setFillSelection] = useState<SelectionArea | null>( | ||
null | ||
); | ||
const selectionStart = useRef<CellInterface | null>(null); | ||
const selectionEnd = useRef<CellInterface | null>(null); | ||
const isSelecting = useRef<boolean>(); | ||
const isFilling = useRef<boolean>(); | ||
const firstActiveCell = useRef<CellInterface | null>(null); | ||
@@ -155,3 +177,7 @@ /** | ||
/* Modify current selection */ | ||
const modifySelection = (coords: CellInterface, setInProgress?: boolean) => { | ||
const modifySelection = ( | ||
coords: CellInterface, | ||
setInProgress?: boolean, | ||
isFilling?: boolean | ||
) => { | ||
if (!selectionStart.current) return; | ||
@@ -170,3 +196,5 @@ | ||
if (!len) { | ||
return [{ bounds, inProgress: setInProgress ? true : false }]; | ||
return [ | ||
{ bounds, inProgress: setInProgress ? true : false, isFilling }, | ||
]; | ||
} | ||
@@ -179,2 +207,3 @@ return prevSelection.map((sel, i) => { | ||
inProgress: setInProgress ? true : false, | ||
isFilling, | ||
}; | ||
@@ -672,2 +701,64 @@ } | ||
const handleFillHandleMouseDown = useCallback( | ||
(e: React.MouseEvent<HTMLDivElement>) => { | ||
e.stopPropagation(); | ||
isFilling.current = true; | ||
document.addEventListener("mousemove", handleFillHandleMouseMove); | ||
document.addEventListener("mouseup", handleFillHandleMouseUp); | ||
}, | ||
[activeCell, selections] | ||
); | ||
const handleFillHandleMouseMove = useCallback( | ||
(e: globalThis.MouseEvent) => { | ||
/* Exit if user is not in selection mode */ | ||
if (!isFilling.current || !gridRef || !activeCell) return; | ||
const coords = gridRef.current.getCellCoordsFromOffset( | ||
e.clientX, | ||
e.clientY | ||
); | ||
const bounds = selectionFromStartEnd(activeCell, coords); | ||
if (!bounds) return; | ||
setFillSelection({ bounds }); | ||
gridRef.current.scrollToItem(coords); | ||
}, | ||
[activeCell, selections] | ||
); | ||
const handleFillHandleMouseUp = useCallback( | ||
(e: globalThis.MouseEvent) => { | ||
isFilling.current = false; | ||
/* Remove listener */ | ||
document.removeEventListener("mousemove", handleFillHandleMouseMove); | ||
document.removeEventListener("mouseup", handleFillHandleMouseUp); | ||
/* Exit early */ | ||
if (!gridRef) return; | ||
/* Update last selection */ | ||
let fillSelection: SelectionArea | null = null; | ||
const coords = gridRef.current.getCellCoordsFromOffset( | ||
e.clientX, | ||
e.clientY | ||
); | ||
setFillSelection((prev) => { | ||
fillSelection = prev; | ||
return null; | ||
}); | ||
if (!activeCell) return; | ||
onFill && onFill(activeCell, fillSelection, selections); | ||
/* Modify last selection */ | ||
modifySelection(coords); | ||
}, | ||
[activeCell, selections] | ||
); | ||
return { | ||
@@ -681,2 +772,4 @@ activeCell, | ||
setActiveCell: handleSetActiveCell, | ||
onFillHandleMouseDown: handleFillHandleMouseDown, | ||
fillSelection, | ||
}; | ||
@@ -683,0 +776,0 @@ }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
396844
8694
249
1
56