From 76aaae5b5f5c02de1d1a365b7407d99789467650 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Wed, 6 Nov 2024 13:03:56 +0100 Subject: [PATCH 1/2] Deploy documentation with native HTML+JS demos (#1441) (#1454) * Merge hotfix to develop (#1431) Change publish-docs workflow to use Node 20 instead of 22 due to the temporary npm issue * Merge hotfix to develop (#1432) * Change publish-docs workflow to use Node 20 instead of 22 due to the temporary npm issue (#1424) * Set NODE_OPTIONS=--openssl-legacy-provider for the typedoc command * Merge hotfix to develop (#1433) * Change publish-docs workflow to use Node 20 instead of 22 due to the temporary npm issue (#1424) * Hotfix: Set NODE_OPTIONS=--openssl-legacy-provider for the typedoc command (#1425) Set NODE_OPTIONS=--openssl-legacy-provider for the typedoc command * Revert the publish-docs workflow to Node 16 * Add build-docs workflow (#1434) * Add build-docs workflow * Add environment variables necessary to build the docs with Node 20 * Use Node 20 in all the build/test workflows (#1435) * Remove license comments from the UMD build (#1437) * Install terser plugin explicite to enable extensive customization * Remove license comment from the UMD builds using string-replace-loader webpack plugin * Test for the redundant license comments in the UMD build inside the verify:umd script * Update build badge in readme to show the build workflow status instead of test (#1443) * Update build badge to show the build workflow status instead of test * Native demos in the docs (#1447) * Add markdown-it plugins * Make HTML+CSS preview work * Make some of the buttons work * Add edit-in-stackblitz button * Make the entire runtime of the demo work (including js) * Add real HF demo. * Make HyperFormula import work in runtime of the docs examples * Add script code-examples-generator * Style stackblitz button * Style demo internal buttons * Remove unused glue-code * Remove not needed vuepress plugins * Fix linter error * Reformat demo/example1.ts file * Add advanced-usage demo * Change the framework demos in the docs to use Codesandbox iframe instead of Stackblitz * Add basic-operations demo * Add basic-usage demo * Add batch-operations demo * Add demo clipboard-operations * Changed the custom-functions demo to use Codesandbox instead of Stackblitz * Add date-time demo * Add demo i18n * Add localizing-functions guide * Add named-expressions demo * Add sorting-data demo * Add undo-redo demo * Fix lint errors * Disable linter for the docs examples * Tweak the styling of demo example * Fix row and column counting in the docs examples * Adjust css of basic-usage demo * Adjust style of advanced-usage demo * Fix styles for basic-operations demo * Fix styles for batch-operations demo * Fix styles for sorting demo * Fix height of the table cell in the demos * Adjust text decoration of the summary row in the i18n demo * Fix styles for basic-operations demo * Adjust styles for the inputs * Disable logging HF version to console in the native examples embedded in the docs * Update styles for the open-in-stackblitz button * Fix broken favicon link * Fix examples.js script to handle newline characters in both unix and windows styles * Reduce space between buttons and table in the undo-redo demo * Fix style for checkbox in the batch-operations demo --- .config/webpack/base.js | 9 + .eslintignore | 3 + docs/.vuepress/components/ScriptLoader.vue | 39 + docs/.vuepress/config.js | 30 +- .../plugins/examples/code-builder.js | 32 + docs/.vuepress/plugins/examples/examples.js | 119 ++ docs/.vuepress/plugins/examples/stackblitz.js | 77 + .../markdown-it-include-code-snippet/index.js | 105 ++ .../.vuepress/public/favicon/site.webmanifest | 4 +- docs/.vuepress/styles/index.styl | 58 +- docs/.vuepress/styles/palette.styl | 4 + docs/code-examples-generator.sh | 108 ++ docs/examples/advanced-usage/example1.css | 181 +++ docs/examples/advanced-usage/example1.html | 49 + docs/examples/advanced-usage/example1.js | 129 ++ docs/examples/advanced-usage/example1.ts | 131 ++ docs/examples/basic-operations/example1.css | 228 +++ docs/examples/basic-operations/example1.html | 57 + docs/examples/basic-operations/example1.js | 461 ++++++ docs/examples/basic-operations/example1.ts | 481 ++++++ docs/examples/basic-usage/example1.css | 181 +++ docs/examples/basic-usage/example1.html | 17 + docs/examples/basic-usage/example1.js | 87 ++ docs/examples/basic-usage/example1.ts | 90 ++ docs/examples/batch-operations/example1.css | 181 +++ docs/examples/batch-operations/example1.html | 39 + docs/examples/batch-operations/example1.js | 164 ++ docs/examples/batch-operations/example1.ts | 171 +++ .../clipboard-operations/example1.css | 181 +++ .../clipboard-operations/example1.html | 37 + .../examples/clipboard-operations/example1.js | 130 ++ .../examples/clipboard-operations/example1.ts | 134 ++ docs/examples/date-time/example1.css | 181 +++ docs/examples/date-time/example1.html | 23 + docs/examples/date-time/example1.js | 154 ++ docs/examples/date-time/example1.ts | 157 ++ docs/examples/demo/example1.css | 181 +++ docs/examples/demo/example1.html | 27 + docs/examples/demo/example1.js | 132 ++ docs/examples/demo/example1.ts | 137 ++ docs/examples/eslintrc.examples.js | 60 + docs/examples/i18n/example1.css | 181 +++ docs/examples/i18n/example1.html | 27 + docs/examples/i18n/example1.js | 212 +++ docs/examples/i18n/example1.ts | 217 +++ .../localizing-functions/example1.css | 181 +++ .../localizing-functions/example1.html | 27 + .../examples/localizing-functions/example1.js | 148 ++ .../examples/localizing-functions/example1.ts | 155 ++ docs/examples/named-expressions/example1.css | 181 +++ docs/examples/named-expressions/example1.html | 19 + docs/examples/named-expressions/example1.js | 121 ++ docs/examples/named-expressions/example1.ts | 125 ++ docs/examples/sorting-data/example1.css | 181 +++ docs/examples/sorting-data/example1.html | 18 + docs/examples/sorting-data/example1.js | 148 ++ docs/examples/sorting-data/example1.ts | 154 ++ docs/examples/undo-redo/example1.css | 181 +++ docs/examples/undo-redo/example1.html | 22 + docs/examples/undo-redo/example1.js | 138 ++ docs/examples/undo-redo/example1.ts | 141 ++ docs/guide/advanced-usage.md | 15 +- docs/guide/basic-operations.md | 18 +- docs/guide/basic-usage.md | 15 +- docs/guide/batch-operations.md | 15 +- docs/guide/clipboard-operations.md | 15 +- docs/guide/custom-functions.md | 7 +- docs/guide/date-and-time-handling.md | 15 +- docs/guide/demo.md | 15 +- docs/guide/i18n-features.md | 15 +- docs/guide/integration-with-angular.md | 7 +- docs/guide/integration-with-react.md | 7 +- docs/guide/integration-with-svelte.md | 7 +- docs/guide/integration-with-vue.md | 9 +- docs/guide/localizing-functions.md | 15 +- docs/guide/named-expressions.md | 15 +- docs/guide/sorting-data.md | 15 +- docs/guide/undo-redo.md | 15 +- package-lock.json | 1327 +++++++++++------ package.json | 15 + script/check-file.js | 8 + 81 files changed, 8405 insertions(+), 511 deletions(-) create mode 100644 docs/.vuepress/components/ScriptLoader.vue create mode 100644 docs/.vuepress/plugins/examples/code-builder.js create mode 100644 docs/.vuepress/plugins/examples/examples.js create mode 100644 docs/.vuepress/plugins/examples/stackblitz.js create mode 100644 docs/.vuepress/plugins/markdown-it-include-code-snippet/index.js create mode 100644 docs/code-examples-generator.sh create mode 100644 docs/examples/advanced-usage/example1.css create mode 100644 docs/examples/advanced-usage/example1.html create mode 100644 docs/examples/advanced-usage/example1.js create mode 100644 docs/examples/advanced-usage/example1.ts create mode 100644 docs/examples/basic-operations/example1.css create mode 100644 docs/examples/basic-operations/example1.html create mode 100644 docs/examples/basic-operations/example1.js create mode 100644 docs/examples/basic-operations/example1.ts create mode 100644 docs/examples/basic-usage/example1.css create mode 100644 docs/examples/basic-usage/example1.html create mode 100644 docs/examples/basic-usage/example1.js create mode 100644 docs/examples/basic-usage/example1.ts create mode 100644 docs/examples/batch-operations/example1.css create mode 100644 docs/examples/batch-operations/example1.html create mode 100644 docs/examples/batch-operations/example1.js create mode 100644 docs/examples/batch-operations/example1.ts create mode 100644 docs/examples/clipboard-operations/example1.css create mode 100644 docs/examples/clipboard-operations/example1.html create mode 100644 docs/examples/clipboard-operations/example1.js create mode 100644 docs/examples/clipboard-operations/example1.ts create mode 100644 docs/examples/date-time/example1.css create mode 100644 docs/examples/date-time/example1.html create mode 100644 docs/examples/date-time/example1.js create mode 100644 docs/examples/date-time/example1.ts create mode 100644 docs/examples/demo/example1.css create mode 100644 docs/examples/demo/example1.html create mode 100644 docs/examples/demo/example1.js create mode 100644 docs/examples/demo/example1.ts create mode 100644 docs/examples/eslintrc.examples.js create mode 100644 docs/examples/i18n/example1.css create mode 100644 docs/examples/i18n/example1.html create mode 100644 docs/examples/i18n/example1.js create mode 100644 docs/examples/i18n/example1.ts create mode 100644 docs/examples/localizing-functions/example1.css create mode 100644 docs/examples/localizing-functions/example1.html create mode 100644 docs/examples/localizing-functions/example1.js create mode 100644 docs/examples/localizing-functions/example1.ts create mode 100644 docs/examples/named-expressions/example1.css create mode 100644 docs/examples/named-expressions/example1.html create mode 100644 docs/examples/named-expressions/example1.js create mode 100644 docs/examples/named-expressions/example1.ts create mode 100644 docs/examples/sorting-data/example1.css create mode 100644 docs/examples/sorting-data/example1.html create mode 100644 docs/examples/sorting-data/example1.js create mode 100644 docs/examples/sorting-data/example1.ts create mode 100644 docs/examples/undo-redo/example1.css create mode 100644 docs/examples/undo-redo/example1.html create mode 100644 docs/examples/undo-redo/example1.js create mode 100644 docs/examples/undo-redo/example1.ts diff --git a/.config/webpack/base.js b/.config/webpack/base.js index dd7dde6766..9f3305d906 100644 --- a/.config/webpack/base.js +++ b/.config/webpack/base.js @@ -2,6 +2,7 @@ const path = require('path'); const fs = require('fs'); const { BannerPlugin } = require('webpack'); +const licenseComment = fs.readFileSync(path.resolve(__dirname, '../source-license-header.js'), 'utf8').trim(); let licensePreamble = fs.readFileSync(path.resolve(__dirname, '../../LICENSE.txt'), 'utf8'); licensePreamble += '\n\nVersion: ' + process.env.HT_VERSION; @@ -42,6 +43,14 @@ module.exports.create = function create(processedFile) { } ] }, + { + test: /\.js$/, + loader: 'string-replace-loader', + options: { + search: licenseComment, + replace: '', + } + } ] }, plugins: [ diff --git a/.eslintignore b/.eslintignore index c0a76472bd..1bec93b8cd 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,9 @@ # Common files node_modules +# example files +docs/examples/ + # 3rd party src/interpreter/plugin/3rdparty diff --git a/docs/.vuepress/components/ScriptLoader.vue b/docs/.vuepress/components/ScriptLoader.vue new file mode 100644 index 0000000000..c77d3eb329 --- /dev/null +++ b/docs/.vuepress/components/ScriptLoader.vue @@ -0,0 +1,39 @@ + + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index d966d28666..599773844d 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -2,9 +2,9 @@ const highlight = require('./highlight'); const regexPlugin = require('markdown-it-regex').default; const footnotePlugin = require('markdown-it-footnote'); const searchBoxPlugin = require('./plugins/search-box'); +const examples = require('./plugins/examples/examples'); const HyperFormula = require('../../dist/hyperformula.full'); -const fs = require('fs'); -const path = require('path'); +const includeCodeSnippet = require('./plugins/markdown-it-include-code-snippet'); const searchPattern = new RegExp('^/api', 'i'); @@ -12,6 +12,12 @@ module.exports = { title: 'HyperFormula (v' + HyperFormula.version + ')', description: 'HyperFormula is an open-source, high-performance calculation engine for spreadsheets and web applications.', head: [ + // Import HF (required for the examples) + [ 'script', { src: 'https://cdn.jsdelivr.net/npm/hyperformula/dist/hyperformula.full.min.js' } ], + [ 'script', { src: 'https://cdn.jsdelivr.net/npm/hyperformula@2.7.1/dist/languages/enUS.js' } ], + [ 'script', { src: 'https://cdn.jsdelivr.net/npm/hyperformula@2.7.1/dist/languages/frFR.js' } ], + // Import moment (required for the examples) + [ 'script', { src: 'https://cdn.jsdelivr.net/npm/moment/moment.min.js' } ], // Google Tag Manager, an extra element within the `ssr.html` file. ['script', {}, ` (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': @@ -41,11 +47,11 @@ module.exports = { `], [ 'script', - { - id: 'Sentry.io', - src: 'https://js.sentry-cdn.com/50617701901516ce348cb7b252564a60.min.js', - crossorigin: 'anonymous', - }, + { + id: 'Sentry.io', + src: 'https://js.sentry-cdn.com/50617701901516ce348cb7b252564a60.min.js', + crossorigin: 'anonymous', + }, ], // Favicon ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/favicon/apple-touch-icon.png' }], @@ -57,14 +63,7 @@ module.exports = { base: '/', plugins: [ searchBoxPlugin, - // [ - // 'vuepress-plugin-clean-urls', - // { - // normalSuffix: '', - // indexSuffix: '/', - // notFoundPath: '/404.html', - // }, - // ], + ['container', examples()], { extendPageData ($page) { // inject current HF version as {{ $page.version }} variable @@ -109,6 +108,7 @@ module.exports = { replace: () => `'${HyperFormula.releaseDate}'` }) md.use(footnotePlugin) + md.use(includeCodeSnippet) } }, // TODO: It doesn't work. It's seems that this option is bugged. Documentation says that this option is configurable, diff --git a/docs/.vuepress/plugins/examples/code-builder.js b/docs/.vuepress/plugins/examples/code-builder.js new file mode 100644 index 0000000000..9c48be9ae4 --- /dev/null +++ b/docs/.vuepress/plugins/examples/code-builder.js @@ -0,0 +1,32 @@ +const { transformSync } = require('@babel/core'); + +const babelConfig = { + presets: [ + '@babel/preset-env', + '@babel/preset-react', + '@babel/preset-typescript', + ], + plugins: [ + '@babel/plugin-transform-modules-commonjs', + '@babel/plugin-syntax-class-properties', + ['@babel/plugin-proposal-decorators', { legacy: true }], + ['@babel/plugin-proposal-class-properties', { loose: true }], + ['@babel/plugin-proposal-private-methods', { loose: true }], + ['@babel/plugin-proposal-private-property-in-object', { loose: true }] + ], + targets: { + ie: 9 + } +}; + +const buildCode = (filename, contentJs, relativePath = '') => { + try { + return transformSync(contentJs, { ...babelConfig, filename }).code; + } catch (error) { + // eslint-disable-next-line + console.error(`Babel error when building ${relativePath}`); + throw error; + } +}; + +module.exports = { buildCode }; diff --git a/docs/.vuepress/plugins/examples/examples.js b/docs/.vuepress/plugins/examples/examples.js new file mode 100644 index 0000000000..18a19c1b5b --- /dev/null +++ b/docs/.vuepress/plugins/examples/examples.js @@ -0,0 +1,119 @@ +const Token = require('markdown-it/lib/token'); +const { buildCode } = require('./code-builder'); +const { stackblitz } = require('./stackblitz'); + +/** + * Matches into: `example #ID .class :preset --css 2 --html 0 --js 1 --ts 3 --no-edit`. + * + * @type {RegExp} + */ +const EXAMPLE_REGEX = /^(example)\s*(#\S*|)\s*(\.\S*|)\s*(:\S*|)\s*([\S|\s]*)$/; + +const parseCode = (content) => { + if (!content) return ''; + + return content + // Remove the all "/* start:skip-in-preview */" and "/* end:skip-in-preview */" comments + .replace(/\/\*(\s+)?(start|end):skip-in-preview(\s+)?\*\/\r?\n/gm, '') + // Remove the all "/* start:skip-in-sandbox */" and "/* end:skip-in-sandbox */" comments + .replace(/\/\*(\s+)?(start|end):skip-in-sandbox(\s+)?\*\/\r?\n/gm, '') + // Remove the code between "/* start:skip-in-compilation */" and "/* end:skip-in-compilation */" expressions + .replace(/\/\*(\s+)?start:skip-in-compilation(\s+)?\*\/\r?\n.*?\/\*(\s+)?end:skip-in-compilation(\s+)?\*\/\r?\n/msg, '') + // Remove /* end-file */ + .replace(/\/\* end-file \*\//gm, ''); +}; + +const parseCodeSandbox = (content) => { + if (!content) return ''; + + return content + // Remove the code between "/* start:skip-in-sandbox */" and "/* end:skip-in-sandbox */" expressions + .replace(/\/\*(\s+)?start:skip-in-sandbox(\s+)?\*\/\r?\n.*?\/\*(\s+)?end:skip-in-sandbox(\s+)?\*\/\r?\n/msg, '') + // Remove the all "/* start:skip-in-preview */" and "/* end:skip-in-preview */" comments + .replace(/\/\*(\s+)?(start|end):skip-in-preview(\s+)?\*\/\r?\n/gm, '') + // Remove the all "/* start:skip-in-compilation */" and "/* end:skip-in-compilation */" comments + .replace(/\/\*(\s+)?(start|end):skip-in-compilation(\s+)?\*\/\r?\n/gm, ''); +}; + +module.exports = function() { + return { + type: 'example', + render(tokens, index, _opts, env) { + const token = tokens[index]; + const m = token.info.trim().match(EXAMPLE_REGEX); + + if (token.nesting !== 1 || !m) { + return ''; + } + + let [, , id, klass, preset, args] = m; + + id = id ? id.substring(1) : 'example1'; + klass = klass ? klass.substring(1) : ''; + preset = preset ? preset.substring(1) : 'hot'; + args = args || ''; + + const htmlPos = args.match(/--html (\d*)/)?.[1]; + const htmlIndex = htmlPos ? index + Number.parseInt(htmlPos, 10) : 0; + const htmlToken = htmlPos ? tokens[htmlIndex] : undefined; + const htmlContent = htmlToken + ? htmlToken.content + : `
`; + const htmlContentRoot = `
${htmlContent}
`; + + const cssPos = args.match(/--css (\d*)/)?.[1]; + const cssIndex = cssPos ? index + Number.parseInt(cssPos, 10) : 0; + const cssToken = cssPos ? tokens[cssIndex] : undefined; + const cssContent = cssToken ? cssToken.content : ''; + + const jsPos = args.match(/--js (\d*)/)?.[1] || 1; + const jsIndex = jsPos ? index + Number.parseInt(jsPos, 10) : 0; + const jsToken = jsPos ? tokens[jsIndex] : undefined; + + const tsPos = args.match(/--ts (\d*)/)?.[1]; + const tsIndex = tsPos ? index + Number.parseInt(tsPos, 10) : 0; + const tsToken = tsPos ? tokens[tsIndex] : undefined; + + // Parse code + const codeToCompile = parseCode(jsToken?.content); + const tsCodeToCompileSandbox = parseCodeSandbox(tsToken?.content); + + [htmlIndex, jsIndex, tsIndex, cssIndex].filter(x => !!x).sort().reverse().forEach((x) => { + tokens.splice(x, 1); + }); + + const newTokens = [ + new Token('container_div_open', 'div', 1), + new Token('container_div_close', 'div', -1), + new Token('container_div_open', 'div', 1), + new Token('container_div_close', 'div', -1), + ]; + + tokens.splice(index + 1, 0, ...newTokens); + + const builtCode = buildCode( + id + '.js', + codeToCompile, + env.relativePath + ); + const encodedCode = encodeURI(builtCode); + + return ` +
+ +
${htmlContentRoot}
+ +
+
+ ${stackblitz( + id, + htmlContent, + tsCodeToCompileSandbox, + cssContent, + 'ts' + )} +
+ `; + }, + }; +}; diff --git a/docs/.vuepress/plugins/examples/stackblitz.js b/docs/.vuepress/plugins/examples/stackblitz.js new file mode 100644 index 0000000000..7b891637c8 --- /dev/null +++ b/docs/.vuepress/plugins/examples/stackblitz.js @@ -0,0 +1,77 @@ +const buildJavascriptBody = ({ id, html, js, css, hyperformulaVersion, lang }) => { + return { + files: { + 'package.json': { + content: `{ + "name": "hyperformula-demo", + "version": "1.0.0", + "main": "index.html", + "dependencies": { + "hyperformula": "${hyperformulaVersion}", + "moment": "latest" + } +}` + }, + 'index.html': { + content: ` + + + + + HyperFormula demo + + + + ${html || `
`} + +` + }, + 'styles.css': { + content: css + }, + [`index.${lang}`]: { + content: `import './styles.css' +${js}` + }, + } + }; +}; + +const stackblitz = (id, html, js, css, lang) => { + const hyperformulaVersion = 'latest'; + const body = buildJavascriptBody({ id, html, js, css, hyperformulaVersion, lang }); + const template = lang === 'ts' ? 'typescript' : 'node'; + + const projects = body?.files + ? Object.entries(body?.files).map(([key, value]) => ( + `` + )) : []; + + return ` +
+ ${projects.join('\n')} + + + + + +
+ `; +}; + +module.exports = { stackblitz }; diff --git a/docs/.vuepress/plugins/markdown-it-include-code-snippet/index.js b/docs/.vuepress/plugins/markdown-it-include-code-snippet/index.js new file mode 100644 index 0000000000..c35bd4c956 --- /dev/null +++ b/docs/.vuepress/plugins/markdown-it-include-code-snippet/index.js @@ -0,0 +1,105 @@ +const fs = require('fs'); + +const fileExists = filePath => fs.existsSync(filePath); + +const readFileContent = filePath => + (fileExists(filePath) + ? fs.readFileSync(filePath).toString() + : `Not Found: ${filePath}`); + +const parseOptions = (optionsString) => { + const parsedOptions = {}; + + optionsString + .trim() + .split(' ') + .forEach((pair) => { + const [option, value] = pair.split('='); + + parsedOptions[option] = value; + }); + + return parsedOptions; +}; + +module.exports = function includeCodeSnippet(markdown, options) { + const rootDirectory = options && options.root ? options.root : process.cwd(); + + const createDataObject = (state, position, maximum) => { + const start = position + 6; + const end = state.skipSpacesBack(maximum, position) - 1; + const [optionsString, fullpathWithAtSym] = state.src + .slice(start, end) + .trim() + .split(']('); + + const fullpath = fullpathWithAtSym.replace(/^@/, rootDirectory).trim(); + const pathParts = fullpath.split('/'); + const fileParts = pathParts[pathParts.length - 1].split('.'); + + return { + file: { + resolve: fullpath, + path: pathParts.slice(0, pathParts.length - 1).join('/'), + name: fileParts.slice(0, fileParts.length - 1).join('.'), + ext: fileParts[fileParts.length - 1], + }, + options: parseOptions(optionsString), + content: readFileContent(fullpath), + fileExists: fileExists(fullpath), + }; + }; + + // eslint-disable-next-line no-shadow + const mapOptions = ({ options }) => ({ + hasHighlight: options.highlight || false, + get meta() { + return this.hasHighlight ? options.highlight : ''; + }, + }); + + /** + * Custom parser function for handling code snippets in markdown. + * + * @param {object} state - The current state object of the markdown-it parser. + * @param {number} startLine - The line number where the code snippet starts. + * @param {number} endLine - The line number where the code snippet ends. + * @param {boolean} silent - Flag indicating whether the parser should run in silent mode. + * @returns {boolean} - Returns true if the code snippet was successfully parsed, false otherwise. + */ + function customParser(state, startLine, endLine, silent) { + const fenceMarker = '@[code]'; + const position = state.bMarks[startLine] + state.tShift[startLine]; + const maximum = state.eMarks[startLine]; + + // Early return for indented blocks + if (state.sCount[startLine] - state.blkIndent >= 4) return false; + + if (!state.src.startsWith(fenceMarker, position)) { + // Early return if fence marker is not present + return false; + } + + if (silent) { + // Handle silent mode + return true; + } + + const dataObject = createDataObject(state, position, maximum); + const optionsMapping = mapOptions(dataObject); + + const token = state.push('fence', 'code', 0); + + token.info = + (dataObject.options.lang || dataObject.file.ext) + optionsMapping.meta; + token.content = dataObject.content; + token.markup = '```'; + token.map = [startLine, startLine + 1]; + + state.line = startLine + 1; + + return true; + } + + markdown.block.ruler.before('fence', 'snippet', customParser); +}; diff --git a/docs/.vuepress/public/favicon/site.webmanifest b/docs/.vuepress/public/favicon/site.webmanifest index c2700e2936..191b296147 100644 --- a/docs/.vuepress/public/favicon/site.webmanifest +++ b/docs/.vuepress/public/favicon/site.webmanifest @@ -3,12 +3,12 @@ "short_name": "HyperFormula", "icons": [ { - "src": "/android-chrome-192x192.png", + "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/android-chrome-512x512.png", + "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl index 0679d6cdbe..f2969329b3 100644 --- a/docs/.vuepress/styles/index.styl +++ b/docs/.vuepress/styles/index.styl @@ -87,4 +87,60 @@ table { line-height: 1.7; text-align: left; } -} \ No newline at end of file +} + +.example-controls { + display: flex + flex-wrap: wrap + gap: 8px + background: $editorColor + border-radius: $radius + padding: 10px + margin 0 + + .hidden { + display: none + } + + button { + border: 1px solid $borderColor + border-radius: $radius + padding: 12px 20px 11px 17px !important + display: flex + align-items: center + gap: 8px + transition: background 0.3s ease, box-shadow 0.3s ease + cursor: pointer + background: white + font-size: 14px + + &:hover { + background: #f3f3f3 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) + } + + i, svg { + opacity: .7 + } + + svg { + path { + fill: black + } + } + + &:disabled { + i { + opacity: 0.3 !important + } + } + + @media (hover: hover) { + &:hover { + i, svg { + opacity 1 + } + } + } + } +} diff --git a/docs/.vuepress/styles/palette.styl b/docs/.vuepress/styles/palette.styl index 4b7e0fa3d3..4659740559 100644 --- a/docs/.vuepress/styles/palette.styl +++ b/docs/.vuepress/styles/palette.styl @@ -7,6 +7,7 @@ $arrowBgColor = #ccc $badgeTipColor = #1147f1 $badgeWarningColor = darken(#ffe564, 35%) $badgeErrorColor = #DA5961 +$editorColor = #efeff3 // layout $navbarHeight = 3.6rem @@ -18,3 +19,6 @@ $homePageWidth = 960px $MQNarrow = 959px $MQMobile = 719px $MQMobileNarrow = 419px + +// other +$radius = 8px diff --git a/docs/code-examples-generator.sh b/docs/code-examples-generator.sh new file mode 100644 index 0000000000..e9e36c492b --- /dev/null +++ b/docs/code-examples-generator.sh @@ -0,0 +1,108 @@ +#!/usr/bin/bash + +# This script generates JS/JSX examples from TS/TSX files and formats them using ESLint. +# Usage: +# ./code-examples-generator.sh path/to/file.ts - generates single example path/to/file.js +# ./code-examples-generator.sh --generateAll - generates all examples in the content/guides directory +# ./code-examples-generator.sh --formatAllTsExamples - runs the autoformatter on all TS and TSX example files in the content/guides directory + +ALL_EXAMPLES_DIR="docs/examples" +ESLINT_CONFIG="docs/examples/eslintrc.examples.js" + +format() { + eslint --fix --no-ignore -c "$ESLINT_CONFIG" "$1" > /dev/null +} + +build_output_filename() { + local input_filename + local output_filename + input_filename="$1" + + if [[ "$input_filename" == *.ts ]]; then + output_filename="${input_filename%.*}.js" + elif [[ "$input_filename" == *.tsx ]]; then + output_filename="${input_filename%.*}.jsx" + else + echo "Invalid file extension: $input_filename. Must be .ts or .tsx" >&2 + return 1 + fi + + echo "$output_filename" +} + +generate() { + local input_filename + local output_filename + input_filename="$1" + output_filename=$(build_output_filename "$input_filename") + + if [[ "$input_filename" == *.ts ]]; then + tsc --target esnext --skipLibCheck "$input_filename" > /dev/null + elif [[ "$input_filename" == *.tsx ]]; then + tsc --target esnext --jsx preserve --skipLibCheck "$input_filename" > /dev/null + else + echo "Invalid file extension: $input_filename. Must be .ts or .tsx" >&2 + return 1 + fi + + if [ ! -f "$output_filename" ]; then + echo "Failed to generate $output_filename from $input_filename" >&2 + return 1 + fi +} + +format_single_file() { + format "$1" + echo "Formatted $1" +} + +generate_single_example() { + local input_filename + local output_filename + input_filename="$1" + output_filename=$(build_output_filename "$input_filename") + + generate "$input_filename" + format "$output_filename" + echo "Generated $output_filename" +} + +go_through_all_examples_concurrently() { + local task + local jobs_limit + task="$1" + jobs_limit=16 + + echo "Running $jobs_limit jobs in parallel..." + + find "$ALL_EXAMPLES_DIR" -type f \( -name "*.ts" -o -name "*.tsx" \) -print0 | while read -d $'\0' source_input_filename; do + while test "$(jobs | wc -l)" -ge "$jobs_limit"; do + sleep 1 + done + + if [ "$task" == "formatAllTsExamples" ]; then + format_single_file "$source_input_filename" & + else + generate_single_example "$source_input_filename" & + fi + + done + + wait + echo "Waiting for the result of all jobs..." + sleep 30 + echo "All jobs finished" +} + +if [ -z "$1" ]; then + echo "Provide a path to the TS/TSX file or use one of the commands: --generateAll, --formatAllTsExamples" +elif [ "$1" == "--generateAll" ]; then + echo "Generating all examples..." + go_through_all_examples_concurrently "generateAll" +elif [ "$1" == "--formatAllTsExamples" ]; then + echo "Formatting all TS/TSX example files..." + go_through_all_examples_concurrently "formatAllTsExamples" +else + echo "Generating single example..." + generate_single_example "$1" +fi diff --git a/docs/examples/advanced-usage/example1.css b/docs/examples/advanced-usage/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/advanced-usage/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/advanced-usage/example1.html b/docs/examples/advanced-usage/example1.html new file mode 100644 index 0000000000..2973be171d --- /dev/null +++ b/docs/examples/advanced-usage/example1.html @@ -0,0 +1,49 @@ +
+ +
+ 🏆 + +
+
+
+

Team A

+ + + + + + + + + + + + +
IDScore
+
+
+

Team B

+ + + + + + + + + + + + +
IDScore
+
+
+

Formulas

+ + +
+
+
+
\ No newline at end of file diff --git a/docs/examples/advanced-usage/example1.js b/docs/examples/advanced-usage/example1.js new file mode 100644 index 0000000000..564a58c6fd --- /dev/null +++ b/docs/examples/advanced-usage/example1.js @@ -0,0 +1,129 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +// first column represents players' IDs +// second column represents players' scores +const playersAData = [ + ['1', '2'], + ['2', '3'], + ['3', '5'], + ['4', '7'], + ['5', '13'], + ['6', '17'], +]; + +const playersBData = [ + ['7', '19'], + ['8', '31'], + ['9', '61'], + ['10', '89'], + ['11', '107'], + ['12', '127'], +]; + +// in a cell A1 a formula checks which team is a winning one +// in cells A2 and A3 formulas calculate the average score of players +const formulasData = [ + ['=IF(Formulas!A2>Formulas!A3,"TeamA","TeamB")'], + ['=AVERAGE(TeamA!B1:B6)'], + ['=AVERAGE(TeamB!B1:B6)'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +const sheetInfo = { + teamA: { sheetName: 'TeamA' }, + teamB: { sheetName: 'TeamB' }, + formulas: { sheetName: 'Formulas' }, +}; + +// add 'TeamA' sheet +hf.addSheet(sheetInfo.teamA.sheetName); +// insert playersA content into targeted 'TeamA' sheet +hf.setSheetContent(hf.getSheetId(sheetInfo.teamA.sheetName), playersAData); +// add 'TeamB' sheet +hf.addSheet(sheetInfo.teamB.sheetName); +// insert playersB content into targeted 'TeamB' sheet +hf.setSheetContent(hf.getSheetId(sheetInfo.teamB.sheetName), playersBData); +// add a sheet named 'Formulas' +hf.addSheet(sheetInfo.formulas.sheetName); +// add formulas to that sheet +hf.setSheetContent(hf.getSheetId(sheetInfo.formulas.sheetName), formulasData); + +/** + * Fill the HTML table with data. + * + * @param {string} sheetName Sheet name. + */ +function renderTable(sheetName) { + const sheetId = hf.getSheetId(sheetName); + const tbodyDOM = document.querySelector( + `.example #${sheetName}-container tbody` + ); + + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && !cellHasFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += `${cellValue}`; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Render the result block + */ +function renderResult() { + const resultOutputDOM = document.querySelector('.example #output'); + const cellAddress = hf.simpleCellAddressFromString( + `${sheetInfo.formulas.sheetName}!A1`, + hf.getSheetId(sheetInfo.formulas.sheetName) + ); + + resultOutputDOM.innerHTML = ` + ${hf.getCellValue(cellAddress)} won! + `; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + + runButton.addEventListener('click', () => { + renderResult(); + }); +} + +// Bind the button events. +bindEvents(); + +// Render the preview tables. +for (const [_, tableInfo] of Object.entries(sheetInfo)) { + renderTable(tableInfo.sheetName); +} diff --git a/docs/examples/advanced-usage/example1.ts b/docs/examples/advanced-usage/example1.ts new file mode 100644 index 0000000000..fe2519e418 --- /dev/null +++ b/docs/examples/advanced-usage/example1.ts @@ -0,0 +1,131 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +// first column represents players' IDs +// second column represents players' scores +const playersAData = [ + ['1', '2'], + ['2', '3'], + ['3', '5'], + ['4', '7'], + ['5', '13'], + ['6', '17'], +]; + +const playersBData = [ + ['7', '19'], + ['8', '31'], + ['9', '61'], + ['10', '89'], + ['11', '107'], + ['12', '127'], +]; + +// in a cell A1 a formula checks which team is a winning one +// in cells A2 and A3 formulas calculate the average score of players +const formulasData = [ + ['=IF(Formulas!A2>Formulas!A3,"TeamA","TeamB")'], + ['=AVERAGE(TeamA!B1:B6)'], + ['=AVERAGE(TeamB!B1:B6)'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +const sheetInfo = { + teamA: { sheetName: 'TeamA' }, + teamB: { sheetName: 'TeamB' }, + formulas: { sheetName: 'Formulas' }, +}; + +// add 'TeamA' sheet +hf.addSheet(sheetInfo.teamA.sheetName); +// insert playersA content into targeted 'TeamA' sheet +hf.setSheetContent(hf.getSheetId(sheetInfo.teamA.sheetName), playersAData); + +// add 'TeamB' sheet +hf.addSheet(sheetInfo.teamB.sheetName); +// insert playersB content into targeted 'TeamB' sheet +hf.setSheetContent(hf.getSheetId(sheetInfo.teamB.sheetName), playersBData); + +// add a sheet named 'Formulas' +hf.addSheet(sheetInfo.formulas.sheetName); +// add formulas to that sheet +hf.setSheetContent(hf.getSheetId(sheetInfo.formulas.sheetName), formulasData); + +/** + * Fill the HTML table with data. + * + * @param {string} sheetName Sheet name. + */ +function renderTable(sheetName) { + const sheetId = hf.getSheetId(sheetName); + const tbodyDOM = document.querySelector( + `.example #${sheetName}-container tbody` + ); + + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && !cellHasFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += `${cellValue}`; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Render the result block + */ +function renderResult() { + const resultOutputDOM = document.querySelector('.example #output'); + const cellAddress = hf.simpleCellAddressFromString( + `${sheetInfo.formulas.sheetName}!A1`, + hf.getSheetId(sheetInfo.formulas.sheetName) + ); + + resultOutputDOM.innerHTML = ` + ${hf.getCellValue(cellAddress)} won! + `; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + + runButton.addEventListener('click', () => { + renderResult(); + }); +} + +// Bind the button events. +bindEvents(); + +// Render the preview tables. +for (const [_, tableInfo] of Object.entries(sheetInfo)) { + renderTable(tableInfo.sheetName); +} diff --git a/docs/examples/basic-operations/example1.css b/docs/examples/basic-operations/example1.css new file mode 100644 index 0000000000..ef0730e924 --- /dev/null +++ b/docs/examples/basic-operations/example1.css @@ -0,0 +1,228 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* basic-operations form */ + +.example #inputs { + display: none; +} + +.example #inputs input, +.example #toolbar select, +.example #inputs button { + height: 38px; +} + +.example #inputs input.inline, +.example #inputs select.inline { + border-bottom-right-radius: 0; + border-right: 0; + border-top-right-radius: 0; + margin: 0; + width: 10em; + float: left; +} + +.example #inputs button.inline { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + margin: 0; +} + +.example #inputs input.inline.middle { + border-radius: 0; + margin: 0; + width: 10em; + float: left; +} + +.example #inputs input::placeholder { + opacity: 0.55; +} + +.example #inputs input:disabled { + background-color: #f7f7f7; +} + +.example #inputs.error input { + border: 1px solid red; +} diff --git a/docs/examples/basic-operations/example1.html b/docs/examples/basic-operations/example1.html new file mode 100644 index 0000000000..f009af48ac --- /dev/null +++ b/docs/examples/basic-operations/example1.html @@ -0,0 +1,57 @@ +
+
+
+
+ +
+
+
+ +
+
+ + + +
+

+

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
diff --git a/docs/examples/basic-operations/example1.js b/docs/examples/basic-operations/example1.js new file mode 100644 index 0000000000..a0ecad379e --- /dev/null +++ b/docs/examples/basic-operations/example1.js @@ -0,0 +1,461 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +const ANIMATION_ENABLED = true; + +/** + * Return sample data for the provided number of rows and columns. + * + * @param {number} rows Amount of rows to create. + * @param {number} columns Amount of columns to create. + * @returns {string[][]} + */ +function getSampleData(rows, columns) { + const data = []; + + for (let r = 0; r < rows; r++) { + data.push([]); + + for (let c = 0; c < columns; c++) { + data[r].push(`${Math.floor(Math.random() * 999) + 1}`); + } + } + + return data; +} + +/** + * A simple state object for the demo. + * + * @type {object} + */ +const state = { + currentSheet: null, +}; + +/** + * Input configuration and definition. + * + * @type {object} + */ +const inputConfig = { + 'add-sheet': { + inputs: [ + { + type: 'text', + placeholder: 'Sheet name', + }, + ], + buttonText: 'Add Sheet', + disclaimer: + 'For the sake of this demo, the new sheets will be filled with random data.', + }, + 'remove-sheet': { + inputs: [ + { + type: 'text', + placeholder: 'Sheet name', + }, + ], + buttonText: 'Remove Sheet', + }, + 'add-rows': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Add Rows', + }, + 'add-columns': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Add Columns', + }, + 'remove-rows': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Remove Rows', + }, + 'remove-columns': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Remove Columns', + }, + 'get-value': { + inputs: [ + { + type: 'text', + placeholder: 'Cell Address', + }, + { + type: 'text', + disabled: 'disabled', + placeholder: '', + }, + ], + disclaimer: 'Cell addresses format examples: A1, B4, C6.', + buttonText: 'Get Value', + }, + 'set-value': { + inputs: [ + { + type: 'text', + placeholder: 'Cell Address', + }, + { + type: 'text', + placeholder: 'Value', + }, + ], + disclaimer: 'Cell addresses format examples: A1, B4, C6.', + buttonText: 'Set Value', + }, +}; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +state.currentSheet = 'InitialSheet'; + +const sheetName = hf.addSheet(state.currentSheet); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setSheetContent(sheetId, getSampleData(5, 5)); + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const sheetId = hf.getSheetId(state.currentSheet); + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const isEmpty = hf.isCellEmpty(cellAddress); + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = cellHasFormula; + let cellValue = ''; + + if (isEmpty) { + cellValue = ''; + } else if (!showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Updates the sheet dropdown. + */ +function updateSheetDropdown() { + const sheetNames = hf.getSheetNames(); + const sheetDropdownDOM = document.querySelector('.example #sheet-select'); + let dropdownContent = ''; + + sheetDropdownDOM.innerHTML = ''; + sheetNames.forEach((sheetName) => { + const isCurrent = sheetName === state.currentSheet; + + dropdownContent += ``; + }); + sheetDropdownDOM.innerHTML = dropdownContent; +} + +/** + * Update the form to the provided action. + * + * @param {string} action Action chosen from the dropdown. + */ +function updateForm(action) { + const inputsDOM = document.querySelector('.example #inputs'); + const submitButtonDOM = document.querySelector('.example #inputs button'); + const allInputsDOM = document.querySelectorAll('.example #inputs input'); + const disclaimerDOM = document.querySelector('.example #disclaimer'); + + // Hide all inputs + allInputsDOM.forEach((input) => { + input.style.display = 'none'; + input.value = ''; + input.disabled = false; + }); + inputConfig[action].inputs.forEach((inputCfg, index) => { + const inputDOM = document.querySelector(`.example #input-${index + 1}`); + + // Show only those needed + inputDOM.style.display = 'block'; + + for (const [attribute, value] of Object.entries(inputCfg)) { + inputDOM.setAttribute(attribute, value); + } + }); + submitButtonDOM.innerText = inputConfig[action].buttonText; + + if (inputConfig[action].disclaimer) { + disclaimerDOM.innerHTML = inputConfig[action].disclaimer; + disclaimerDOM.parentElement.style.display = 'block'; + } else { + disclaimerDOM.innerHTML = ' '; + } + + inputsDOM.style.display = 'block'; +} + +/** + * Add the error overlay. + * + * @param {string} message Error message. + */ +function renderError(message) { + const inputsDOM = document.querySelector('.example #inputs'); + const errorDOM = document.querySelector('.example #error-message'); + + if (inputsDOM.className.indexOf('error') === -1) { + inputsDOM.className += ' error'; + } + + errorDOM.innerText = message; + errorDOM.parentElement.style.display = 'block'; +} + +/** + * Clear the error overlay. + */ +function clearError() { + const inputsDOM = document.querySelector('.example #inputs'); + const errorDOM = document.querySelector('.example #error-message'); + + inputsDOM.className = inputsDOM.className.replace(' error', ''); + errorDOM.innerText = ''; + errorDOM.parentElement.style.display = 'none'; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const sheetDropdown = document.querySelector('.example #sheet-select'); + const actionDropdown = document.querySelector('.example #action-select'); + const submitButton = document.querySelector('.example #inputs button'); + + sheetDropdown.addEventListener('change', (event) => { + state.currentSheet = event.target.value; + clearError(); + renderTable(); + }); + actionDropdown.addEventListener('change', (event) => { + clearError(); + updateForm(event.target.value); + }); + submitButton.addEventListener('click', (event) => { + const action = document.querySelector('.example #action-select').value; + + doAction(action); + }); +} + +/** + * Perform the wanted action. + * + * @param {string} action Action to perform. + */ +function doAction(action) { + let cellAddress = null; + const inputValues = [ + document.querySelector('.example #input-1').value || void 0, + document.querySelector('.example #input-2').value || void 0, + ]; + + clearError(); + + switch (action) { + case 'add-sheet': + state.currentSheet = hf.addSheet(inputValues[0]); + handleError(() => { + hf.setSheetContent( + hf.getSheetId(state.currentSheet), + getSampleData(5, 5) + ); + }); + updateSheetDropdown(); + renderTable(); + + break; + case 'remove-sheet': + handleError(() => { + hf.removeSheet(hf.getSheetId(inputValues[0])); + }); + + if (state.currentSheet === inputValues[0]) { + state.currentSheet = hf.getSheetNames()[0]; + renderTable(); + } + + updateSheetDropdown(); + + break; + case 'add-rows': + handleError(() => { + hf.addRows(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + renderTable(); + + break; + case 'add-columns': + handleError(() => { + hf.addColumns(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + renderTable(); + + break; + case 'remove-rows': + handleError(() => { + hf.removeRows(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + renderTable(); + + break; + case 'remove-columns': + handleError(() => { + hf.removeColumns(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + renderTable(); + + break; + case 'get-value': + const resultDOM = document.querySelector('.example #input-2'); + + cellAddress = handleError(() => { + return hf.simpleCellAddressFromString( + inputValues[0], + hf.getSheetId(state.currentSheet) + ); + }, 'Invalid cell address format.'); + + if (cellAddress !== null) { + resultDOM.value = handleError(() => { + return hf.getCellValue(cellAddress); + }); + } + + break; + case 'set-value': + cellAddress = handleError(() => { + return hf.simpleCellAddressFromString( + inputValues[0], + hf.getSheetId(state.currentSheet) + ); + }, 'Invalid cell address format.'); + + if (cellAddress !== null) { + handleError(() => { + hf.setCellContents(cellAddress, inputValues[1]); + }); + } + + renderTable(); + + break; + default: + } +} + +/** + * Handle the HF errors. + * + * @param {Function} tryFunc Function to handle. + * @param {string} [message] Optional forced error message. + */ +function handleError(tryFunc, message = null) { + let result = null; + + try { + result = tryFunc(); + } catch (e) { + if (e instanceof Error) { + renderError(message || e.message); + } else { + renderError('Something went wrong'); + } + } + + return result; +} + +// // Bind the UI events. +bindEvents(); +// Render the table. +renderTable(); +// Refresh the sheet dropdown list +updateSheetDropdown(); +document.querySelector('.example .message-box').style.display = 'block'; diff --git a/docs/examples/basic-operations/example1.ts b/docs/examples/basic-operations/example1.ts new file mode 100644 index 0000000000..ca2b9e24f4 --- /dev/null +++ b/docs/examples/basic-operations/example1.ts @@ -0,0 +1,481 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +const ANIMATION_ENABLED = true; + +/** + * Return sample data for the provided number of rows and columns. + * + * @param {number} rows Amount of rows to create. + * @param {number} columns Amount of columns to create. + * @returns {string[][]} + */ +function getSampleData(rows, columns) { + const data = []; + + for (let r = 0; r < rows; r++) { + data.push([]); + + for (let c = 0; c < columns; c++) { + data[r].push(`${Math.floor(Math.random() * 999) + 1}`); + } + } + + return data; +} + +/** + * A simple state object for the demo. + * + * @type {object} + */ +const state = { + currentSheet: null, +}; + +/** + * Input configuration and definition. + * + * @type {object} + */ +const inputConfig = { + 'add-sheet': { + inputs: [ + { + type: 'text', + placeholder: 'Sheet name', + }, + ], + buttonText: 'Add Sheet', + disclaimer: + 'For the sake of this demo, the new sheets will be filled with random data.', + }, + 'remove-sheet': { + inputs: [ + { + type: 'text', + placeholder: 'Sheet name', + }, + ], + buttonText: 'Remove Sheet', + }, + 'add-rows': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Add Rows', + }, + 'add-columns': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Add Columns', + }, + 'remove-rows': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Remove Rows', + }, + 'remove-columns': { + inputs: [ + { + type: 'number', + placeholder: 'Index', + }, + { + type: 'number', + placeholder: 'Amount', + }, + ], + buttonText: 'Remove Columns', + }, + 'get-value': { + inputs: [ + { + type: 'text', + placeholder: 'Cell Address', + }, + { + type: 'text', + disabled: 'disabled', + placeholder: '', + }, + ], + disclaimer: 'Cell addresses format examples: A1, B4, C6.', + buttonText: 'Get Value', + }, + 'set-value': { + inputs: [ + { + type: 'text', + placeholder: 'Cell Address', + }, + { + type: 'text', + placeholder: 'Value', + }, + ], + disclaimer: 'Cell addresses format examples: A1, B4, C6.', + buttonText: 'Set Value', + }, +}; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +state.currentSheet = 'InitialSheet'; + +const sheetName = hf.addSheet(state.currentSheet); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setSheetContent(sheetId, getSampleData(5, 5)); + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const sheetId = hf.getSheetId(state.currentSheet); + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const isEmpty = hf.isCellEmpty(cellAddress); + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = cellHasFormula; + let cellValue = ''; + + if (isEmpty) { + cellValue = ''; + } else if (!showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Updates the sheet dropdown. + */ +function updateSheetDropdown() { + const sheetNames = hf.getSheetNames(); + const sheetDropdownDOM = document.querySelector('.example #sheet-select'); + let dropdownContent = ''; + + sheetDropdownDOM.innerHTML = ''; + + sheetNames.forEach((sheetName) => { + const isCurrent = sheetName === state.currentSheet; + + dropdownContent += ``; + }); + + sheetDropdownDOM.innerHTML = dropdownContent; +} + +/** + * Update the form to the provided action. + * + * @param {string} action Action chosen from the dropdown. + */ +function updateForm(action) { + const inputsDOM = document.querySelector('.example #inputs'); + const submitButtonDOM = document.querySelector('.example #inputs button'); + const allInputsDOM = document.querySelectorAll('.example #inputs input'); + const disclaimerDOM = document.querySelector('.example #disclaimer'); + + // Hide all inputs + allInputsDOM.forEach((input) => { + input.style.display = 'none'; + input.value = ''; + input.disabled = false; + }); + + inputConfig[action].inputs.forEach((inputCfg, index) => { + const inputDOM = document.querySelector(`.example #input-${index + 1}`); + + // Show only those needed + inputDOM.style.display = 'block'; + + for (const [attribute, value] of Object.entries(inputCfg)) { + inputDOM.setAttribute(attribute, value); + } + }); + + submitButtonDOM.innerText = inputConfig[action].buttonText; + + if (inputConfig[action].disclaimer) { + disclaimerDOM.innerHTML = inputConfig[action].disclaimer; + disclaimerDOM.parentElement.style.display = 'block'; + } else { + disclaimerDOM.innerHTML = ' '; + } + + inputsDOM.style.display = 'block'; +} + +/** + * Add the error overlay. + * + * @param {string} message Error message. + */ +function renderError(message) { + const inputsDOM = document.querySelector('.example #inputs'); + const errorDOM = document.querySelector('.example #error-message'); + + if (inputsDOM.className.indexOf('error') === -1) { + inputsDOM.className += ' error'; + } + + errorDOM.innerText = message; + errorDOM.parentElement.style.display = 'block'; +} + +/** + * Clear the error overlay. + */ +function clearError() { + const inputsDOM = document.querySelector('.example #inputs'); + const errorDOM = document.querySelector('.example #error-message'); + + inputsDOM.className = inputsDOM.className.replace(' error', ''); + + errorDOM.innerText = ''; + errorDOM.parentElement.style.display = 'none'; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const sheetDropdown = document.querySelector('.example #sheet-select'); + const actionDropdown = document.querySelector('.example #action-select'); + const submitButton = document.querySelector('.example #inputs button'); + + sheetDropdown.addEventListener('change', (event) => { + state.currentSheet = event.target.value; + + clearError(); + + renderTable(); + }); + + actionDropdown.addEventListener('change', (event) => { + clearError(); + + updateForm(event.target.value); + }); + + submitButton.addEventListener('click', (event) => { + const action = document.querySelector('.example #action-select').value; + + doAction(action); + }); +} + +/** + * Perform the wanted action. + * + * @param {string} action Action to perform. + */ +function doAction(action) { + let cellAddress = null; + const inputValues = [ + document.querySelector('.example #input-1').value || void 0, + document.querySelector('.example #input-2').value || void 0, + ]; + + clearError(); + + switch (action) { + case 'add-sheet': + state.currentSheet = hf.addSheet(inputValues[0]); + + handleError(() => { + hf.setSheetContent( + hf.getSheetId(state.currentSheet), + getSampleData(5, 5) + ); + }); + + updateSheetDropdown(); + renderTable(); + + break; + case 'remove-sheet': + handleError(() => { + hf.removeSheet(hf.getSheetId(inputValues[0])); + }); + + if (state.currentSheet === inputValues[0]) { + state.currentSheet = hf.getSheetNames()[0]; + + renderTable(); + } + + updateSheetDropdown(); + + break; + case 'add-rows': + handleError(() => { + hf.addRows(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + + renderTable(); + + break; + case 'add-columns': + handleError(() => { + hf.addColumns(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + + renderTable(); + + break; + case 'remove-rows': + handleError(() => { + hf.removeRows(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + + renderTable(); + + break; + case 'remove-columns': + handleError(() => { + hf.removeColumns(hf.getSheetId(state.currentSheet), [ + parseInt(inputValues[0], 10), + parseInt(inputValues[1], 10), + ]); + }); + + renderTable(); + + break; + case 'get-value': + const resultDOM = document.querySelector('.example #input-2'); + + cellAddress = handleError(() => { + return hf.simpleCellAddressFromString( + inputValues[0], + hf.getSheetId(state.currentSheet) + ); + }, 'Invalid cell address format.'); + + if (cellAddress !== null) { + resultDOM.value = handleError(() => { + return hf.getCellValue(cellAddress); + }); + } + + break; + case 'set-value': + cellAddress = handleError(() => { + return hf.simpleCellAddressFromString( + inputValues[0], + hf.getSheetId(state.currentSheet) + ); + }, 'Invalid cell address format.'); + + if (cellAddress !== null) { + handleError(() => { + hf.setCellContents(cellAddress, inputValues[1]); + }); + } + + renderTable(); + + break; + default: + } +} + +/** + * Handle the HF errors. + * + * @param {Function} tryFunc Function to handle. + * @param {string} [message] Optional forced error message. + */ +function handleError(tryFunc, message = null) { + let result = null; + + try { + result = tryFunc(); + } catch (e) { + if (e instanceof Error) { + renderError(message || e.message); + } else { + renderError('Something went wrong'); + } + } + + return result; +} + +// // Bind the UI events. +bindEvents(); + +// Render the table. +renderTable(); + +// Refresh the sheet dropdown list +updateSheetDropdown(); + +document.querySelector('.example .message-box').style.display = 'block'; diff --git a/docs/examples/basic-usage/example1.css b/docs/examples/basic-usage/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/basic-usage/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/basic-usage/example1.html b/docs/examples/basic-usage/example1.html new file mode 100644 index 0000000000..df9c6a2a33 --- /dev/null +++ b/docs/examples/basic-usage/example1.html @@ -0,0 +1,17 @@ +
+ +
+ result: +
+ + + + + + + + +
+
\ No newline at end of file diff --git a/docs/examples/basic-usage/example1.js b/docs/examples/basic-usage/example1.js new file mode 100644 index 0000000000..4484b6bba7 --- /dev/null +++ b/docs/examples/basic-usage/example1.js @@ -0,0 +1,87 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +const tableData = [['10', '20', '=SUM(A1,B1)']]; +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + precisionRounding: 10, + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const theadDOM = document.querySelector('.example thead'); + const tbodyDOM = document.querySelector('.example tbody'); + const { height, width } = hf.getSheetDimensions(sheetId); + let newTheadHTML = ''; + let newTbodyHTML = ''; + + for (let row = -1; row < height; row++) { + for (let col = 0; col < width; col++) { + if (row === -1) { + newTheadHTML += ``; + + continue; + } + + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && !cellHasFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + } + + tbodyDOM.innerHTML = `${newTbodyHTML}`; + theadDOM.innerHTML = newTheadHTML; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const calculateButton = document.querySelector('.example #calculate'); + const formulaPreview = document.querySelector('.example #address-output'); + const calculationResult = document.querySelector('.example #result-output'); + const cellAddress = { sheet: sheetId, row: 0, col: 2 }; + + formulaPreview.innerText = hf.simpleCellAddressToString(cellAddress, sheetId); + calculateButton.addEventListener('click', () => { + calculationResult.innerText = hf.getCellValue(cellAddress); + }); +} + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/basic-usage/example1.ts b/docs/examples/basic-usage/example1.ts new file mode 100644 index 0000000000..3901624e55 --- /dev/null +++ b/docs/examples/basic-usage/example1.ts @@ -0,0 +1,90 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +const tableData = [['10', '20', '=SUM(A1,B1)']]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + precisionRounding: 10, + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const theadDOM = document.querySelector('.example thead'); + const tbodyDOM = document.querySelector('.example tbody'); + const { height, width } = hf.getSheetDimensions(sheetId); + let newTheadHTML = ''; + let newTbodyHTML = ''; + + for (let row = -1; row < height; row++) { + for (let col = 0; col < width; col++) { + if (row === -1) { + newTheadHTML += ``; + + continue; + } + + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && !cellHasFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + } + + tbodyDOM.innerHTML = `${newTbodyHTML}`; + theadDOM.innerHTML = newTheadHTML; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const calculateButton = document.querySelector('.example #calculate'); + const formulaPreview = document.querySelector('.example #address-output'); + const calculationResult = document.querySelector('.example #result-output'); + const cellAddress = { sheet: sheetId, row: 0, col: 2 }; + + formulaPreview.innerText = hf.simpleCellAddressToString(cellAddress, sheetId); + + calculateButton.addEventListener('click', () => { + calculationResult.innerText = hf.getCellValue(cellAddress); + }); +} + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/batch-operations/example1.css b/docs/examples/batch-operations/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/batch-operations/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/batch-operations/example1.html b/docs/examples/batch-operations/example1.html new file mode 100644 index 0000000000..febd5ed821 --- /dev/null +++ b/docs/examples/batch-operations/example1.html @@ -0,0 +1,39 @@ +
+
+
+ + +
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + +
NameYear_1Year_2AverageSum
+
+
\ No newline at end of file diff --git a/docs/examples/batch-operations/example1.js b/docs/examples/batch-operations/example1.js new file mode 100644 index 0000000000..72a98595a3 --- /dev/null +++ b/docs/examples/batch-operations/example1.js @@ -0,0 +1,164 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/** + * Initial table data. + */ +const tableData = [ + ['Greg Black', 4.66, '=B1*1.3', '=AVERAGE(B1:C1)', '=SUM(B1:C1)'], + ['Anne Carpenter', 5.25, '=$B$2*30%', '=AVERAGE(B2:C2)', '=SUM(B2:C2)'], + ['Natalie Dem', 3.59, '=B3*2.7+2+1', '=AVERAGE(B3:C3)', '=SUM(B3:C3)'], + ['John Sieg', 12.51, '=B4*(1.22+1)', '=AVERAGE(B4:C4)', '=SUM(B4:C4)'], + [ + 'Chris Aklips', + 7.63, + '=B5*1.1*SUM(10,20)+1', + '=AVERAGE(B5:C5)', + '=SUM(B5:C5)', + ], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); +// Add named expressions for the "TOTAL" row. +hf.addNamedExpression('Year_1', '=SUM(main!$B$1:main!$B$5)'); +hf.addNamedExpression('Year_2', '=SUM(main!$C$1:main!$C$5)'); + +const ANIMATION_ENABLED = true; + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const totals = ['=SUM(Year_1)', '=SUM(Year_2)']; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + let totalRowsHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + + if (!isNaN(cellValue)) { + cellValue = cellValue.toFixed(2); + } + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + totalRowsHTML = ` + TOTAL + + ${ + calculated + ? hf.calculateFormula(totals[0], sheetId).toFixed(2) + : totals[0] + } + + + ${ + calculated + ? hf.calculateFormula(totals[1], sheetId).toFixed(2) + : totals[1] + } + + + `; + newTbodyHTML += totalRowsHTML; + tbodyDOM.innerHTML = newTbodyHTML; +} + +let IS_CALCULATED = false; + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + const calculatedCheckbox = document.querySelector('.example #isCalculated'); + + runButton.addEventListener('click', () => { + runBatchOperations(); + }); + resetButton.addEventListener('click', () => { + resetTableData(); + }); + calculatedCheckbox.addEventListener('change', (e) => { + if (e.target.checked) { + renderTable(true); + } else { + renderTable(); + } + + IS_CALCULATED = e.target.checked; + }); +} + +/** + * Reset the data for the table. + */ +function resetTableData() { + hf.setSheetContent(sheetId, tableData); + renderTable(IS_CALCULATED); +} + +/** + * Run batch operations. + */ +function runBatchOperations() { + hf.batch(() => { + hf.setCellContents({ col: 1, row: 0, sheet: sheetId }, [['=B4']]); + hf.setCellContents({ col: 1, row: 1, sheet: sheetId }, [['=B4']]); + hf.setCellContents({ col: 1, row: 2, sheet: sheetId }, [['=B4']]); + hf.setCellContents({ col: 1, row: 4, sheet: sheetId }, [['=B4']]); + }); + renderTable(IS_CALCULATED); +} + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/batch-operations/example1.ts b/docs/examples/batch-operations/example1.ts new file mode 100644 index 0000000000..d6501f5fd7 --- /dev/null +++ b/docs/examples/batch-operations/example1.ts @@ -0,0 +1,171 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/** + * Initial table data. + */ +const tableData = [ + ['Greg Black', 4.66, '=B1*1.3', '=AVERAGE(B1:C1)', '=SUM(B1:C1)'], + ['Anne Carpenter', 5.25, '=$B$2*30%', '=AVERAGE(B2:C2)', '=SUM(B2:C2)'], + ['Natalie Dem', 3.59, '=B3*2.7+2+1', '=AVERAGE(B3:C3)', '=SUM(B3:C3)'], + ['John Sieg', 12.51, '=B4*(1.22+1)', '=AVERAGE(B4:C4)', '=SUM(B4:C4)'], + [ + 'Chris Aklips', + 7.63, + '=B5*1.1*SUM(10,20)+1', + '=AVERAGE(B5:C5)', + '=SUM(B5:C5)', + ], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +// Add named expressions for the "TOTAL" row. +hf.addNamedExpression('Year_1', '=SUM(main!$B$1:main!$B$5)'); +hf.addNamedExpression('Year_2', '=SUM(main!$C$1:main!$C$5)'); + +const ANIMATION_ENABLED = true; + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const totals = ['=SUM(Year_1)', '=SUM(Year_2)']; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + let totalRowsHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + + if (!isNaN(cellValue)) { + cellValue = cellValue.toFixed(2); + } + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + totalRowsHTML = ` + TOTAL + + ${ + calculated + ? hf.calculateFormula(totals[0], sheetId).toFixed(2) + : totals[0] + } + + + ${ + calculated + ? hf.calculateFormula(totals[1], sheetId).toFixed(2) + : totals[1] + } + + + `; + + newTbodyHTML += totalRowsHTML; + + tbodyDOM.innerHTML = newTbodyHTML; +} + +let IS_CALCULATED = false; + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + const calculatedCheckbox = document.querySelector('.example #isCalculated'); + + runButton.addEventListener('click', () => { + runBatchOperations(); + }); + + resetButton.addEventListener('click', () => { + resetTableData(); + }); + + calculatedCheckbox.addEventListener('change', (e) => { + if (e.target.checked) { + renderTable(true); + } else { + renderTable(); + } + + IS_CALCULATED = e.target.checked; + }); +} + +/** + * Reset the data for the table. + */ +function resetTableData() { + hf.setSheetContent(sheetId, tableData); + renderTable(IS_CALCULATED); +} + +/** + * Run batch operations. + */ +function runBatchOperations() { + hf.batch(() => { + hf.setCellContents({ col: 1, row: 0, sheet: sheetId }, [['=B4']]); + hf.setCellContents({ col: 1, row: 1, sheet: sheetId }, [['=B4']]); + hf.setCellContents({ col: 1, row: 2, sheet: sheetId }, [['=B4']]); + hf.setCellContents({ col: 1, row: 4, sheet: sheetId }, [['=B4']]); + }); + + renderTable(IS_CALCULATED); +} + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/clipboard-operations/example1.css b/docs/examples/clipboard-operations/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/clipboard-operations/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/clipboard-operations/example1.html b/docs/examples/clipboard-operations/example1.html new file mode 100644 index 0000000000..ba9e9668bf --- /dev/null +++ b/docs/examples/clipboard-operations/example1.html @@ -0,0 +1,37 @@ +
+
+
+ + + +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + +
NameSurnameBoth
+
diff --git a/docs/examples/clipboard-operations/example1.js b/docs/examples/clipboard-operations/example1.js new file mode 100644 index 0000000000..6cd3f1f1d1 --- /dev/null +++ b/docs/examples/clipboard-operations/example1.js @@ -0,0 +1,130 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/** + * Initial table data. + */ +const tableData = [ + ['Greg', 'Black', '=CONCATENATE(A1, " ",B1)'], + ['Anne', 'Carpenter', '=CONCATENATE(A2, " ", B2)'], + ['Chris', 'Aklips', '=CONCATENATE(A3, " ",B3)'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +/** + * Reinitialize the HF data. + */ +function reinitializeData() { + hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData + ); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const copyButton = document.querySelector('.example #copy'); + const pasteButton = document.querySelector('.example #paste'); + const resetButton = document.querySelector('.example #reset'); + + copyButton.addEventListener('click', () => { + copy(); + updateCopyInfo('Second row copied'); + }); + pasteButton.addEventListener('click', () => { + paste(); + updateCopyInfo('Pasted into the first row'); + }); + resetButton.addEventListener('click', () => { + reinitializeData(); + updateCopyInfo(''); + renderTable(); + }); +} + +/** + * Copy the second row. + */ +function copy() { + return hf.copy({ + start: { sheet: 0, col: 0, row: 1 }, + end: { sheet: 0, col: 2, row: 1 }, + }); +} + +/** + * Paste the HF clipboard into the first row. + */ +function paste() { + hf.paste({ sheet: 0, col: 0, row: 0 }); + renderTable(); +} + +const ANIMATION_ENABLED = true; + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress)) { + cellValue = hf.getCellValue(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Update the information about the copy/paste action. + * + * @param {string} message Message to display. + */ +function updateCopyInfo(message) { + const copyInfoDOM = document.querySelector('.example #copyInfo'); + + copyInfoDOM.innerText = message; +} + +// Fill the HyperFormula sheet with data. +reinitializeData(); +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/clipboard-operations/example1.ts b/docs/examples/clipboard-operations/example1.ts new file mode 100644 index 0000000000..c0c67e07f7 --- /dev/null +++ b/docs/examples/clipboard-operations/example1.ts @@ -0,0 +1,134 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/** + * Initial table data. + */ +const tableData = [ + ['Greg', 'Black', '=CONCATENATE(A1, " ",B1)'], + ['Anne', 'Carpenter', '=CONCATENATE(A2, " ", B2)'], + ['Chris', 'Aklips', '=CONCATENATE(A3, " ",B3)'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +/** + * Reinitialize the HF data. + */ +function reinitializeData() { + hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData + ); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const copyButton = document.querySelector('.example #copy'); + const pasteButton = document.querySelector('.example #paste'); + const resetButton = document.querySelector('.example #reset'); + + copyButton.addEventListener('click', () => { + copy(); + updateCopyInfo('Second row copied'); + }); + + pasteButton.addEventListener('click', () => { + paste(); + updateCopyInfo('Pasted into the first row'); + }); + + resetButton.addEventListener('click', () => { + reinitializeData(); + updateCopyInfo(''); + renderTable(); + }); +} + +/** + * Copy the second row. + */ +function copy() { + return hf.copy({ + start: { sheet: 0, col: 0, row: 1 }, + end: { sheet: 0, col: 2, row: 1 }, + }); +} + +/** + * Paste the HF clipboard into the first row. + */ +function paste() { + hf.paste({ sheet: 0, col: 0, row: 0 }); + renderTable(); +} + +const ANIMATION_ENABLED = true; + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress)) { + cellValue = hf.getCellValue(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Update the information about the copy/paste action. + * + * @param {string} message Message to display. + */ +function updateCopyInfo(message) { + const copyInfoDOM = document.querySelector('.example #copyInfo'); + + copyInfoDOM.innerText = message; +} + +// Fill the HyperFormula sheet with data. +reinitializeData(); + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/date-time/example1.css b/docs/examples/date-time/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/date-time/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/date-time/example1.html b/docs/examples/date-time/example1.html new file mode 100644 index 0000000000..4193fe4caa --- /dev/null +++ b/docs/examples/date-time/example1.html @@ -0,0 +1,23 @@ +
+ + + + + + + + + + + + + + + + +
Release 1.0.0Release 4.3.1Number of days between
+
\ No newline at end of file diff --git a/docs/examples/date-time/example1.js b/docs/examples/date-time/example1.js new file mode 100644 index 0000000000..01d646fecf --- /dev/null +++ b/docs/examples/date-time/example1.js @@ -0,0 +1,154 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; +import moment from 'moment'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/** + * Function defining the way HF should handle the provided date string. + * + * @param {string} dateString The date string. + * @param {string} dateFormat The date format. + * @returns {{month: *, year: *, day: *}} Object with date-related information. + */ +const customParseDate = (dateString, dateFormat) => { + const momentDate = moment(dateString, dateFormat, true); + + if (momentDate.isValid()) { + return { + year: momentDate.year(), + month: momentDate.month() + 1, + day: momentDate.date(), + }; + } +}; + +/** + * Date formatting function. + * + * @param {{month: *, year: *, day: *}} dateObject Object with date-related information. + * @returns {string} Formatted date string. + */ +const getFormattedDate = (dateObject) => { + dateObject.month -= 1; + + return moment(dateObject).format('MMM D YY'); +}; + +/** + * Initial table data. + */ +const tableData = [['Jan 31 00', 'Jun 2 01', '=B1-A1']]; +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + parseDateTime: customParseDate, + dateFormats: ['MMM D YY'], + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + const cellValue = displayValue(cellAddress, showFormula); + + newTbodyHTML += ` + ${cellValue} + `; + } + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Force the table to display either the formula, the value or a raw source data value. + * + * @param {SimpleCellAddress} cellAddress Cell address. + * @param {boolean} showFormula `true` if the formula should be visible. + */ +function displayValue(cellAddress, showFormula) { + // Declare which columns should display the raw source data, instead of the data from HyperFormula. + const sourceColumns = [0, 1]; + let cellValue = ''; + + if (sourceColumns.includes(cellAddress.col)) { + cellValue = getFormattedDate(hf.numberToDate(hf.getCellValue(cellAddress))); + } else { + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + } + + return cellValue; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/date-time/example1.ts b/docs/examples/date-time/example1.ts new file mode 100644 index 0000000000..b9fe3adde7 --- /dev/null +++ b/docs/examples/date-time/example1.ts @@ -0,0 +1,157 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; +import moment from 'moment'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/** + * Function defining the way HF should handle the provided date string. + * + * @param {string} dateString The date string. + * @param {string} dateFormat The date format. + * @returns {{month: *, year: *, day: *}} Object with date-related information. + */ +const customParseDate = (dateString, dateFormat) => { + const momentDate = moment(dateString, dateFormat, true); + + if (momentDate.isValid()) { + return { + year: momentDate.year(), + month: momentDate.month() + 1, + day: momentDate.date(), + }; + } +}; + +/** + * Date formatting function. + * + * @param {{month: *, year: *, day: *}} dateObject Object with date-related information. + * @returns {string} Formatted date string. + */ +const getFormattedDate = (dateObject) => { + dateObject.month -= 1; + + return moment(dateObject).format('MMM D YY'); +}; + +/** + * Initial table data. + */ +const tableData = [['Jan 31 00', 'Jun 2 01', '=B1-A1']]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + parseDateTime: customParseDate, + dateFormats: ['MMM D YY'], + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + const cellValue = displayValue(cellAddress, showFormula); + + newTbodyHTML += ` + ${cellValue} + `; + } + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Force the table to display either the formula, the value or a raw source data value. + * + * @param {SimpleCellAddress} cellAddress Cell address. + * @param {boolean} showFormula `true` if the formula should be visible. + */ +function displayValue(cellAddress, showFormula) { + // Declare which columns should display the raw source data, instead of the data from HyperFormula. + const sourceColumns = [0, 1]; + let cellValue = ''; + + if (sourceColumns.includes(cellAddress.col)) { + cellValue = getFormattedDate(hf.numberToDate(hf.getCellValue(cellAddress))); + } else { + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + } + + return cellValue; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/demo/example1.css b/docs/examples/demo/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/demo/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/demo/example1.html b/docs/examples/demo/example1.html new file mode 100644 index 0000000000..976656b64e --- /dev/null +++ b/docs/examples/demo/example1.html @@ -0,0 +1,27 @@ +
+ + + + + + + + + + + + + + + + + + + + +
NameYear_1Year_2AverageSum
+
diff --git a/docs/examples/demo/example1.js b/docs/examples/demo/example1.js new file mode 100644 index 0000000000..9921163782 --- /dev/null +++ b/docs/examples/demo/example1.js @@ -0,0 +1,132 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +const tableData = [ + ['Greg Black', 4.66, '=B1*1.3', '=AVERAGE(B1:C1)', '=SUM(B1:C1)'], + ['Anne Carpenter', 5.25, '=$B$2*30%', '=AVERAGE(B2:C2)', '=SUM(B2:C2)'], + ['Natalie Dem', 3.59, '=B3*2.7+2+1', '=AVERAGE(B3:C3)', '=SUM(B3:C3)'], + ['John Sieg', 12.51, '=B4*(1.22+1)', '=AVERAGE(B4:C4)', '=SUM(B4:C4)'], + [ + 'Chris Aklips', + 7.63, + '=B5*1.1*SUM(10,20)+1', + '=AVERAGE(B5:C5)', + '=SUM(B5:C5)', + ], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); +// Add named expressions for the "TOTAL" row. +hf.addNamedExpression('Year_1', '=SUM(main!$B$1:main!$B$5)'); +hf.addNamedExpression('Year_2', '=SUM(main!$C$1:main!$C$5)'); + +// Bind the events to the buttons. +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const totals = ['=SUM(Year_1)', '=SUM(Year_2)']; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + let totalRowsHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + + if (!isNaN(cellValue)) { + cellValue = cellValue.toFixed(2); + } + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + totalRowsHTML = ` +TOTAL + + ${ + calculated ? hf.calculateFormula(totals[0], sheetId).toFixed(2) : totals[0] + } + + + ${ + calculated ? hf.calculateFormula(totals[1], sheetId).toFixed(2) : totals[1] + } + + +`; + newTbodyHTML += totalRowsHTML; + tbodyDOM.innerHTML = newTbodyHTML; +} + +// Replace formulas with their results. +function runCalculations() { + renderTable(true); +} + +// Replace the values in the table with initial data. +function resetTable() { + renderTable(); +} + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/demo/example1.ts b/docs/examples/demo/example1.ts new file mode 100644 index 0000000000..15cf8ec0e1 --- /dev/null +++ b/docs/examples/demo/example1.ts @@ -0,0 +1,137 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +const tableData = [ + ['Greg Black', 4.66, '=B1*1.3', '=AVERAGE(B1:C1)', '=SUM(B1:C1)'], + ['Anne Carpenter', 5.25, '=$B$2*30%', '=AVERAGE(B2:C2)', '=SUM(B2:C2)'], + ['Natalie Dem', 3.59, '=B3*2.7+2+1', '=AVERAGE(B3:C3)', '=SUM(B3:C3)'], + ['John Sieg', 12.51, '=B4*(1.22+1)', '=AVERAGE(B4:C4)', '=SUM(B4:C4)'], + [ + 'Chris Aklips', + 7.63, + '=B5*1.1*SUM(10,20)+1', + '=AVERAGE(B5:C5)', + '=SUM(B5:C5)', + ], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +// Add named expressions for the "TOTAL" row. +hf.addNamedExpression('Year_1', '=SUM(main!$B$1:main!$B$5)'); +hf.addNamedExpression('Year_2', '=SUM(main!$C$1:main!$C$5)'); + +// Bind the events to the buttons. +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const totals = ['=SUM(Year_1)', '=SUM(Year_2)']; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + let totalRowsHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + + if (!isNaN(cellValue)) { + cellValue = cellValue.toFixed(2); + } + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + totalRowsHTML = ` +TOTAL + + ${ + calculated ? hf.calculateFormula(totals[0], sheetId).toFixed(2) : totals[0] + } + + + ${ + calculated ? hf.calculateFormula(totals[1], sheetId).toFixed(2) : totals[1] + } + + +`; + + newTbodyHTML += totalRowsHTML; + + tbodyDOM.innerHTML = newTbodyHTML; +} + +// Replace formulas with their results. +function runCalculations() { + renderTable(true); +} + +// Replace the values in the table with initial data. +function resetTable() { + renderTable(); +} + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/eslintrc.examples.js b/docs/examples/eslintrc.examples.js new file mode 100644 index 0000000000..aa4f35cb39 --- /dev/null +++ b/docs/examples/eslintrc.examples.js @@ -0,0 +1,60 @@ +const jsdocOff = Object.keys(require('eslint-plugin-jsdoc').rules) + .reduce((acc, rule) => { + acc[`jsdoc/${rule}`] = 'off'; + + return acc; + }, {}); + +module.exports = { + extends: ['../../.eslintrc.js', 'plugin:prettier/recommended'], + parserOptions: { + requireConfigFile: false + }, + rules: { + ...jsdocOff, + "prettier/prettier": [ + "error", + { + "singleQuote": true, + } + ], + 'no-restricted-syntax': 'off', + 'no-restricted-globals': 'off', + 'no-console': 'off', + 'no-await-in-loop': 'off', + 'no-unused-vars': 'off', + 'padding-line-between-statements': [ + 'error', + { blankLine: 'always', prev: '*', next: 'multiline-block-like' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + + // { blankLine: "always", prev: "*", next: "multiline-const" }, + { blankLine: 'always', prev: 'multiline-const', next: '*' }, + + // { blankLine: "always", prev: "*", next: "multiline-let" }, + { blankLine: 'always', prev: 'multiline-let', next: '*' }, + + // { blankLine: "always", prev: "*", next: "multiline-var" }, + { blankLine: 'always', prev: 'multiline-var', next: '*' }, + + { blankLine: 'always', prev: ['singleline-const', 'singleline-let', 'singleline-var'], next: '*' }, + { + blankLine: 'any', + prev: ['singleline-const', 'singleline-let', 'singleline-var'], + next: ['const', 'let', 'var'] + }, + + // { blankLine: "always", prev: "*", next: "multiline-expression" }, + { blankLine: 'always', prev: 'multiline-expression', next: '*' }, + + { blankLine: 'always', prev: 'expression', next: '*' }, + { blankLine: 'any', prev: 'expression', next: 'expression' }, + + { blankLine: 'always', prev: 'import', next: '*' }, + { blankLine: 'any', prev: 'import', next: 'import' }, + + { blankLine: 'always', prev: ['case', 'default'], next: '*' }, + { blankLine: 'any', prev: ['case', 'default'], next: ['case', 'default'] } + ] + } +}; diff --git a/docs/examples/i18n/example1.css b/docs/examples/i18n/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/i18n/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/i18n/example1.html b/docs/examples/i18n/example1.html new file mode 100644 index 0000000000..f92527c957 --- /dev/null +++ b/docs/examples/i18n/example1.html @@ -0,0 +1,27 @@ +
+ + + + + + + + + + + + + + + + + + + + +
NameLunch timeDate of BirthAgeSalary
+
\ No newline at end of file diff --git a/docs/examples/i18n/example1.js b/docs/examples/i18n/example1.js new file mode 100644 index 0000000000..f4fa847a40 --- /dev/null +++ b/docs/examples/i18n/example1.js @@ -0,0 +1,212 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; +import moment from 'moment'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/* start:skip-in-sandbox */ +const enUS = HyperFormula.languages.enUS; +/* end:skip-in-sandbox */ +/** + * Initial table data. + */ +const tableData = [ + [ + 'Greg Black', + '11:45 AM', + '05/23/1989', + '=YEAR(NOW())-YEAR(C1)', + '$80,000.00', + ], + [ + 'Anne Carpenter', + '12:30 PM', + '01/01/1980', + '=YEAR(NOW())-YEAR(C2)', + '$95,000.00', + ], + [ + 'Natalie Dem', + '1:30 PM', + '12/13/1973', + '=YEAR(NOW())-YEAR(C3)', + '$78,500.00', + ], + [ + 'John Sieg', + '2:00 PM', + '10/31/1995', + '=YEAR(NOW())-YEAR(C4)', + '$114,000.00', + ], + [ + 'Chris Aklips', + '11:30 AM', + '08/18/1987', + '=YEAR(NOW())-YEAR(C5)', + '$71,900.00', + ], + ['AVERAGE', null, null, '=AVERAGE(D1:D5)', '=AVERAGE(E1:E5)'], +]; + +const config = { + language: 'enUS', + dateFormats: ['MM/DD/YYYY', 'MM/DD/YY', 'YYYY/MM/DD'], + timeFormats: ['hh:mm', 'hh:mm:ss.sss'], + decimalSeparator: '.', + thousandSeparator: ',', + functionArgSeparator: ';', + currencySymbol: ['$', 'USD'], + localeLang: 'en-US', + licenseKey: 'gpl-v3', +}; + +HyperFormula.registerLanguage('enUS', enUS); + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty(config); +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +const columnTypes = ['string', 'time', 'date', 'number', 'currency']; + +/** + * Display value in human-readable format + * + * @param {SimpleCellAddress} cellAddress Cell address. + */ +function formatCellValue(cellAddress) { + if (hf.isCellEmpty(cellAddress)) { + return ''; + } + + if (columnTypes[cellAddress.col] === 'time') { + return formatTime(hf.numberToTime(hf.getCellValue(cellAddress))); + } + + if (columnTypes[cellAddress.col] === 'date') { + return formatDate(hf.numberToDate(hf.getCellValue(cellAddress))); + } + + if (columnTypes[cellAddress.col] === 'currency') { + return formatCurrency(hf.getCellValue(cellAddress)); + } + + return hf.getCellValue(cellAddress); +} + +/** + * Date formatting function. + * + * @param {{month: *, year: *, day: *}} dateObject Object with date-related information. + */ +function formatDate(dateObject) { + dateObject.month -= 1; + + return moment(dateObject).format('MM/DD/YYYY'); +} + +/** + * Time formatting function. + * + * @param dateTimeObject Object with date and time information. + */ +function formatTime(dateTimeObject) { + return moment(dateTimeObject).format('h:mm A'); +} + +/** + * Currency formatting function. + * + * @param value Number representing the currency value + */ +function formatCurrency(value) { + return value.toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + }); +} + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + newTbodyHTML += ``; + + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = cellHasFormula && !calculated; + const displayValue = showFormula + ? hf.getCellFormula(cellAddress) + : formatCellValue(cellAddress); + + newTbodyHTML += `${displayValue}`; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/i18n/example1.ts b/docs/examples/i18n/example1.ts new file mode 100644 index 0000000000..6767563972 --- /dev/null +++ b/docs/examples/i18n/example1.ts @@ -0,0 +1,217 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; +import enUS from 'hyperformula/es/i18n/languages/enUS'; +import moment from 'moment'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/* start:skip-in-sandbox */ +const enUS = HyperFormula.languages.enUS; +/* end:skip-in-sandbox */ + +/** + * Initial table data. + */ +const tableData = [ + [ + 'Greg Black', + '11:45 AM', + '05/23/1989', + '=YEAR(NOW())-YEAR(C1)', + '$80,000.00', + ], + [ + 'Anne Carpenter', + '12:30 PM', + '01/01/1980', + '=YEAR(NOW())-YEAR(C2)', + '$95,000.00', + ], + [ + 'Natalie Dem', + '1:30 PM', + '12/13/1973', + '=YEAR(NOW())-YEAR(C3)', + '$78,500.00', + ], + [ + 'John Sieg', + '2:00 PM', + '10/31/1995', + '=YEAR(NOW())-YEAR(C4)', + '$114,000.00', + ], + [ + 'Chris Aklips', + '11:30 AM', + '08/18/1987', + '=YEAR(NOW())-YEAR(C5)', + '$71,900.00', + ], + ['AVERAGE', null, null, '=AVERAGE(D1:D5)', '=AVERAGE(E1:E5)'], +]; + +const config = { + language: 'enUS', + dateFormats: ['MM/DD/YYYY', 'MM/DD/YY', 'YYYY/MM/DD'], + timeFormats: ['hh:mm', 'hh:mm:ss.sss'], + decimalSeparator: '.', + thousandSeparator: ',', + functionArgSeparator: ';', + currencySymbol: ['$', 'USD'], + localeLang: 'en-US', + licenseKey: 'gpl-v3', +}; + +HyperFormula.registerLanguage('enUS', enUS); + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty(config); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +const columnTypes = ['string', 'time', 'date', 'number', 'currency']; + +/** + * Display value in human-readable format + * + * @param {SimpleCellAddress} cellAddress Cell address. + */ +function formatCellValue(cellAddress) { + if (hf.isCellEmpty(cellAddress)) { + return ''; + } + + if (columnTypes[cellAddress.col] === 'time') { + return formatTime(hf.numberToTime(hf.getCellValue(cellAddress))); + } + + if (columnTypes[cellAddress.col] === 'date') { + return formatDate(hf.numberToDate(hf.getCellValue(cellAddress))); + } + + if (columnTypes[cellAddress.col] === 'currency') { + return formatCurrency(hf.getCellValue(cellAddress)); + } + + return hf.getCellValue(cellAddress); +} + +/** + * Date formatting function. + * + * @param {{month: *, year: *, day: *}} dateObject Object with date-related information. + */ +function formatDate(dateObject) { + dateObject.month -= 1; + + return moment(dateObject).format('MM/DD/YYYY'); +} + +/** + * Time formatting function. + * + * @param dateTimeObject Object with date and time information. + */ +function formatTime(dateTimeObject) { + return moment(dateTimeObject).format('h:mm A'); +} + +/** + * Currency formatting function. + * + * @param value Number representing the currency value + */ +function formatCurrency(value) { + return value.toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + }); +} + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + newTbodyHTML += ``; + + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = cellHasFormula && !calculated; + const displayValue = showFormula + ? hf.getCellFormula(cellAddress) + : formatCellValue(cellAddress); + + newTbodyHTML += `${displayValue}`; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/localizing-functions/example1.css b/docs/examples/localizing-functions/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/localizing-functions/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/localizing-functions/example1.html b/docs/examples/localizing-functions/example1.html new file mode 100644 index 0000000000..c5c58267f2 --- /dev/null +++ b/docs/examples/localizing-functions/example1.html @@ -0,0 +1,27 @@ +
+ + + + + + + + + + + + + + + + + + + + +
NameYear_1Year_2AverageSum
+
\ No newline at end of file diff --git a/docs/examples/localizing-functions/example1.js b/docs/examples/localizing-functions/example1.js new file mode 100644 index 0000000000..98d81e5b8c --- /dev/null +++ b/docs/examples/localizing-functions/example1.js @@ -0,0 +1,148 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/* start:skip-in-sandbox */ +const frFR = HyperFormula.languages.frFR; +/* end:skip-in-sandbox */ +/** + * Initial table data. + */ +const tableData = [ + ['Greg Black', 4.66, '=B1*1.3', '=MOYENNE(B1:C1)', '=SOMME(B1:C1)'], + ['Anne Carpenter', 5.25, '=$B$2*30%', '=MOYENNE(B2:C2)', '=SOMME(B2:C2)'], + ['Natalie Dem', 3.59, '=B3*2.7+2+1', '=MOYENNE(B3:C3)', '=SOMME(B3:C3)'], + ['John Sieg', 12.51, '=B4*(1.22+1)', '=MOYENNE(B4:C4)', '=SOMME(B4:C4)'], + [ + 'Chris Aklips', + 7.63, + '=B5*1.1*SUM(10,20)+1', + '=MOYENNE(B5:C5)', + '=SOMME(B5:C5)', + ], +]; + +// register language +HyperFormula.registerLanguage('frFR', frFR); + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + language: 'frFR', + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); +// Add named expressions for the "TOTAL" row. +hf.addNamedExpression('Year_1', '=SOMME(main!$B$1:main!$B$5)'); +hf.addNamedExpression('Year_2', '=SOMME(main!$C$1:main!$C$5)'); + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const totals = ['=SOMME(Year_1)', '=SOMME(Year_2)']; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + let totalRowsHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + + if (!isNaN(cellValue)) { + cellValue = cellValue.toFixed(2); + } + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + totalRowsHTML = ` +TOTAL + + ${ + calculated ? hf.calculateFormula(totals[0], sheetId).toFixed(2) : totals[0] + } + + + ${ + calculated ? hf.calculateFormula(totals[1], sheetId).toFixed(2) : totals[1] + } + + +`; + newTbodyHTML += totalRowsHTML; + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/localizing-functions/example1.ts b/docs/examples/localizing-functions/example1.ts new file mode 100644 index 0000000000..b224f3cb1d --- /dev/null +++ b/docs/examples/localizing-functions/example1.ts @@ -0,0 +1,155 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; +import frFR from 'hyperformula/es/i18n/languages/frFR'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/* start:skip-in-sandbox */ +const frFR = HyperFormula.languages.frFR; +/* end:skip-in-sandbox */ + +/** + * Initial table data. + */ +const tableData = [ + ['Greg Black', 4.66, '=B1*1.3', '=MOYENNE(B1:C1)', '=SOMME(B1:C1)'], + ['Anne Carpenter', 5.25, '=$B$2*30%', '=MOYENNE(B2:C2)', '=SOMME(B2:C2)'], + ['Natalie Dem', 3.59, '=B3*2.7+2+1', '=MOYENNE(B3:C3)', '=SOMME(B3:C3)'], + ['John Sieg', 12.51, '=B4*(1.22+1)', '=MOYENNE(B4:C4)', '=SOMME(B4:C4)'], + [ + 'Chris Aklips', + 7.63, + '=B5*1.1*SUM(10,20)+1', + '=MOYENNE(B5:C5)', + '=SOMME(B5:C5)', + ], +]; + +// register language +HyperFormula.registerLanguage('frFR', frFR); + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + language: 'frFR', + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +// Add named expressions for the "TOTAL" row. +hf.addNamedExpression('Year_1', '=SOMME(main!$B$1:main!$B$5)'); +hf.addNamedExpression('Year_2', '=SOMME(main!$C$1:main!$C$5)'); + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const totals = ['=SOMME(Year_1)', '=SOMME(Year_2)']; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + let totalRowsHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + + if (!isNaN(cellValue)) { + cellValue = cellValue.toFixed(2); + } + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + totalRowsHTML = ` +TOTAL + + ${ + calculated ? hf.calculateFormula(totals[0], sheetId).toFixed(2) : totals[0] + } + + + ${ + calculated ? hf.calculateFormula(totals[1], sheetId).toFixed(2) : totals[1] + } + + +`; + + newTbodyHTML += totalRowsHTML; + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/named-expressions/example1.css b/docs/examples/named-expressions/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/named-expressions/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/named-expressions/example1.html b/docs/examples/named-expressions/example1.html new file mode 100644 index 0000000000..a276542d8b --- /dev/null +++ b/docs/examples/named-expressions/example1.html @@ -0,0 +1,19 @@ +
+ + + + + + + + + + + + +
ABCD
+
diff --git a/docs/examples/named-expressions/example1.js b/docs/examples/named-expressions/example1.js new file mode 100644 index 0000000000..7917f72527 --- /dev/null +++ b/docs/examples/named-expressions/example1.js @@ -0,0 +1,121 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/** + * Initial table data. + */ +const tableData = [ + [10, 20, 20, 30], + [50, 60, 70, 80], + [90, 100, 110, 120], + ['=myOneCell', '=myTwoCells', '=myOneColumn', '=myTwoColumns'], + ['=myFormula+myNumber+34', '=myText', '=myOneRow', '=myTwoRows'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); +// Add named expressions +hf.addNamedExpression('myOneCell', '=main!$A$1'); +hf.addNamedExpression('myTwoCells', '=SUM(main!$A$1, main!$A$2)'); +hf.addNamedExpression('myOneColumn', '=SUM(main!$A$1:main!$A$3)'); +hf.addNamedExpression('myTwoColumns', '=SUM(main!$A$1:main!$B$3)'); +hf.addNamedExpression('myOneRow', '=SUM(main!$A$1:main!$D$1)'); +hf.addNamedExpression('myTwoRows', '=SUM(main!$A$1:main!$D$2)'); +hf.addNamedExpression('myFormula', '=SUM(0, 1, 1, 2, 3, 5, 8, 13)'); +hf.addNamedExpression('myNumber', '=21'); +hf.addNamedExpression('myText', 'Apollo 11'); + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/named-expressions/example1.ts b/docs/examples/named-expressions/example1.ts new file mode 100644 index 0000000000..594ecdaa07 --- /dev/null +++ b/docs/examples/named-expressions/example1.ts @@ -0,0 +1,125 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/** + * Initial table data. + */ +const tableData = [ + [10, 20, 20, 30], + [50, 60, 70, 80], + [90, 100, 110, 120], + ['=myOneCell', '=myTwoCells', '=myOneColumn', '=myTwoColumns'], + ['=myFormula+myNumber+34', '=myText', '=myOneRow', '=myTwoRows'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +// Add named expressions +hf.addNamedExpression('myOneCell', '=main!$A$1'); +hf.addNamedExpression('myTwoCells', '=SUM(main!$A$1, main!$A$2)'); +hf.addNamedExpression('myOneColumn', '=SUM(main!$A$1:main!$A$3)'); +hf.addNamedExpression('myTwoColumns', '=SUM(main!$A$1:main!$B$3)'); +hf.addNamedExpression('myOneRow', '=SUM(main!$A$1:main!$D$1)'); +hf.addNamedExpression('myTwoRows', '=SUM(main!$A$1:main!$D$2)'); +hf.addNamedExpression('myFormula', '=SUM(0, 1, 1, 2, 3, 5, 8, 13)'); +hf.addNamedExpression('myNumber', '=21'); +hf.addNamedExpression('myText', 'Apollo 11'); + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Replace formulas with their results. + */ +function runCalculations() { + renderTable(true); +} + +/** + * Replace the values in the table with initial data. + */ +function resetTable() { + renderTable(); +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const runButton = document.querySelector('.example #run'); + const resetButton = document.querySelector('.example #reset'); + + runButton.addEventListener('click', () => { + runCalculations(); + }); + + resetButton.addEventListener('click', () => { + resetTable(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/sorting-data/example1.css b/docs/examples/sorting-data/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/sorting-data/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/sorting-data/example1.html b/docs/examples/sorting-data/example1.html new file mode 100644 index 0000000000..27eec1da0c --- /dev/null +++ b/docs/examples/sorting-data/example1.html @@ -0,0 +1,18 @@ +
+ + + + + + + + + + + + +
Name + Score + +
+
diff --git a/docs/examples/sorting-data/example1.js b/docs/examples/sorting-data/example1.js new file mode 100644 index 0000000000..97c2bfe97f --- /dev/null +++ b/docs/examples/sorting-data/example1.js @@ -0,0 +1,148 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/** + * Initial table data. + */ +const tableData = [ + ['Greg Black', '100'], + ['Anne Carpenter', '=SUM(100,100)'], + ['Natalie Dem', '500'], + ['John Sieg', '50'], + ['Chris Aklips', '20'], + ['Bart Hoopoe', '700'], + ['Chris Site', '80'], + ['Agnes Whitey', '90'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +/** + * Sort the HF's dataset. + * + * @param {boolean} ascending `true` if sorting in ascending order, `false` otherwise. + * @param {Function} callback The callback function. + */ +function sort(ascending, callback) { + const rowCount = hf.getSheetDimensions(sheetId).height; + const colValues = []; + let newOrder = null; + const newOrderMapping = []; + + for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { + colValues.push({ + rowIndex, + value: hf.getCellValue({ + sheet: sheetId, + col: 1, + row: rowIndex, + }), + }); + } + + colValues.sort((objA, objB) => { + const delta = objA.value - objB.value; + + return ascending ? delta : -delta; + }); + newOrder = colValues.map((el) => el.rowIndex); + newOrder.forEach((orderIndex, arrIndex) => { + newOrderMapping[orderIndex] = arrIndex; + }); + hf.setRowOrder(sheetId, newOrderMapping); + callback(); +} + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +const doSortASC = () => { + sort(true, () => { + renderTable(true); + }); +}; + +const doSortDESC = () => { + sort(false, () => { + renderTable(true); + }); +}; + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const ascSort = document.querySelector('.example #asc'); + const descSort = document.querySelector('.example #desc'); + + ascSort.addEventListener('click', () => { + doSortASC(); + }); + descSort.addEventListener('click', () => { + doSortDESC(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/sorting-data/example1.ts b/docs/examples/sorting-data/example1.ts new file mode 100644 index 0000000000..8454d6382f --- /dev/null +++ b/docs/examples/sorting-data/example1.ts @@ -0,0 +1,154 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/** + * Initial table data. + */ +const tableData = [ + ['Greg Black', '100'], + ['Anne Carpenter', '=SUM(100,100)'], + ['Natalie Dem', '500'], + ['John Sieg', '50'], + ['Chris Aklips', '20'], + ['Bart Hoopoe', '700'], + ['Chris Site', '80'], + ['Agnes Whitey', '90'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +/** + * Sort the HF's dataset. + * + * @param {boolean} ascending `true` if sorting in ascending order, `false` otherwise. + * @param {Function} callback The callback function. + */ +function sort(ascending, callback) { + const rowCount = hf.getSheetDimensions(sheetId).height; + const colValues = []; + let newOrder = null; + const newOrderMapping = []; + + for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { + colValues.push({ + rowIndex, + value: hf.getCellValue({ + sheet: sheetId, + col: 1, + row: rowIndex, + }), + }); + } + + colValues.sort((objA, objB) => { + const delta = objA.value - objB.value; + + return ascending ? delta : -delta; + }); + + newOrder = colValues.map((el) => el.rowIndex); + + newOrder.forEach((orderIndex, arrIndex) => { + newOrderMapping[orderIndex] = arrIndex; + }); + + hf.setRowOrder(sheetId, newOrderMapping); + + callback(); +} + +/** + * Fill the HTML table with data. + * + * @param {boolean} calculated `true` if it should render calculated values, `false` otherwise. + */ +function renderTable(calculated = false) { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellHasFormula = hf.doesCellHaveFormula(cellAddress); + const showFormula = calculated || !cellHasFormula; + let cellValue = ''; + + if (!hf.isCellEmpty(cellAddress) && showFormula) { + cellValue = hf.getCellValue(cellAddress); + } else { + cellValue = hf.getCellFormula(cellAddress); + } + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +const doSortASC = () => { + sort(true, () => { + renderTable(true); + }); +}; + +const doSortDESC = () => { + sort(false, () => { + renderTable(true); + }); +}; + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const ascSort = document.querySelector('.example #asc'); + const descSort = document.querySelector('.example #desc'); + + ascSort.addEventListener('click', () => { + doSortASC(); + }); + + descSort.addEventListener('click', () => { + doSortDESC(); + }); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/examples/undo-redo/example1.css b/docs/examples/undo-redo/example1.css new file mode 100644 index 0000000000..224282eb7a --- /dev/null +++ b/docs/examples/undo-redo/example1.css @@ -0,0 +1,181 @@ +/* general */ +.example { + color: #606c76; + font-family: sans-serif; + font-size: 14px; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.example *, +.example *::before, +.example *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* buttons */ + +.example button { + border: 0.1em solid #1c49e4; + border-radius: .3em; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: .85em; + font-family: inherit; + font-weight: 700; + height: 3em; + letter-spacing: .1em; + line-height: 3em; + padding: 0 3em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + margin-bottom: 20px; + background-color: #1c49e4; +} + +.example button:hover { + background-color: #2350ea; +} + +.example button.outline { + background-color: transparent; + color: #1c49e4; +} + +/* labels */ + +.example label { + display: inline-block; + margin-left: 5px; +} + +/* inputs */ + +.example input:not([type='checkbox']), .example select, .example textarea, .example fieldset { + margin-bottom: 1.5em; + border: 0.1em solid #d1d1d1; + border-radius: .4em; + height: 3.8em; + width: 12em; + padding: 0 .5em; +} + +.example input:focus, +.example select:focus { + outline: none; + border-color: #1c49e4; +} + +/* message */ + +.example .message-box { + border: 1px solid #1c49e433; + background-color: #1c49e405; + border-radius: 0.2em; + padding: 10px; +} + +.example .message-box span { + animation-name: cell-appear; + animation-duration: 0.2s; + margin: 0; +} + +/* table */ + +.example table { + table-layout: fixed; + border-spacing: 0; + overflow-x: auto; + text-align: left; + width: 100%; + counter-reset: row-counter col-counter; +} + +.example table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.example table tr td, +.example table tr th { + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 0.1em solid #e1e1e1; + padding: 0 1em; + height: 3.5em; +} + +/* table: header row */ + +.example table thead tr th span::before { + display: inline-block; + width: 20px; +} + +.example table.spreadsheet thead tr th span::before { + content: counter(col-counter, upper-alpha); +} + +.example table.spreadsheet thead tr th { + counter-increment: col-counter; +} + +/* table: first column */ + +.example table tbody tr td:first-child { + text-align: center; + padding: 0; +} + +.example table thead tr th:first-child { + padding-left: 40px; +} + +.example table tbody tr td:first-child span { + width: 100%; + display: inline-block; + text-align: left; + padding-left: 15px; + margin-left: 0; +} + +.example table tbody tr td:first-child span::before { + content: counter(row-counter); + display: inline-block; + width: 20px; + position: relative; + left: -10px; +} + +.example table tbody tr { + counter-increment: row-counter; +} + +/* table: summary row */ + +.example table tbody tr.summary { + font-weight: 600; +} + +/* updated-cell animation */ + +.example table tr td.updated-cell span { + animation-name: cell-appear; + animation-duration: 0.6s; +} + +@keyframes cell-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/docs/examples/undo-redo/example1.html b/docs/examples/undo-redo/example1.html new file mode 100644 index 0000000000..7b084324d0 --- /dev/null +++ b/docs/examples/undo-redo/example1.html @@ -0,0 +1,22 @@ +
+ + +

 

+ + + + + + + + + + + + +
NameValue
+
\ No newline at end of file diff --git a/docs/examples/undo-redo/example1.js b/docs/examples/undo-redo/example1.js new file mode 100644 index 0000000000..9a3be5b486 --- /dev/null +++ b/docs/examples/undo-redo/example1.js @@ -0,0 +1,138 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); + +/* end:skip-in-compilation */ +/** + * Initial table data. + */ +const tableData = [ + ['Greg', '2'], + ['Chris', '4'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); +// Clear the undo stack to prevent undoing the initialization steps. +hf.clearUndoStack(); + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellValue = hf.getCellValue(cellAddress); + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Clear the existing information. + */ +function clearInfo() { + const infoBoxDOM = document.querySelector('.example #info-box'); + + infoBoxDOM.innerHTML = ' '; +} + +/** + * Display the provided message in the info box. + * + * @param {string} message Message to display. + */ +function displayInfo(message) { + const infoBoxDOM = document.querySelector('.example #info-box'); + + infoBoxDOM.innerText = message; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const removeRowButton = document.querySelector('.example #remove-row'); + const undoButton = document.querySelector('.example #undo'); + + removeRowButton.addEventListener('click', () => { + removeSecondRow(); + }); + undoButton.addEventListener('click', () => { + undo(); + }); +} + +/** + * Remove the second row from the table. + */ +function removeSecondRow() { + const filledRowCount = hf.getSheetDimensions(sheetId).height; + + clearInfo(); + + if (filledRowCount < 2) { + displayInfo("There's not enough filled rows to perform this action."); + + return; + } + + hf.removeRows(sheetId, [1, 1]); + renderTable(); +} + +/** + * Run the HF undo action. + */ +function undo() { + clearInfo(); + + if (!hf.isThereSomethingToUndo()) { + displayInfo("There's nothing to undo."); + + return; + } + + hf.undo(); + renderTable(); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); +// Render the table. +renderTable(); diff --git a/docs/examples/undo-redo/example1.ts b/docs/examples/undo-redo/example1.ts new file mode 100644 index 0000000000..3b03b80341 --- /dev/null +++ b/docs/examples/undo-redo/example1.ts @@ -0,0 +1,141 @@ +/* start:skip-in-compilation */ +import HyperFormula from 'hyperformula'; + +console.log( + `%c Using HyperFormula ${HyperFormula.version}`, + 'color: blue; font-weight: bold' +); +/* end:skip-in-compilation */ + +/** + * Initial table data. + */ +const tableData = [ + ['Greg', '2'], + ['Chris', '4'], +]; + +// Create an empty HyperFormula instance. +const hf = HyperFormula.buildEmpty({ + licenseKey: 'gpl-v3', +}); + +// Add a new sheet and get its id. +const sheetName = hf.addSheet('main'); +const sheetId = hf.getSheetId(sheetName); + +// Fill the HyperFormula sheet with data. +hf.setCellContents( + { + row: 0, + col: 0, + sheet: sheetId, + }, + tableData +); + +// Clear the undo stack to prevent undoing the initialization steps. +hf.clearUndoStack(); + +/** + * Fill the HTML table with data. + */ +function renderTable() { + const tbodyDOM = document.querySelector('.example tbody'); + const updatedCellClass = ANIMATION_ENABLED ? 'updated-cell' : ''; + const { height, width } = hf.getSheetDimensions(sheetId); + let newTbodyHTML = ''; + + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const cellAddress = { sheet: sheetId, col, row }; + const cellValue = hf.getCellValue(cellAddress); + + newTbodyHTML += ` + ${cellValue} + `; + } + + newTbodyHTML += ''; + } + + tbodyDOM.innerHTML = newTbodyHTML; +} + +/** + * Clear the existing information. + */ +function clearInfo() { + const infoBoxDOM = document.querySelector('.example #info-box'); + + infoBoxDOM.innerHTML = ' '; +} + +/** + * Display the provided message in the info box. + * + * @param {string} message Message to display. + */ +function displayInfo(message) { + const infoBoxDOM = document.querySelector('.example #info-box'); + + infoBoxDOM.innerText = message; +} + +/** + * Bind the events to the buttons. + */ +function bindEvents() { + const removeRowButton = document.querySelector('.example #remove-row'); + const undoButton = document.querySelector('.example #undo'); + + removeRowButton.addEventListener('click', () => { + removeSecondRow(); + }); + + undoButton.addEventListener('click', () => { + undo(); + }); +} + +/** + * Remove the second row from the table. + */ +function removeSecondRow() { + const filledRowCount = hf.getSheetDimensions(sheetId).height; + + clearInfo(); + + if (filledRowCount < 2) { + displayInfo("There's not enough filled rows to perform this action."); + + return; + } + + hf.removeRows(sheetId, [1, 1]); + renderTable(); +} + +/** + * Run the HF undo action. + */ +function undo() { + clearInfo(); + + if (!hf.isThereSomethingToUndo()) { + displayInfo("There's nothing to undo."); + + return; + } + + hf.undo(); + renderTable(); +} + +const ANIMATION_ENABLED = true; + +// Bind the button events. +bindEvents(); + +// Render the table. +renderTable(); diff --git a/docs/guide/advanced-usage.md b/docs/guide/advanced-usage.md index 4a8300f386..7ccb1f64c1 100644 --- a/docs/guide/advanced-usage.md +++ b/docs/guide/advanced-usage.md @@ -121,7 +121,14 @@ console.log(winningTeam) ## Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/advanced-usage/example1.html) + +@[code](@/docs/examples/advanced-usage/example1.css) + +@[code](@/docs/examples/advanced-usage/example1.js) + +@[code](@/docs/examples/advanced-usage/example1.ts) + +::: diff --git a/docs/guide/basic-operations.md b/docs/guide/basic-operations.md index 9b689c6685..591308df18 100644 --- a/docs/guide/basic-operations.md +++ b/docs/guide/basic-operations.md @@ -403,10 +403,16 @@ console.log(changes); ## Demo -This demo presents several basic operations integrated with a -sample UI. +This demo presents several basic operations integrated with a sample UI. - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/basic-operations/example1.html) + +@[code](@/docs/examples/basic-operations/example1.css) + +@[code](@/docs/examples/basic-operations/example1.js) + +@[code](@/docs/examples/basic-operations/example1.ts) + +::: diff --git a/docs/guide/basic-usage.md b/docs/guide/basic-usage.md index 16372888ee..d1b392c9ec 100644 --- a/docs/guide/basic-usage.md +++ b/docs/guide/basic-usage.md @@ -64,7 +64,14 @@ works. It's time to move on to a more ## Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/basic-usage/example1.html) + +@[code](@/docs/examples/basic-usage/example1.css) + +@[code](@/docs/examples/basic-usage/example1.js) + +@[code](@/docs/examples/basic-usage/example1.ts) + +::: diff --git a/docs/guide/batch-operations.md b/docs/guide/batch-operations.md index 835e2087e4..54596320ae 100644 --- a/docs/guide/batch-operations.md +++ b/docs/guide/batch-operations.md @@ -123,7 +123,14 @@ The [paste](../api/classes/hyperformula.md#paste) method also can't be called wh ## Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/batch-operations/example1.html) + +@[code](@/docs/examples/batch-operations/example1.css) + +@[code](@/docs/examples/batch-operations/example1.js) + +@[code](@/docs/examples/batch-operations/example1.ts) + +::: diff --git a/docs/guide/clipboard-operations.md b/docs/guide/clipboard-operations.md index bf6ae78bd8..0360f0afd5 100644 --- a/docs/guide/clipboard-operations.md +++ b/docs/guide/clipboard-operations.md @@ -112,7 +112,14 @@ Depending on what was cut, the data is stored as: ## Demo - \ No newline at end of file +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/clipboard-operations/example1.html) + +@[code](@/docs/examples/clipboard-operations/example1.css) + +@[code](@/docs/examples/clipboard-operations/example1.js) + +@[code](@/docs/examples/clipboard-operations/example1.ts) + +::: \ No newline at end of file diff --git a/docs/guide/custom-functions.md b/docs/guide/custom-functions.md index 9726b2a40a..14ace0edd4 100644 --- a/docs/guide/custom-functions.md +++ b/docs/guide/custom-functions.md @@ -363,8 +363,11 @@ This demo contains the implementation of both the [`DOUBLE_RANGE`](#advanced-custom-function-example) custom functions. ## Function options diff --git a/docs/guide/date-and-time-handling.md b/docs/guide/date-and-time-handling.md index b84ef18890..98ad1fb0cf 100644 --- a/docs/guide/date-and-time-handling.md +++ b/docs/guide/date-and-time-handling.md @@ -98,7 +98,14 @@ And now, HyperFormula recognizes these values as valid dates and can operate on ## Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/date-time/example1.html) + +@[code](@/docs/examples/date-time/example1.css) + +@[code](@/docs/examples/date-time/example1.js) + +@[code](@/docs/examples/date-time/example1.ts) + +::: diff --git a/docs/guide/demo.md b/docs/guide/demo.md index 25c092022f..1d4e754a42 100644 --- a/docs/guide/demo.md +++ b/docs/guide/demo.md @@ -1,9 +1,16 @@ # Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/demo/example1.html) + +@[code](@/docs/examples/demo/example1.css) + +@[code](@/docs/examples/demo/example1.js) + +@[code](@/docs/examples/demo/example1.ts) + +::: In this demo, you can see how HyperFormula handles basic operations by using API methods, such as: diff --git a/docs/guide/i18n-features.md b/docs/guide/i18n-features.md index 1fab319ac4..90eba856c0 100644 --- a/docs/guide/i18n-features.md +++ b/docs/guide/i18n-features.md @@ -105,7 +105,14 @@ localeLang: 'en-US', This demo shows HyperFormula configured for the `en-US` locale. - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/i18n/example1.html) + +@[code](@/docs/examples/i18n/example1.css) + +@[code](@/docs/examples/i18n/example1.js) + +@[code](@/docs/examples/i18n/example1.ts) + +::: diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 0eb483004b..49dfdd17a2 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -7,6 +7,9 @@ For more details, see the [client-side installation](client-side-installation.md ## Demo diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index d58de06e04..418dc190a4 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -7,6 +7,9 @@ For more details, see the [client-side installation](client-side-installation.md ## Demo diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md index c2488e6bfb..df9b9f5b86 100644 --- a/docs/guide/integration-with-svelte.md +++ b/docs/guide/integration-with-svelte.md @@ -7,6 +7,9 @@ For more details, see the [client-side installation](client-side-installation.md ## Demo diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md index 62d4f36d8a..baf9b8bda0 100644 --- a/docs/guide/integration-with-vue.md +++ b/docs/guide/integration-with-vue.md @@ -11,6 +11,9 @@ This demo uses the [Vue 3](https://v3.vuejs.org/) framework. If you are looking ::: \ No newline at end of file + :src="`https://codesandbox.io/embed/github/handsontable/hyperformula-demos/tree/2.7.x/vue-3-demo?autoresize=1&fontsize=11&hidenavigation=1&theme=light&view=preview&v=${$page.buildDateURIEncoded}`" + style="width:100%; height:1070px; border:0; border-radius: 4px; overflow:hidden;" + title="handsontable/hyperformula-demos: react-demo" + allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" + sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"> + diff --git a/docs/guide/localizing-functions.md b/docs/guide/localizing-functions.md index fdf5946f02..4622b6c9c5 100644 --- a/docs/guide/localizing-functions.md +++ b/docs/guide/localizing-functions.md @@ -96,7 +96,14 @@ You can localize your custom functions as well. For details, see the [Custom fun ## Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/localizing-functions/example1.html) + +@[code](@/docs/examples/localizing-functions/example1.css) + +@[code](@/docs/examples/localizing-functions/example1.js) + +@[code](@/docs/examples/localizing-functions/example1.ts) + +::: diff --git a/docs/guide/named-expressions.md b/docs/guide/named-expressions.md index 8284ecff66..9c34c196cc 100644 --- a/docs/guide/named-expressions.md +++ b/docs/guide/named-expressions.md @@ -149,7 +149,14 @@ described in [that section](basic-operations.md#isitpossibleto-methods). ## Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/named-expressions/example1.html) + +@[code](@/docs/examples/named-expressions/example1.css) + +@[code](@/docs/examples/named-expressions/example1.js) + +@[code](@/docs/examples/named-expressions/example1.ts) + +::: diff --git a/docs/guide/sorting-data.md b/docs/guide/sorting-data.md index 1eaaa1dc0d..8faedd3ca9 100644 --- a/docs/guide/sorting-data.md +++ b/docs/guide/sorting-data.md @@ -195,7 +195,14 @@ if (!isColumnOrderOk) { The demo below shows how to sort rows in ascending and descending order, based on the results (calculated values) of the cells in the second column. - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/sorting-data/example1.html) + +@[code](@/docs/examples/sorting-data/example1.css) + +@[code](@/docs/examples/sorting-data/example1.js) + +@[code](@/docs/examples/sorting-data/example1.ts) + +::: diff --git a/docs/guide/undo-redo.md b/docs/guide/undo-redo.md index c34b5f8849..dceb482025 100644 --- a/docs/guide/undo-redo.md +++ b/docs/guide/undo-redo.md @@ -25,7 +25,14 @@ that undo-redo will recognize them as a single cumulative operation. ## Demo - +::: example #example1 --html 1 --css 2 --js 3 --ts 4 + +@[code](@/docs/examples/undo-redo/example1.html) + +@[code](@/docs/examples/undo-redo/example1.css) + +@[code](@/docs/examples/undo-redo/example1.js) + +@[code](@/docs/examples/undo-redo/example1.ts) + +::: diff --git a/package-lock.json b/package-lock.json index 0b9dea852c..2391e83b5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,17 @@ "devDependencies": { "@babel/cli": "^7.8.4", "@babel/core": "^7.8.4", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-decorators": "^7.13.5", + "@babel/plugin-proposal-private-methods": "^7.13.0", + "@babel/plugin-proposal-private-property-in-object": "^7.13.0", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", "@babel/plugin-transform-runtime": "^7.8.3", + "@babel/plugin-transform-typescript": "^7.24.7", "@babel/preset-env": "^7.8.4", + "@babel/preset-react": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", "@babel/register": "^7.9.0", "@babel/runtime": "^7.18.9", "@microsoft/tsdoc": "^0.12.16", @@ -35,8 +44,10 @@ "cross-env": "^7.0.0", "env-cmd": "^10.1.0", "eslint": "^7.0.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-jsdoc": "^43.1.1", "eslint-plugin-license-header": "^0.6.0", + "eslint-plugin-prettier": "^4.2.1", "full-icu": "^1.3.1", "jasmine": "^4.0.0", "jest": "^26.0.0", @@ -58,6 +69,7 @@ "serve": "^14.2.0", "string-replace-loader": "^2.3.0", "tar": "^6.0.1", + "terser-webpack-plugin": "^4.2.3", "ts-jest": "^26.0.0", "ts-loader": "^7.0.2", "ts-node": "^8.0.1", @@ -167,12 +179,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", - "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.9", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -223,19 +235,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz", - "integrity": "sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", "@babel/helper-member-expression-to-functions": "^7.24.8", "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/traverse": "^7.25.4", "semver": "^6.3.1" }, "engines": { @@ -399,14 +409,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", - "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", - "@babel/helper-optimise-call-expression": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -524,10 +534,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", - "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -633,11 +646,35 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -906,6 +943,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", @@ -1523,6 +1575,71 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", + "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz", + "integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-regenerator": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", @@ -1650,6 +1767,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", @@ -1808,6 +1944,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", @@ -1822,6 +1970,45 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-react": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", + "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.24.7", + "@babel/plugin-transform-react-jsx-development": "^7.24.7", + "@babel/plugin-transform-react-pure-annotations": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/register": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", @@ -1860,33 +2047,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", - "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.8", - "@babel/types": "^7.24.8", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1895,9 +2079,9 @@ } }, "node_modules/@babel/types": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", - "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -2059,6 +2243,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -2893,6 +3083,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -2970,6 +3170,54 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -3960,39 +4208,39 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.32.tgz", - "integrity": "sha512-8tCVWkkLe/QCWIsrIvExUGnhYCAOroUs5dzhSoKL5w4MJS8uIYiou+pOPSVIOALOQ80B0jBs+Ri+kd5+MBnCDw==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.33.tgz", + "integrity": "sha512-MoIREbkdPQlnGfSKDMgzTqzqx5nmEjIc0ydLVYlTACGBsfvOJ4tHSbZXKVF536n6fB+0eZaGEOqsGThPpdvF5A==", "dev": true, "dependencies": { "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.32", + "@vue/shared": "3.4.33", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.32.tgz", - "integrity": "sha512-PbSgt9KuYo4fyb90dynuPc0XFTfFPs3sCTbPLOLlo+PrUESW1gn/NjSsUvhR+mI2AmmEzexwYMxbHDldxSOr2A==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.33.tgz", + "integrity": "sha512-GzB8fxEHKw0gGet5BKlpfXEqoBnzSVWwMnT+dc25wE7pFEfrU/QsvjZMP9rD4iVXHBBoemTct8mN0GJEI6ZX5A==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.4.32", - "@vue/shared": "3.4.32" + "@vue/compiler-core": "3.4.33", + "@vue/shared": "3.4.33" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.32.tgz", - "integrity": "sha512-STy9im/WHfaguJnfKjjVpMHukxHUrOKjm2vVCxiojQJyo3Sb6Os8SMXBr/MI+ekpstEGkDONfqAQoSbZhspLYw==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.33.tgz", + "integrity": "sha512-7rk7Vbkn21xMwIUpHQR4hCVejwE6nvhBOiDgoBcR03qvGqRKA7dCBSsHZhwhYUsmjlbJ7OtD5UFIyhP6BY+c8A==", "dev": true, "dependencies": { "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.32", - "@vue/compiler-dom": "3.4.32", - "@vue/compiler-ssr": "3.4.32", - "@vue/shared": "3.4.32", + "@vue/compiler-core": "3.4.33", + "@vue/compiler-dom": "3.4.33", + "@vue/compiler-ssr": "3.4.33", + "@vue/shared": "3.4.33", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.39", @@ -4000,13 +4248,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.32.tgz", - "integrity": "sha512-nyu/txTecF6DrxLrpLcI34xutrvZPtHPBj9yRoPxstIquxeeyywXpYZrQMsIeDfBhlw1abJb9CbbyZvDw2kjdg==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.33.tgz", + "integrity": "sha512-0WveC9Ai+eT/1b6LCV5IfsufBZ0HP7pSSTdDjcuW302tTEgoBw8rHVHKPbGUtzGReUFCRXbv6zQDDgucnV2WzQ==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.32", - "@vue/shared": "3.4.32" + "@vue/compiler-dom": "3.4.33", + "@vue/shared": "3.4.33" } }, "node_modules/@vue/component-compiler-utils": { @@ -4068,9 +4316,9 @@ "dev": true }, "node_modules/@vue/shared": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.32.tgz", - "integrity": "sha512-ep4mF1IVnX/pYaNwxwOpJHyBtOMKWoKZMbnUyd+z0udqIxLUh7YCCd/JfDna8aUrmnG9SFORyIq2HzEATRrQsg==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.33.tgz", + "integrity": "sha512-aoRY0jQk3A/cuvdkodTrM4NMfxco8n55eG4H7ML/CRy7OryHfiqvug4xrCBBMbbN+dvXAetDDwZW9DXWWjBntA==", "dev": true }, "node_modules/@vuepress/core": { @@ -4259,25 +4507,6 @@ "node": ">=0.10.0" } }, - "node_modules/@vuepress/core/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/@vuepress/core/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -5324,6 +5553,19 @@ "node": ">= 0.10.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -6747,47 +6989,76 @@ } }, "node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", "dev": true, "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" } }, - "node_modules/cacache/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/cacache/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "yallist": "^4.0.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=10" } }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -7018,9 +7289,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001642", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", - "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "version": "1.0.30001643", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", + "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", "dev": true, "funding": [ { @@ -7262,6 +7533,15 @@ "node": ">= 4.0" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -7857,6 +8137,35 @@ "node": ">=0.10.0" } }, + "node_modules/copy-webpack-plugin/node_modules/cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/copy-webpack-plugin/node_modules/dir-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", @@ -7961,6 +8270,19 @@ "node": ">=4" } }, + "node_modules/copy-webpack-plugin/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/copy-webpack-plugin/node_modules/schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -7975,6 +8297,15 @@ "node": ">= 4" } }, + "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/copy-webpack-plugin/node_modules/slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -7984,6 +8315,15 @@ "node": ">=0.10.0" } }, + "node_modules/copy-webpack-plugin/node_modules/ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "dependencies": { + "figgy-pudding": "^3.5.1" + } + }, "node_modules/core-js": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", @@ -9067,6 +9407,15 @@ "node": ">=0.10.0" } }, + "node_modules/del/node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/del/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -9465,9 +9814,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.829", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz", - "integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz", + "integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==", "dev": true }, "node_modules/elliptic": { @@ -9874,201 +10223,6 @@ "esbuild-windows-arm64": "0.14.7" } }, - "node_modules/esbuild-android-arm64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.7.tgz", - "integrity": "sha512-9/Q1NC4JErvsXzJKti0NHt+vzKjZOgPIjX/e6kkuCzgfT/GcO3FVBcGIv4HeJG7oMznE6KyKhvLrFgt7CdU2/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/esbuild-darwin-64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.7.tgz", - "integrity": "sha512-Z9X+3TT/Xj+JiZTVlwHj2P+8GoiSmUnGVz0YZTSt8WTbW3UKw5Pw2ucuJ8VzbD2FPy0jbIKJkko/6CMTQchShQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.7.tgz", - "integrity": "sha512-68e7COhmwIiLXBEyxUxZSSU0akgv8t3e50e2QOtKdBUE0F6KIRISzFntLe2rYlNqSsjGWsIO6CCc9tQxijjSkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.7.tgz", - "integrity": "sha512-76zy5jAjPiXX/S3UvRgG85Bb0wy0zv/J2lel3KtHi4V7GUTBfhNUPt0E5bpSXJ6yMT7iThhnA5rOn+IJiUcslQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.7.tgz", - "integrity": "sha512-lSlYNLiqyzd7qCN5CEOmLxn7MhnGHPcu5KuUYOG1i+t5A6q7LgBmfYC9ZHJBoYyow3u4CNu79AWHbvVLpE/VQQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/esbuild-linux-32": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.7.tgz", - "integrity": "sha512-Vk28u409wVOXqTaT6ek0TnfQG4Ty1aWWfiysIaIRERkNLhzLhUf4i+qJBN8mMuGTYOkE40F0Wkbp6m+IidOp2A==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/esbuild-linux-64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.7.tgz", - "integrity": "sha512-+Lvz6x+8OkRk3K2RtZwO+0a92jy9si9cUea5Zoru4yJ/6EQm9ENX5seZE0X9DTwk1dxJbjmLsJsd3IoowyzgVg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/esbuild-linux-arm": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.7.tgz", - "integrity": "sha512-OzpXEBogbYdcBqE4uKynuSn5YSetCvK03Qv1HcOY1VN6HmReuatjJ21dCH+YPHSpMEF0afVCnNfffvsGEkxGJQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.7.tgz", - "integrity": "sha512-kJd5beWSqteSAW086qzCEsH6uwpi7QRIpzYWHzEYwKKu9DiG1TwIBegQJmLpPsLp4v5RAFjea0JAmAtpGtRpqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.7.tgz", - "integrity": "sha512-mFWpnDhZJmj/h7pxqn1GGDsKwRfqtV7fx6kTF5pr4PfXe8pIaTERpwcKkoCwZUkWAOmUEjMIUAvFM72A6hMZnA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.7.tgz", - "integrity": "sha512-wM7f4M0bsQXfDL4JbbYD0wsr8cC8KaQ3RPWc/fV27KdErPW7YsqshZZSjDV0kbhzwpNNdhLItfbaRT8OE8OaKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.7.tgz", - "integrity": "sha512-J/afS7woKyzGgAL5FlgvMyqgt5wQ597lgsT+xc2yJ9/7BIyezeXutXqfh05vszy2k3kSvhLesugsxIA71WsqBw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ] - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.7.tgz", - "integrity": "sha512-7CcxgdlCD+zAPyveKoznbgr3i0Wnh0L8BDGRCjE/5UGkm5P/NQko51tuIDaYof8zbmXjjl0OIt9lSo4W7I8mrw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/esbuild-sunos-64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.7.tgz", - "integrity": "sha512-GKCafP2j/KUljVC3nesw1wLFSZktb2FGCmoT1+730zIF5O6hNroo0bSEofm6ZK5mNPnLiSaiLyRB9YFgtkd5Xg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ] - }, - "node_modules/esbuild-windows-32": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.7.tgz", - "integrity": "sha512-5I1GeL/gZoUUdTPA0ws54bpYdtyeA2t6MNISalsHpY269zK8Jia/AXB3ta/KcDHv2SvNwabpImeIPXC/k0YW6A==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/esbuild-windows-64": { "version": "0.14.7", "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.7.tgz", @@ -10082,19 +10236,6 @@ "win32" ] }, - "node_modules/esbuild-windows-arm64": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.7.tgz", - "integrity": "sha512-eOs1eSivOqN7cFiRIukEruWhaCf75V0N8P0zP7dh44LIhLl8y6/z++vv9qQVbkBm5/D7M7LfCfCTmt1f1wHOCw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -10215,6 +10356,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-plugin-jsdoc": { "version": "43.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-43.2.0.tgz", @@ -10270,6 +10423,27 @@ "requireindex": "^1.2.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -10959,6 +11133,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -11591,20 +11771,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/full-icu": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/full-icu/-/full-icu-1.5.0.tgz", @@ -12808,9 +12974,9 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "dependencies": { "pkg-dir": "^4.2.0", @@ -12847,6 +13013,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/indexes-of": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", @@ -13686,9 +13861,9 @@ } }, "node_modules/jasmine-core": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", - "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.2.0.tgz", + "integrity": "sha512-tSAtdrvWybZkQmmaIoDgnvHG8ORUNw5kEVlO5CvrXj02Jjr9TZrmjFq7FUiOUzJiOP2wLGYT6PgrQgQF4R1xiw==", "dev": true, "peer": true }, @@ -16831,6 +17006,96 @@ "node": ">=8" } }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -17223,9 +17488,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", - "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/nopt": { @@ -17802,12 +18067,18 @@ } }, "node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-retry": { @@ -19738,7 +20009,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, - "optional": true, "bin": { "prettier": "bin-prettier.js" }, @@ -19749,6 +20019,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-error": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", @@ -21543,9 +21825,9 @@ } }, "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -22478,14 +22760,35 @@ } }, "node_modules/ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "dependencies": { - "figgy-pudding": "^3.5.1" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, + "node_modules/ssri/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -23232,66 +23535,137 @@ } }, "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "version": "5.31.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.3.tgz", + "integrity": "sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, "node_modules/terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", "dev": true, "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.5.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" + "terser": "^5.3.4", + "webpack-sources": "^1.4.3" }, "engines": { - "node": ">= 6.9.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.0.0" + "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/terser-webpack-plugin/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "node_modules/terser-webpack-plugin/node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser-webpack-plugin/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser-webpack-plugin/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">= 4" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/terser/node_modules/commander": { @@ -25371,25 +25745,6 @@ "node": ">=0.10.0" } }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/watchpack-chokidar2/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -26206,25 +26561,6 @@ "node": ">=6" } }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/webpack-dev-server/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -26705,6 +27041,41 @@ "node": ">=0.10.0" } }, + "node_modules/webpack/node_modules/cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "node_modules/webpack/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/webpack/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/webpack/node_modules/define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -26795,6 +27166,15 @@ "node": ">=0.10.0" } }, + "node_modules/webpack/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/webpack/node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -26868,6 +27248,19 @@ "node": ">=0.10.0" } }, + "node_modules/webpack/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/webpack/node_modules/schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -26882,6 +27275,64 @@ "node": ">= 4" } }, + "node_modules/webpack/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/webpack/node_modules/ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "dependencies": { + "figgy-pudding": "^3.5.1" + } + }, + "node_modules/webpack/node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/webpack/node_modules/terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "dependencies": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, "node_modules/webpack/node_modules/to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -27357,6 +27808,18 @@ "node": ">=6" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zepto": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/zepto/-/zepto-1.2.0.tgz", diff --git a/package.json b/package.json index aeb0c964f9..0a84f1de7d 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,9 @@ "scripts": { "docs:dev": "npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress dev docs --silent --no-clear-screen --no-cache", "docs:build": "npm run bundle-all && npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress build docs", + "docs:code-examples:generate-js": "bash docs/code-examples-generator.sh", + "docs:code-examples:generate-all-js": "bash docs/code-examples-generator.sh --generateAll", + "docs:code-examples:format-all-ts": "bash docs/code-examples-generator.sh --formatAllTsExamples", "bundle-all": "cross-env HF_COMPILE=1 npm-run-all clean compile bundle:** verify-bundles", "bundle:es": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=es env-cmd -f ht.config.js babel lib --out-dir es", "bundle:cjs": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=commonjs env-cmd -f ht.config.js babel lib --out-dir commonjs", @@ -103,6 +106,15 @@ "@babel/preset-env": "^7.8.4", "@babel/register": "^7.9.0", "@babel/runtime": "^7.18.9", + "@babel/preset-react": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-proposal-decorators": "^7.13.5", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-private-methods": "^7.13.0", + "@babel/plugin-proposal-private-property-in-object": "^7.13.0", + "@babel/plugin-transform-typescript": "^7.24.7", "@microsoft/tsdoc": "^0.12.16", "@types/jasmine": "^4.0.0", "@types/jest": "^26.0.0", @@ -119,8 +131,10 @@ "cross-env": "^7.0.0", "env-cmd": "^10.1.0", "eslint": "^7.0.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-jsdoc": "^43.1.1", "eslint-plugin-license-header": "^0.6.0", + "eslint-plugin-prettier": "^4.2.1", "full-icu": "^1.3.1", "jasmine": "^4.0.0", "jest": "^26.0.0", @@ -142,6 +156,7 @@ "serve": "^14.2.0", "string-replace-loader": "^2.3.0", "tar": "^6.0.1", + "terser-webpack-plugin": "^4.2.3", "ts-jest": "^26.0.0", "ts-loader": "^7.0.2", "ts-node": "^8.0.1", diff --git a/script/check-file.js b/script/check-file.js index 4d2cde24df..151902739a 100644 --- a/script/check-file.js +++ b/script/check-file.js @@ -1,5 +1,6 @@ const { resolve } = require('path') const assert = require('assert') +const fs = require('fs') const htConfig = require('../ht.config') const [ /* node bin */ , /* path to this script */ , fileToCheck] = process.argv; @@ -29,6 +30,13 @@ try { assert(valueA1 === 42) assert(valueB1 === 44) + // Check if the file contains no redundant license comments + if (fileToCheck.includes('.js')) { + const fileContent = fs.readFileSync(resolve(fileToCheck), 'utf8') + const licenseComments = fileContent.match(/@license/g) + assert.equal(licenseComments.length, 2) + } + console.log(`Bundle check: \u001b[1;37m${fileToCheck}\u001b[0m \u001b[0;32mOK\u001b[0m`) } catch (ex) { console.log(`Bundle check: \u001b[1;37m${fileToCheck}\u001b[0m \u001b[0;31mERROR\u001b[0m`) From 9a36fae0f8147a0f8f8d7a412bef0a94282a630e Mon Sep 17 00:00:00 2001 From: Kenton Gray Date: Fri, 22 Nov 2024 12:36:57 -0600 Subject: [PATCH 2/2] Fix Typo in docs --- docs/guide/compatibility-with-microsoft-excel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/compatibility-with-microsoft-excel.md b/docs/guide/compatibility-with-microsoft-excel.md index 9f96283d4c..65f252f4e1 100644 --- a/docs/guide/compatibility-with-microsoft-excel.md +++ b/docs/guide/compatibility-with-microsoft-excel.md @@ -1,6 +1,6 @@ # Compatibility with Microsoft Excel -Achieve nearly full compatibility wih Microsoft Excel, using the right HyperFormula configuration. +Achieve nearly full compatibility with Microsoft Excel, using the right HyperFormula configuration. **Contents:** [[toc]]