UNPKG

@adamson/react-crossword

Version:

A flexible, responsive, and easy-to-use crossword component for React apps

1,029 lines (865 loc) 35.9 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _immer = _interopRequireDefault(require("immer")); var _styledComponents = _interopRequireWildcard(require("styled-components")); var _lib = require("puz-reader/lib"); var _Cell = _interopRequireDefault(require("./Cell")); var _DirectionClues = _interopRequireDefault(require("./DirectionClues")); var _util = require("./util"); var _context = require("./context"); var _Toolbar = _interopRequireDefault(require("./Toolbar")); var _Cluebar = _interopRequireDefault(require("./Cluebar")); // TODO: make this a component property! var defaultStorageKey = 'guesses'; var defaultTheme = { columnBreakpoint: '768px', gridBackground: 'rgb(0,0,0)', cellBackground: 'rgb(255,255,255)', cellBorder: 'rgb(0,0,0)', textColor: 'rgb(0,0,0)', numberColor: 'rgba(0,0,0, 0.25)', focusBackground: 'rgb(255,255,0)', highlightBackground: 'rgb(255,255,204)' }; // eslint-disable-next-line var OuterWrapper = _styledComponents["default"].div.attrs(function (props) { return { className: "crossword" + (props.correct ? ' correct' : '') }; }).withConfig({ displayName: "Crossword__OuterWrapper", componentId: "sc-15inq77-0" })(["margin:0;padding:0;border:0;height:100vh;display:flex;flex-direction:row;@media (max-width:", "){flex-direction:column;}"], function (props) { return props.theme.columnBreakpoint; }); var GridWrapper = _styledComponents["default"].div.attrs(function () { return { className: 'grid' }; }).withConfig({ displayName: "Crossword__GridWrapper", componentId: "sc-15inq77-1" })(["overflow:hidden;flex:2 1 50%;"]); var CluesWrapper = _styledComponents["default"].div.attrs(function () { return { className: 'clues' }; }).withConfig({ displayName: "Crossword__CluesWrapper", componentId: "sc-15inq77-2" })(["overflow:hidden;padding:0.5em 1em 0.5em;flex:1 2 25%;@media (max-width:", "){margin-top:2em;}"], function (props) { return props.theme.columnBreakpoint; }); /** * The primary, and default, export from the react-crossword library, Crossword * renders an answer grid and clues, and manages data and user interaction. */ var Crossword = /*#__PURE__*/_react["default"].forwardRef(function (_ref, ref) { var crossword = _ref.crossword, onCorrect = _ref.onCorrect, onLoadedCorrect = _ref.onLoadedCorrect, onCrosswordCorrect = _ref.onCrosswordCorrect, onCellChange = _ref.onCellChange, useStorage = _ref.useStorage, theme = _ref.theme; var _useState = (0, _react.useState)(null), size = _useState[0], setSize = _useState[1]; var _useState2 = (0, _react.useState)(null), guessState = _useState2[0], setGuessState = _useState2[1]; var _useState3 = (0, _react.useState)(null), clueCorrectness = _useState3[0], setClueCorrectness = _useState3[1]; // const [gridData, setGridData] = useState(null); // const [clues, setClues] = useState(null); var _useState4 = (0, _react.useState)(false), focused = _useState4[0], setFocused = _useState4[1]; var _useState5 = (0, _react.useState)(0), focusedRow = _useState5[0], setFocusedRow = _useState5[1]; var _useState6 = (0, _react.useState)(0), focusedCol = _useState6[0], setFocusedCol = _useState6[1]; var _useState7 = (0, _react.useState)(_lib.Direction.Across), currentDirection = _useState7[0], setCurrentDirection = _useState7[1]; var _useState8 = (0, _react.useState)(1), currentNumber = _useState8[0], setCurrentNumber = _useState8[1]; var _useState9 = (0, _react.useState)(null), bulkChange = _useState9[0], setBulkChange = _useState9[1]; var _useState10 = (0, _react.useState)([]), checkQueue = _useState10[0], setCheckQueue = _useState10[1]; var _useState11 = (0, _react.useState)(false), crosswordCorrect = _useState11[0], setCrosswordCorrect = _useState11[1]; var inputRef = (0, _react.useRef)(); var contextTheme = (0, _react.useContext)(_styledComponents.ThemeContext); var getCellData = (0, _react.useCallback)(function (row, col) { if (row >= 0 && row < size && col >= 0 && col < size) { return crossword.squares[row][col]; } // fake cellData to represent "out of bounds" return null; }, [size]); var getSquareGuessData = (0, _react.useCallback)(function (row, col) { if (row >= 0 && row < size && col >= 0 && col < size) { return guessState[row][col]; } return null; }, [guessState]); var setCellMark = (0, _react.useCallback)(function (row, col, marked) { setGuessState((0, _immer["default"])(function (draft) { draft[row][col].marked = marked; })); }, [getCellData, guessState]); var setCellCharacter = (0, _react.useCallback)(function (row, col, _char) { var cell = getCellData(row, col); if (!cell) { return; } // If the character is already the cell's guess, there's nothing to do. if (cell.value === _char) { return; } // update the gridData with the guess setGuessState((0, _immer["default"])(function (draft) { draft[row][col].value = _char; })); // push the row/col for checking! setCheckQueue((0, _immer["default"])(function (draft) { draft.push({ row: row, col: col }); })); if (onCellChange) { onCellChange(row, col, _char); } }, [getCellData, onCellChange]); // TODO(adamson) is there any advantage to batching calls to set*/produce*? var fillCurrentSquare = (0, _react.useCallback)(function () { var square = crossword.squares[focusedRow][focusedCol]; setCellCharacter(focusedRow, focusedCol, square.answer); }, [crossword, focusedRow, focusedCol, setCellCharacter]); // TODO(adamson) is there any advantage to batching calls to set*/produce*? var fillCurrentClue = (0, _react.useCallback)(function () { var clue = crossword.getClue(currentDirection, currentNumber); var squaresToFill = clue.squares; squaresToFill.forEach(function (square) { return setCellCharacter(square.row, square.column, square.answer); }); }, [currentDirection, currentNumber, setCellCharacter]); var markCurrentSquare = (0, _react.useCallback)(function () { setCellMark(focusedRow, focusedCol, true); }, [focusedRow, focusedCol, setCellMark]); // TODO(adamson) is there any advantage to batching calls to set*/produce*? var markCurrentClue = (0, _react.useCallback)(function () { var clue = crossword.getClue(currentDirection, currentNumber); clue.squares.forEach(function (square) { return setCellMark(square.row, square.column, true); }); }, [currentDirection, currentNumber, setCellMark]); var notifyCorrect = (0, _react.useCallback)(function (direction, number, answer) { if (onCorrect) { // We *used* to need a timeout workaround to ensure this happened // *after* the state had updated and the DOM rendered.... do we still? onCorrect(direction, number, answer); // For future reference, the call looked like: // // setTimeout(() => { // window.requestAnimationFrame(() => { // onCorrect(direction, number, answer); // }); // }); } }, [onCorrect]); // TODO var checkCorrectness = (0, _react.useCallback)(function (row, col) { var cell = getCellData(row, col); if (!cell) { return; } var cellGuess = getSquareGuessData(cell.row, cell.column); // check all the cells for both across and down answers that use this // cell _util.bothDirections.forEach(function (direction) { var clue = crossword.getClue(direction, cell.getClueNumber(direction)); // We start by looking at the current cell... if it's not correct, we // don't need to check anything else! var correct = cellGuess.value === cell.answer; if (correct) { clue.squares.forEach(function (square) { var squareGuess = getSquareGuessData(square.row, square.column); if (squareGuess.value !== square.answer) { correct = false; } }); } // update the clue state setClueCorrectness((0, _immer["default"])(function (draft) { draft[clue.direction][clue.number] = correct; })); if (correct) { notifyCorrect(clue.direction, clue.number, clue.answer); } }); }, [getCellData, getSquareGuessData]); // Any time the checkQueue changes, call checkCorrectness! (0, _react.useEffect)(function () { if (checkQueue.length === 0) { return; } checkQueue.forEach(function (_ref2) { var row = _ref2.row, col = _ref2.col; return checkCorrectness(row, col); }); setCheckQueue([]); }, [checkQueue, checkCorrectness]); // Any time the clues change, determine if they are all correct or not. (0, _react.useEffect)(function () { var allCorrect = clueCorrectness && _util.bothDirections.every(function (direction) { return Object.entries(clueCorrectness[direction]).every(function (_ref3) { var num = _ref3[0], clue = _ref3[1]; return clue; }); }); setCrosswordCorrect(allCorrect); }, [clueCorrectness, setCrosswordCorrect]); // Let the consumer know everything's correct (or not) if they've asked to // be informed. (0, _react.useEffect)(function () { if (onCrosswordCorrect) { onCrosswordCorrect(crosswordCorrect); } }, [crosswordCorrect, onCrosswordCorrect]); // focus and movement var _focus = (0, _react.useCallback)(function () { if (inputRef.current) { inputRef.current.focus(); setFocused(true); } }, []); var moveTo = (0, _react.useCallback)(function (row, col, directionOverride) { var direction = directionOverride != null ? directionOverride : currentDirection; var candidate = getCellData(row, col); if (!candidate) { return false; } if (candidate.getClueNumber(direction) === null) { direction = (0, _util.otherDirection)(direction); } var clueNumber = candidate.getClueNumber(direction); console.debug("Moving to square (" + row + "," + col + "), clue " + clueNumber + " " + direction); setFocusedRow(row); setFocusedCol(col); setCurrentDirection(direction); setCurrentNumber(clueNumber); return candidate; }, [getCellData, currentDirection]); var moveRelative = (0, _react.useCallback)(function (dRow, dCol) { // We expect *only* one of dRow or dCol to have a non-zero value, and // that's the direction we will "prefer". If *both* are set (or zero), // we don't change the direction. var direction; if (dRow !== 0 && dCol === 0) { direction = _lib.Direction.Down; } else if (dRow === 0 && dCol !== 0) { direction = _lib.Direction.Across; } var cell = moveTo(focusedRow + dRow, focusedCol + dCol, direction); return cell; }, [focusedRow, focusedCol, moveTo]); var moveForward = (0, _react.useCallback)(function () { var across = (0, _util.isAcross)(currentDirection); moveRelative(across ? 0 : 1, across ? 1 : 0); }, [currentDirection, moveRelative]); var moveBackward = (0, _react.useCallback)(function () { var across = (0, _util.isAcross)(currentDirection); moveRelative(across ? 0 : -1, across ? -1 : 0); }, [currentDirection, moveRelative]); var getNextOpenSquareInClue = (0, _react.useCallback)(function (clue, startingSquare) { if (startingSquare === void 0) { startingSquare = null; } var offset = startingSquare ? clue.squares.indexOf(startingSquare) + 1 : 0; // Don't consider the starting square if one is present for (var i = 0; i < clue.squares.length; i++) { var square = clue.squares[(i + offset) % clue.squares.length]; if (guessState[square.row][square.column].value === '') { return square; } } return null; }, [crossword, guessState]); var getNextClue = (0, _react.useCallback)(function (clue) { var direction = clue.direction, startingNumber = clue.number; var nextClue = null; var allDirections = Object.values(_lib.Direction); var offset = allDirections.indexOf(direction); for (var i = 0; i < allDirections.length; i++) { direction = allDirections[(i + offset) % allDirections.length]; var directionClues = crossword.cluesForDirection(direction); var clueIndex = startingNumber !== null ? directionClues.indexOf(clue) : -1; if (clueIndex + 1 < directionClues.length) { nextClue = directionClues[clueIndex + 1]; break; } startingNumber = null; } return nextClue; }, [crossword]); var moveToNextOpenSquare = (0, _react.useCallback)(function () { var startClue = crossword.getClue(currentDirection, currentNumber); var startSquare = crossword.squares[focusedRow][focusedCol]; var currentClue = startClue; var nextSquare = getNextOpenSquareInClue(currentClue, startSquare); // We need to guard against the second condition because if the starting square is open, we will find // it when we change directions and scan all clues while (nextSquare === null || nextSquare === startSquare) { currentClue = getNextClue(currentClue); if (currentClue === startClue) { return null; } nextSquare = getNextOpenSquareInClue(currentClue); } moveTo(nextSquare.row, nextSquare.column, currentClue.direction); return nextSquare; }, [crossword, getNextClue, getNextOpenSquareInClue, focusedRow, focusedCol, currentDirection, currentNumber]); // TODO(adamson) make these actually do the right thing // keyboard handling var handleSingleCharacter = (0, _react.useCallback)(function (_char2) { setCellCharacter(focusedRow, focusedCol, _char2.toUpperCase()); // Clear any mark (checking) if applicable setCellMark(focusedRow, focusedCol, false); // If there's an open square, move to it. Otherwise, go to the next square if (!moveToNextOpenSquare()) { moveForward(); } }, [focusedRow, focusedCol, setCellCharacter, moveToNextOpenSquare]); // We use the keydown event for control/arrow keys, but not for textual // input, because it's hard to suss out when a key is "regular" or not. var handleInputKeyDown = (0, _react.useCallback)(function (event) { // if ctrl, alt, or meta are down, ignore the event (let it bubble) // if (event.ctrlKey || event.altKey || event.metaKey) { // return; // } var preventDefault = true; var key = event.key; // console.log('CROSSWORD KEYDOWN', event.key); if (event.shiftKey && event.ctrlKey) { switch (key) { case 'S': markCurrentSquare(); break; case 'C': markCurrentClue(); break; default: break; } } else if (event.shiftKey) { switch (key) { case 'S': fillCurrentSquare(); break; case 'C': fillCurrentClue(); break; default: break; } } else { // FUTURE: should we "jump" over black space? That might help some for // keyboard users. switch (key) { case 'ArrowUp': moveRelative(-1, 0); break; case 'ArrowDown': moveRelative(1, 0); break; case 'ArrowLeft': moveRelative(0, -1); break; case 'ArrowRight': moveRelative(0, 1); break; case ' ': // treat space like tab? case 'Tab': { var other = (0, _util.otherDirection)(currentDirection); var cellData = getCellData(focusedRow, focusedCol); if (cellData[other]) { setCurrentDirection(other); setCurrentNumber(cellData[other]); } break; } // Backspace: delete the current cell, and move to the previous cell // Delete: delete the current cell, but don't move case 'Backspace': case 'Delete': { setCellCharacter(focusedRow, focusedCol, ''); if (key === 'Backspace') { moveBackward(); } break; } case 'Home': { // move to beginning/end of this entry? var clue = crossword.getClue(currentDirection, currentNumber); var firstSquare = clue.squares[0]; moveTo(firstSquare.row, firstSquare.column); break; } case 'End': { // move to beginning/end of this entry? var _clue = crossword.getClue(currentDirection, currentNumber); var lastSquare = _clue.squares[-1]; moveTo(lastSquare.row, lastSquare.column); break; } default: // It would be nice to handle "regular" characters with onInput, but // that is still experimental, so we can't count on it. Instead, we // assume that only "length 1" values are regular. if (key.length !== 1) { preventDefault = false; break; } handleSingleCharacter(key); break; } } if (preventDefault) { event.preventDefault(); } }, [crossword, focusedRow, focusedCol, currentDirection, currentNumber, getCellData, setCellCharacter, fillCurrentClue, fillCurrentSquare, markCurrentClue, markCurrentSquare, moveRelative, moveTo, moveToNextOpenSquare]); var handleInputChange = (0, _react.useCallback)(function (event) { event.preventDefault(); setBulkChange(event.target.value); }, []); (0, _react.useEffect)(function () { if (!bulkChange) { return; } // handle bulkChange by updating a character at a time (this lets us // leverage the existing character-entry logic). handleSingleCharacter(bulkChange[0]); setBulkChange(bulkChange.length === 1 ? null : bulkChange.substring(1)); }, [bulkChange, handleSingleCharacter]); // When the data changes, recalculate the gridData, size, etc. (0, _react.useEffect)(function () { // eslint-disable-next-line no-shadow var _createGridData = (0, _util.createGridData)(crossword), size = _createGridData.size, guessState = _createGridData.guessState, clueCorrectness = _createGridData.clueCorrectness; var loadedCorrect; if (useStorage) { (0, _util.loadGuesses)(crossword, guessState, defaultStorageKey); loadedCorrect = (0, _util.findCorrectAnswers)(crossword, guessState); loadedCorrect.forEach(function (_ref4) { var direction = _ref4[0], num = _ref4[1]; clueCorrectness[direction][num] = true; }); } setSize(size); setGuessState(guessState); setClueCorrectness(clueCorrectness); // Should we start with 1-across highlighted/focused? // TODO: track input-field focus so we don't draw highlight when we're not // really focused, *and* use first actual clue (whether across or down?) setFocusedRow(0); setFocusedCol(0); setCurrentDirection(_lib.Direction.Across); setCurrentNumber(1); setBulkChange(null); // trigger any "loaded correct" guesses... if (loadedCorrect && loadedCorrect.length > 0 && onLoadedCorrect) { onLoadedCorrect(loadedCorrect); } }, [crossword, onLoadedCorrect, useStorage]); (0, _react.useEffect)(function () { if (guessState === null || !useStorage) { return; } (0, _util.saveGuesses)(crossword, guessState, defaultStorageKey); }, [crossword, guessState, useStorage]); // TODO var handleCellClick = (0, _react.useCallback)(function (guessData, square) { var row = square.row, column = square.column; var other = (0, _util.otherDirection)(currentDirection); // should this use moveTo? setFocusedRow(row); setFocusedCol(column); var direction = currentDirection; // We switch to the "other" direction if (a) the current direction isn't // available in the clicked cell, or (b) we're already focused and the // clicked cell is the focused cell, *and* the other direction is // available. if (!square.getClueNumber(currentDirection) || focused && row === focusedRow && column === focusedCol && square.getClueNumber(other)) { setCurrentDirection(other); direction = other; } setCurrentNumber(square.getClueNumber(direction)); _focus(); }, [focused, focusedRow, focusedCol, currentDirection, _focus]); var handleInputClick = (0, _react.useCallback)(function () { // *don't* event.preventDefault(), because we want the input to actually // take focus // Like general cell-clicks, cliking on the input can change direction. // Unlike cell clicks, we *know* we're clicking on the already-focused // cell! var other = (0, _util.otherDirection)(currentDirection); var cellData = getCellData(focusedRow, focusedCol); var direction = currentDirection; if (focused && cellData.getClueNumber(other)) { setCurrentDirection(other); direction = other; } setCurrentNumber(cellData.getClueNumber(direction)); _focus(); }, [currentDirection, focusedRow, focusedCol, getCellData, _focus]); var handleClueSelected = (0, _react.useCallback)(function (direction, number) { var info = crossword.getClue(direction, number); // TODO: sanity-check info? moveTo(info.startSquare.row, info.startSquare.column, direction); _focus(); }, [crossword, moveTo, _focus]); // expose some imperative methods (0, _react.useImperativeHandle)(ref, function () { return { /** * Sets focus to the crossword component. */ focus: function focus() { _focus(); }, /** * Resets the entire crossword; clearing all answers in the grid and * also any persisted data. */ reset: function reset() { setGuessState((0, _immer["default"])(function (draft) { draft.forEach(function (rowData) { rowData.forEach(function (cellData) { if (cellData) { cellData.value = ''; } }); }); })); setClueCorrectness((0, _immer["default"])(function (draft) { _util.bothDirections.forEach(function (direction) { // TODO I think this is totally fucked draft[direction].forEach(function (clueInfo, num) { // TODO(adamson) should we be deleting or setting to false? draft[direction][num] = false; }); }); })); if (useStorage) { (0, _util.clearGuesses)(defaultStorageKey); } }, // fillCurrentSquare: () => { // // }, // fillCurrentClue: () => { // const clue = crossword.clue(currentDirection, currentNumber); // const squaresToFill = clue.squares; // setGuessState( // produce((draft) => { // squaresToFill.forEach((square) => { // draft[square.row][square.column] = square.answer; // }); // }) // ); // // setClueCorrectness( // produce((draft) => { // draft[currentDirection][currentNumber] = true; // }) // ); // // // trigger onLoadedCorrect with every clue! // if (onLoadedCorrect) { // const loadedCorrect = []; // loadedCorrect.push([currentDirection, currentNumber, clue]); // onLoadedCorrect(loadedCorrect); // } // }, /** * Fills all the answers in the grid and calls the `onLoadedCorrect` * callback with _**every**_ answer. */ fillAllAnswers: function fillAllAnswers() { setGuessState((0, _immer["default"])(function (draft) { // TODO(adamson) this is slow unless we add the answer as well as the guess to the guess state draft.forEach(function (rowData, i) { rowData.forEach(function (guessData, j) { if (guessData) { var square = crossword.squares[i][j]; guessData.value = square.answer; } }); }); })); setClueCorrectness((0, _immer["default"])(function (draft) { // TODO I think this is totally fucked _util.bothDirections.forEach(function (direction) { // TODO I think this is totally fucked draft[direction].forEach(function (clueInfo) { clueInfo.correct = true; }); }); })); // trigger onLoadedCorrect with every clue! if (onLoadedCorrect) { var loadedCorrect = []; _util.bothDirections.forEach(function (direction) { Object.entries(crossword.cluesForDirection(direction)).forEach(function (clue) { loadedCorrect.push([direction, clue.number, clue.answer]); }); }); onLoadedCorrect(loadedCorrect); } }, /** * Returns whether the crossword is entirely correct or not. * * @since 2.2.0 */ isCrosswordCorrect: function isCrosswordCorrect() { return crosswordCorrect; } }; }, [crossword, onLoadedCorrect, useStorage, _focus, crosswordCorrect]); // constants for rendering... // We have several properties that we bundle together as context for the // cells, rather than have them as independent properties. (Or should they // stay separate? Or be passed as "spread" values?) var cellSize = 100 / size; var cellPadding = 0.125; var cellInner = cellSize - cellPadding * 2; var cellHalf = cellSize / 2; var fontSize = cellInner * 0.7; console.log("cellSize: " + cellSize); console.log("cellInner: " + cellInner); console.log("cellHalf: " + cellHalf); // The final theme is the merger of three values: the "theme" property // passed to the component (which takes precedence), any values from // ThemeContext, and finally the "defaultTheme" values fill in for any // needed ones that are missing. (We create this in standard last-one-wins // order in Javascript, of course.) var finalTheme = (0, _extends2["default"])({}, defaultTheme, contextTheme, theme); // REVIEW: do we want to recalc this all the time, or cache in state? var cells = []; if (guessState) { guessState.forEach(function (rowData, row) { rowData.forEach(function (squareGuessState, col) { var square = crossword.squares[row][col]; if (!square) { return; } var mark = null; if (squareGuessState.marked && squareGuessState.value !== '') { if (squareGuessState.value === square.answer) { mark = _util.MarkStatus.CORRECT; } else { mark = _util.MarkStatus.INCORRECT; } } cells.push( /*#__PURE__*/_react["default"].createElement(_Cell["default"] // eslint-disable-next-line react/no-array-index-key , { key: "R" + row + "C" + col, guessData: squareGuessState, square: square, focus: focused && row === focusedRow && col === focusedCol, highlight: focused && currentNumber && square.getClueNumber(currentDirection) === currentNumber, mark: mark, onClick: handleCellClick })); }); }); } var svgGridRef = (0, _react.useRef)(); var svgGrid = /*#__PURE__*/_react["default"].createElement("svg", { ref: svgGridRef, preserveAspectRatio: "xMinYMin", height: "100%", viewBox: "0 0 100 100" }, /*#__PURE__*/_react["default"].createElement("rect", { x: 0, y: 0, width: 100, height: 100, fill: finalTheme.gridBackground }), cells); var svgInput = /*#__PURE__*/_react["default"].createElement("input", { ref: inputRef, "aria-label": "crossword-input", type: "text", onClick: handleInputClick, onKeyDown: handleInputKeyDown, onChange: handleInputChange, value: "" // onInput={this.handleInput} , autoComplete: "off", spellCheck: "false", autoCorrect: "off", style: { position: 'absolute', // In order to ensure the top/left positioning makes sense, // there is an absolutely-positioned <div> with no // margin/padding that we *don't* expose to consumers. This // keeps the math much more reliable. (But we're still // seeing a slight vertical deviation towards the bottom of // the grid! The "* 0.995" seems to help.) top: "calc(" + focusedRow * cellSize + "% + 2px)", left: "calc(" + focusedCol * cellSize + "% + 2px)", width: "calc(" + cellSize + "% - 4px)", height: "calc(" + cellSize + "% - 4px)", fontSize: fontSize * 6 + "px", // waaay too small...? textAlign: 'center', textAnchor: 'middle', backgroundColor: 'transparent', caretColor: 'transparent', margin: 0, padding: 0, border: 0, cursor: 'default' } }); var _useContainerDimensio = function useContainerDimensionsSvg(myRef) { var getDimensions = function () { var boundingRect = myRef.current.getBoundingClientRect(); return { width: boundingRect.width, height: boundingRect.height }; }; var _useState12 = (0, _react.useState)({ width: 0, height: 0 }), dimensions = _useState12[0], setDimensions = _useState12[1]; (0, _react.useEffect)(function () { var handleResize = function () { setDimensions(getDimensions()); }; if (myRef.current) { setDimensions(getDimensions()); } window.addEventListener('resize', handleResize); return function () { window.removeEventListener('resize', handleResize); }; }, [myRef]); return dimensions; }(svgGridRef), gridWidth = _useContainerDimensio.width, gridHeight = _useContainerDimensio.height; var currentClue = crossword.getClue(currentDirection, currentNumber); return /*#__PURE__*/_react["default"].createElement(_context.CrosswordContext.Provider, { value: { focused: focused, selectedDirection: currentDirection, selectedNumber: currentNumber, onClueSelected: handleClueSelected } }, /*#__PURE__*/_react["default"].createElement(_context.CrosswordSizeContext.Provider, { value: { cellSize: cellSize, cellPadding: cellPadding, cellInner: cellInner, cellHalf: cellHalf, fontSize: fontSize } }, /*#__PURE__*/_react["default"].createElement(_styledComponents.ThemeProvider, { theme: finalTheme }, /*#__PURE__*/_react["default"].createElement(OuterWrapper, { correct: crosswordCorrect }, /*#__PURE__*/_react["default"].createElement("div", { style: { display: 'flex', 'flex-direction': 'column' } }, /*#__PURE__*/_react["default"].createElement(_Toolbar["default"], { onClickCheckSquare: markCurrentSquare, onClickCheckClue: markCurrentClue, onClickCheckGrid: markCurrentClue, onClickRevealSquare: fillCurrentSquare, onClickRevealClue: fillCurrentClue, onClickRevealGrid: fillCurrentClue }), /*#__PURE__*/_react["default"].createElement(GridWrapper, null, /*#__PURE__*/_react["default"].createElement("div", { style: { margin: 0, padding: 0, position: 'relative', height: '100%' } }, svgGrid, svgInput)), /*#__PURE__*/_react["default"].createElement("div", { style: { width: gridWidth, height: '10%' } }, /*#__PURE__*/_react["default"].createElement(_Cluebar["default"], { clue: currentClue.text, onClickBack: moveToNextOpenSquare, onClickNext: moveToNextOpenSquare }))), /*#__PURE__*/_react["default"].createElement(CluesWrapper, null, crossword && clueCorrectness && _util.bothDirections.map(function (direction) { return /*#__PURE__*/_react["default"].createElement(_DirectionClues["default"], { key: direction, direction: direction, clues: crossword.cluesForDirection(direction), correctness: clueCorrectness[direction] }); })))))); }); Crossword.displayName = 'Crossword'; // const clueShape = PropTypes.shape({ // clue: PropTypes.string.isRequired, // answer: PropTypes.string.isRequired, // row: PropTypes.number.isRequired, // col: PropTypes.number.isRequired, // }); process.env.NODE_ENV !== "production" ? Crossword.propTypes = { /** clue/answer data; see <a href="#cluedata-format">Clue/data format</a> for details. */ crossword: _propTypes["default"].instanceOf(_lib.Crossword).isRequired, /** presentation values for the crossword; these override any values coming from a parent ThemeProvider context. */ theme: _propTypes["default"].shape({ /** browser-width at which the clues go from showing beneath the grid to showing beside the grid */ columnBreakpoint: _propTypes["default"].string, /** overall background color (fill) for the crossword grid; can be `'transparent'` to show through a page background image */ gridBackground: _propTypes["default"].string, /** background for an answer cell */ cellBackground: _propTypes["default"].string, /** border for an answer cell */ cellBorder: _propTypes["default"].string, /** color for answer text (entered by the player) */ textColor: _propTypes["default"].string, /** color for the across/down numbers in the grid */ numberColor: _propTypes["default"].string, /** background color for the cell with focus, the one that the player is typing into */ focusBackground: _propTypes["default"].string, /** background color for the cells in the answer the player is working on, * helps indicate in which direction focus will be moving; also used as a * background on the active clue */ highlightBackground: _propTypes["default"].string }), /** whether to use browser storage to persist the player's work-in-progress */ useStorage: _propTypes["default"].bool, /** callback function that fires when a player answers a clue correctly; called with `(direction, number, answer)` arguments, where `direction` is `'across'` or `'down'`, `number` is the clue number as text (like `'1'`), and `answer` is the answer itself */ onCorrect: _propTypes["default"].func, /** callback function that's called when a crossword is loaded, to batch up correct answers loaded from storage; passed an array of the same values that `onCorrect` would recieve */ onLoadedCorrect: _propTypes["default"].func, /** callback function that's called when the overall crossword is completely correct (or not) */ onCrosswordCorrect: _propTypes["default"].func, /** * callback function called when a cell changes (e.g. when the user types a * letter); called with `(row, col, char)` arguments, where the `row` and * `column` are the 0-based position of the cell, and `char` is the character * typed (already massaged into upper-case) * * @since 2.1.0 */ onCellChange: _propTypes["default"].func } : void 0; Crossword.defaultProps = { theme: null, useStorage: true, // useStorage: false, onCorrect: null, onLoadedCorrect: null, onCrosswordCorrect: null, onCellChange: null }; var _default = Crossword; exports["default"] = _default;