├── .npmignore ├── .tool-versions ├── pnpm-workspace.yaml ├── packages ├── docs │ ├── .gitignore │ ├── pages │ │ ├── index.mdx │ │ ├── history │ │ │ ├── migration-guide │ │ │ │ └── _meta.json │ │ │ ├── _meta.json │ │ │ └── 2.x.x.mdx │ │ ├── development │ │ │ ├── _meta.json │ │ │ └── package-structure.mdx │ │ ├── getting-started │ │ │ ├── _meta.json │ │ │ ├── react.mdx │ │ │ ├── vanilla.mdx │ │ │ ├── index.mdx │ │ │ └── vue.mdx │ │ ├── api-reference │ │ │ └── _meta.json │ │ ├── _meta.json │ │ ├── examples │ │ │ └── _meta.json │ │ └── introduction.tsx │ ├── public │ │ └── logo.png │ ├── components │ │ ├── counters.module.css │ │ └── counters.tsx │ ├── next-env.d.ts │ ├── wrangler.toml │ ├── next.config.js │ ├── tsconfig.json │ ├── README.md │ ├── LICENSE │ ├── package.json │ └── theme.config.tsx ├── storybook │ ├── _redirects │ ├── .storybook │ │ ├── vitest.setup.js │ │ ├── preview.js │ │ ├── manager.js │ │ └── main.cjs │ ├── _headers │ ├── tsconfig.json │ ├── stories │ │ ├── basic │ │ │ ├── theme.stories.tsx │ │ │ ├── two.stories.tsx │ │ │ ├── showAddress.stories.tsx │ │ │ ├── simple.stories.tsx │ │ │ └── labeler.stories.tsx │ │ ├── formula │ │ │ ├── no_formula_bar.stories.tsx │ │ │ ├── row.stories.tsx │ │ │ ├── col.stories.tsx │ │ │ ├── custom.stories.tsx │ │ │ ├── disabled.stories.tsx │ │ │ └── ref.stories.tsx │ │ └── control │ │ │ └── write.stories.tsx │ ├── vitest.config.js │ └── package.json ├── preact-core │ ├── index.ts │ ├── example │ │ ├── main.jsx │ │ ├── vite.config.js │ │ ├── index.html │ │ ├── style.css │ │ ├── README.md │ │ └── App.jsx │ ├── exports.ts │ ├── tsconfig.json │ ├── vite.config.js │ ├── package.json │ └── README.md ├── vue-core │ ├── example │ │ ├── main.js │ │ ├── vite.config.js │ │ ├── index.html │ │ └── App.vue │ ├── src │ │ ├── index.ts │ │ └── hub.ts │ ├── tsconfig.json │ ├── package.json │ ├── vite.config.js │ └── README.md └── react-core │ ├── src │ ├── utils.ts │ ├── lib │ │ ├── sheet.ts │ │ ├── palette.ts │ │ ├── events.ts │ │ └── reference.ts │ ├── store │ │ └── index.ts │ ├── components │ │ ├── svg │ │ │ ├── AddIcon.tsx │ │ │ ├── CloseIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ └── Base.tsx │ │ ├── Fixed.tsx │ │ ├── Emitter.tsx │ │ ├── hooks.ts │ │ └── PluginBase.tsx │ ├── generate-style.js │ ├── formula │ │ └── functions │ │ │ ├── pi.ts │ │ │ ├── rand.ts │ │ │ ├── now.ts │ │ │ ├── abs.ts │ │ │ ├── uminus.ts │ │ │ ├── len.ts │ │ │ ├── atan.ts │ │ │ ├── concatenate.ts │ │ │ ├── col.spec.ts │ │ │ ├── or.ts │ │ │ ├── row.spec.ts │ │ │ ├── tan.ts │ │ │ ├── and.ts │ │ │ ├── cos.ts │ │ │ ├── sin.ts │ │ │ ├── radians.ts │ │ │ ├── eq.ts │ │ │ ├── exp.ts │ │ │ ├── lenb.ts │ │ │ ├── ne.ts │ │ │ ├── gt.ts │ │ │ ├── gte.ts │ │ │ ├── lt.ts │ │ │ ├── lte.ts │ │ │ ├── multiply.ts │ │ │ ├── not.ts │ │ │ ├── power.ts │ │ │ ├── ln.ts │ │ │ ├── log10.ts │ │ │ ├── concat.ts │ │ │ ├── row.ts │ │ │ ├── sqrt.ts │ │ │ ├── acos.ts │ │ │ ├── asin.ts │ │ │ ├── col.ts │ │ │ ├── atan2.ts │ │ │ ├── __base.ts │ │ │ ├── countif.ts │ │ │ ├── abs.spec.ts │ │ │ ├── round.ts │ │ │ ├── divide.ts │ │ │ ├── roundup.ts │ │ │ ├── rounddown.ts │ │ │ ├── count.ts │ │ │ ├── counta.ts │ │ │ ├── mod.ts │ │ │ ├── log.ts │ │ │ ├── product.ts │ │ │ ├── sum.spec.ts │ │ │ ├── if.ts │ │ │ ├── average.ts │ │ │ ├── max.ts │ │ │ ├── min.ts │ │ │ ├── sum.ts │ │ │ ├── iferror.ts │ │ │ ├── iferror.spec.ts │ │ │ ├── add.ts │ │ │ ├── minus.ts │ │ │ ├── mod.spec.ts │ │ │ ├── sumif.ts │ │ │ ├── index.ts │ │ │ ├── hlookup.ts │ │ │ └── vlookup.ts │ ├── renderers │ │ ├── thousand_separator.ts │ │ └── checkbox.tsx │ ├── styles │ │ ├── embedder.ts │ │ ├── contextmenu.less │ │ ├── tabular.less │ │ ├── search.less │ │ ├── root.less │ │ ├── utils.ts │ │ └── theme-light.less │ ├── constants.ts │ ├── index.ts │ └── policy │ │ └── core.ts │ ├── jest.config.js │ ├── vite.config.js │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── gridsheet.png ├── next.config.mjs ├── HISTORY.md ├── .prettierrc ├── e2e ├── playwright.config.ts ├── time.spec.ts ├── autofill.spec.ts ├── header.spec.ts └── search.spec.ts ├── .editorconfig ├── .github ├── workflows │ ├── stale.yaml │ ├── unittest.yaml │ ├── check.yaml │ ├── deploy-docs.yml │ ├── release.yaml │ └── e2e.yaml ├── ISSUE_TEMPLATE │ ├── QUESTION.yaml │ ├── FEATURE_REQUEST.yaml │ └── BUG_REPORT.yaml └── PULL_REQUEST_TEMPLATE.md ├── tsconfig.json ├── README.md ├── eslint.config.js ├── .gitignore └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | *.spec.ts 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 24.1.0 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | out/ -------------------------------------------------------------------------------- /gridsheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkframe/gridsheet/HEAD/gridsheet.png -------------------------------------------------------------------------------- /packages/storybook/_redirects: -------------------------------------------------------------------------------- 1 | # Handle client-side routing for Storybook 2 | /* /index.html 200 -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | import nextra from 'nextra' 2 | const withNextra = nextra() 3 | export default withNextra() -------------------------------------------------------------------------------- /packages/docs/pages/index.mdx: -------------------------------------------------------------------------------- 1 | import Introduction from './introduction'; 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkframe/gridsheet/HEAD/packages/docs/public/logo.png -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | - https://gridsheet.walkframe.com/history/2.x.x 2 | - https://gridsheet.walkframe.com/history/1.x.x 3 | -------------------------------------------------------------------------------- /packages/docs/pages/history/migration-guide/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "1to2": { 3 | "title": "Migration from 1.x to 2.x" 4 | } 5 | } -------------------------------------------------------------------------------- /packages/preact-core/index.ts: -------------------------------------------------------------------------------- 1 | export { h, render } from 'preact'; 2 | export * from '../react-core/src/index'; 3 | export * from './exports'; 4 | -------------------------------------------------------------------------------- /packages/preact-core/example/main.jsx: -------------------------------------------------------------------------------- 1 | import { render } from 'preact'; 2 | import App from './App'; 3 | 4 | render(, document.getElementById('app')); -------------------------------------------------------------------------------- /packages/vue-core/example/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | const app = createApp(App); 5 | app.mount('#app'); -------------------------------------------------------------------------------- /packages/docs/components/counters.module.css: -------------------------------------------------------------------------------- 1 | .counter { 2 | border: 1px solid #ccc; 3 | border-radius: 5px; 4 | padding: 2px 6px; 5 | margin: 12px 0 0; 6 | } 7 | -------------------------------------------------------------------------------- /packages/vue-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@gridsheet/preact-core'; 2 | export { default as GridSheet } from './GridSheet.vue'; 3 | export { useHub } from './hub'; 4 | -------------------------------------------------------------------------------- /packages/docs/pages/development/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "architecture": "Architecture & Design", 3 | "development-guide": "Development Guide", 4 | "package-structure": "Package Structure" 5 | } -------------------------------------------------------------------------------- /packages/docs/pages/getting-started/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "Overview", 3 | "features": "Features", 4 | "react": "React", 5 | "vue": "Vue", 6 | "vanilla": "Vanilla JavaScript" 7 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "arrowParens": "always" 9 | } -------------------------------------------------------------------------------- /packages/docs/pages/api-reference/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "props": { "title": "GridSheet Props" }, 3 | "table": { "title": "Table Class" }, 4 | "utility-functions": { "title": "Utility Functions" } 5 | } -------------------------------------------------------------------------------- /packages/react-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | export const setDefault = (target: any, key: K, defaultValue: D): D => { 2 | if (target[key] == null) { 3 | target[key] = defaultValue; 4 | } 5 | return target[key]; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/docs/pages/history/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "2.x.x": { 3 | "title": "GridSheet 2.x.x" 4 | }, 5 | "1.x.x": { 6 | "title": "GridSheet 1.x.x" 7 | }, 8 | "migration-guide": { 9 | "title": "Migration Guide" 10 | } 11 | } -------------------------------------------------------------------------------- /packages/docs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/docs/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "gridsheet" 2 | compatibility_date = "2024-05-02" 3 | compatibility_flags = ["nodejs_compat"] 4 | 5 | [build] 6 | command = "pnpm build" 7 | 8 | [build.upload] 9 | format = "directory" 10 | dir = "out" 11 | 12 | [[redirects]] 13 | from = "/*" 14 | to = "/index.html" 15 | status = 200 16 | -------------------------------------------------------------------------------- /packages/react-core/src/lib/sheet.ts: -------------------------------------------------------------------------------- 1 | export const escapeSheetName = (name: string): string => { 2 | const escaped = name.replace(/'/g, "''"); 3 | return `'${escaped}'`; 4 | }; 5 | 6 | export const getSheetPrefix = (name?: string): string => { 7 | if (name) { 8 | return `${escapeSheetName(name)}!`; 9 | } 10 | return ''; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/vue-core/example/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import path from 'path'; 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | resolve: { 8 | alias: { 9 | '@gridsheet/vue-core': path.resolve(__dirname, '../dist'), 10 | }, 11 | }, 12 | }); -------------------------------------------------------------------------------- /packages/preact-core/example/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import preact from '@preact/preset-vite'; 3 | import path from 'path'; 4 | 5 | export default defineConfig({ 6 | plugins: [preact()], 7 | resolve: { 8 | alias: { 9 | '@gridsheet/preact-core': path.resolve(__dirname, '../dist'), 10 | }, 11 | }, 12 | }); -------------------------------------------------------------------------------- /packages/react-core/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { StoreType } from '../types'; 3 | 4 | export type Dispatcher = React.Dispatch<{ 5 | type: number; 6 | value: any; 7 | }>; 8 | 9 | export const Context = createContext( 10 | {} as { 11 | store: StoreType; 12 | dispatch: Dispatcher; 13 | }, 14 | ); 15 | -------------------------------------------------------------------------------- /packages/vue-core/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GridSheet Vue Example 7 | 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /e2e/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@playwright/test'; 2 | 3 | export default defineConfig({ 4 | fullyParallel: true, 5 | use: { 6 | trace: 'on-first-retry', 7 | video: 'retain-on-failure', 8 | launchOptions: { 9 | slowMo: 250, 10 | }, 11 | permissions: ['clipboard-read', 'clipboard-write'], 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/preact-core/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GridSheet Preact Example 7 | 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /packages/docs/next.config.js: -------------------------------------------------------------------------------- 1 | const withNextra = require('nextra')({ 2 | theme: 'nextra-theme-docs', 3 | themeConfig: './theme.config.tsx', 4 | }) 5 | 6 | module.exports = withNextra({ 7 | output: 'export', 8 | images: { 9 | unoptimized: true, 10 | }, 11 | transpilePackages: ['@gridsheet/react-core'], 12 | experimental: { 13 | esmExternals: 'loose' 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /packages/react-core/src/lib/palette.ts: -------------------------------------------------------------------------------- 1 | export const COLOR_PALETTE = [ 2 | // orange 3 | '#FF6600', 4 | // purple 5 | '#AA44FF', 6 | // emeraldblue 7 | '#00CCCC', 8 | // peach 9 | '#EEAAEE', 10 | // yellow 11 | '#DDDD00', 12 | // winered 13 | '#AA4444', 14 | // lightgreen 15 | '#00FF00', 16 | // pink 17 | '#FF00FF', 18 | // navy 19 | '#3366FF', 20 | ]; 21 | -------------------------------------------------------------------------------- /packages/react-core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | 4 | preset: "ts-jest", 5 | "roots": [ 6 | "/" 7 | ], 8 | "testMatch": [ 9 | "**/__tests__/**/*.+(ts|tsx|js)", 10 | "**/?(*.)+(spec|test).+(ts|tsx|js)" 11 | ], 12 | "transform": { 13 | "^.+\\.(ts|tsx)$": "ts-jest" 14 | }, 15 | testEnvironment: 'jest-environment-jsdom' 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | 4 | [*.ts] 5 | indent_style = space 6 | indent_size = 2 7 | 8 | [*.js] 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.json] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.yaml,*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.ts,*.tsx] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.scss,*.css,*.styl] 25 | indent_style = space 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /packages/docs/pages/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "title": "Introduction", 4 | "type": "page" 5 | }, 6 | "getting-started": { 7 | "title": "Getting Started" 8 | }, 9 | "examples": "Examples", 10 | "api-reference": { 11 | "title": "API Reference" 12 | }, 13 | "contact": { 14 | "title": "Contact ↗", 15 | "type": "page", 16 | "href": "https://www.reddit.com/user/Mundane-Muscle-647/", 17 | "newWindow": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/react-core/src/components/svg/AddIcon.tsx: -------------------------------------------------------------------------------- 1 | import { type IconProps, Base } from './Base'; 2 | 3 | // https://tabler.io/icons 4 | 5 | export const AddIcon = ({ style, color = 'none', size = 24 }: IconProps) => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/react-core/src/components/svg/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import { type IconProps, Base } from './Base'; 2 | 3 | // https://tabler.io/icons 4 | 5 | export const CloseIcon = ({ style, color = 'none', size = 24 }: IconProps) => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '0 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 13 | days-before-stale: 30 14 | days-before-close: 5 15 | -------------------------------------------------------------------------------- /packages/react-core/src/generate-style.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { execSync } = require('child_process'); 3 | 4 | const generate = () => { 5 | execSync('pnpm less'); 6 | const css = fs.readFileSync('./src/styles/root.min.css'); 7 | const time = Math.floor((new Date()).getTime() / 1000); 8 | fs.writeFileSync('./src/styles/minified.ts', 9 | `// pnpm generate-style\nexport const LAST_MODIFIED = ${time};\nexport const CSS = \`${css}\`;\n` 10 | ); 11 | }; 12 | 13 | generate(); 14 | -------------------------------------------------------------------------------- /packages/storybook/.storybook/vitest.setup.js: -------------------------------------------------------------------------------- 1 | import { beforeAll } from 'vitest'; 2 | import { setProjectAnnotations } from '@storybook/react'; 3 | import * as projectAnnotations from './preview'; 4 | 5 | // This is an important step to apply the right configuration when testing your stories. 6 | // More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations 7 | const project = setProjectAnnotations([projectAnnotations]); 8 | 9 | beforeAll(project.beforeAll); -------------------------------------------------------------------------------- /packages/react-core/src/components/svg/SearchIcon.tsx: -------------------------------------------------------------------------------- 1 | import { type IconProps, Base } from './Base'; 2 | 3 | // https://tabler.io/icons 4 | 5 | export const SearchIcon = ({ style, color = 'none', size = 24 }: IconProps) => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/storybook/_headers: -------------------------------------------------------------------------------- 1 | /* 2 | X-Frame-Options: DENY 3 | X-Content-Type-Options: nosniff 4 | Referrer-Policy: strict-origin-when-cross-origin 5 | Permissions-Policy: camera=(), microphone=(), geolocation=() 6 | Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; 7 | */ -------------------------------------------------------------------------------- /packages/react-core/src/formula/functions/pi.ts: -------------------------------------------------------------------------------- 1 | import { FormulaError } from '../evaluator'; 2 | import { BaseFunction } from './__base'; 3 | 4 | export class PiFunction extends BaseFunction { 5 | example = 'PI()'; 6 | helpText = ['Returns the value of pi.']; 7 | helpArgs = []; 8 | 9 | protected validate() { 10 | if (this.bareArgs.length !== 0) { 11 | throw new FormulaError('#N/A', 'Number of arguments for PI is incorrect.'); 12 | } 13 | } 14 | 15 | protected main() { 16 | return Math.PI; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-core/src/renderers/thousand_separator.ts: -------------------------------------------------------------------------------- 1 | import { RendererMixinType } from './core'; 2 | 3 | export const ThousandSeparatorRendererMixin: RendererMixinType = { 4 | number({ value, cell }): any { 5 | if (value == null || isNaN(value)) { 6 | return 'NaN'; 7 | } 8 | const [int, fraction] = String(value).split('.'); 9 | const result = int.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,'); 10 | if (fraction == null) { 11 | return result; 12 | } 13 | return `${result}.${fraction}`; 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/react-core/src/renderers/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { RenderProps } from './core'; 2 | 3 | export const CheckboxRendererMixin = { 4 | bool({ value, sync, table, point }: RenderProps): any { 5 | return ( 6 | { 10 | if (sync) { 11 | sync(table.write({ point, value: e.currentTarget.checked.toString() })); 12 | } 13 | e.currentTarget.blur(); 14 | }} 15 | /> 16 | ); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/react-core/src/formula/functions/rand.ts: -------------------------------------------------------------------------------- 1 | import { FormulaError } from '../evaluator'; 2 | import { BaseFunction } from './__base'; 3 | 4 | export class RandFunction extends BaseFunction { 5 | example = 'RAND()'; 6 | helpText = ['Returns a random number between 0 and 1.']; 7 | helpArgs = []; 8 | 9 | protected validate() { 10 | if (this.bareArgs.length !== 0) { 11 | throw new FormulaError('#N/A', 'Number of arguments for RAND is incorrect.'); 12 | } 13 | } 14 | 15 | protected main() { 16 | return Math.random(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-core/src/formula/functions/now.ts: -------------------------------------------------------------------------------- 1 | import { FormulaError } from '../evaluator'; 2 | import { BaseFunction } from './__base'; 3 | 4 | export class NowFunction extends BaseFunction { 5 | example = 'NOW()'; 6 | helpText = ['Returns a serial value corresponding to the current date and time.']; 7 | helpArgs = []; 8 | 9 | protected validate() { 10 | if (this.bareArgs.length !== 0) { 11 | throw new FormulaError('#N/A', 'Number of arguments for NOW is incorrect.'); 12 | } 13 | } 14 | 15 | protected main() { 16 | return new Date(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-core/src/styles/embedder.ts: -------------------------------------------------------------------------------- 1 | import { CSS, LAST_MODIFIED } from './minified'; 2 | 3 | export const embedStyle = () => { 4 | if (typeof window === 'undefined') { 5 | return; 6 | } 7 | const exists = document.querySelector(`style.gs-styling[data-modified-at='${LAST_MODIFIED}']`); 8 | if (exists) { 9 | return; 10 | } 11 | const style = document.createElement('style'); 12 | document.head.appendChild(style); 13 | style.setAttribute('class', 'gs-styling'); 14 | style.setAttribute('data-modified-at', `${LAST_MODIFIED}`); 15 | style.innerText = CSS; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/docs/components/counters.tsx: -------------------------------------------------------------------------------- 1 | // Example from https://beta.reactjs.org/learn 2 | 3 | import { useState } from 'react'; 4 | import styles from './counters.module.css'; 5 | 6 | function MyButton() { 7 | const [count, setCount] = useState(0); 8 | 9 | function handleClick() { 10 | setCount(count + 1); 11 | } 12 | 13 | return ( 14 |
15 | 18 |
19 | ); 20 | } 21 | 22 | export default function MyApp() { 23 | return ; 24 | } 25 | -------------------------------------------------------------------------------- /packages/vue-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "preserve", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "allowSyntheticDefaultImports": true, 12 | "resolveJsonModule": true, 13 | "types": ["vite/client"], 14 | "declaration": true, 15 | "emitDeclarationOnly": true, 16 | "outDir": "dist", 17 | "paths": { 18 | "@gridsheet/preact-core": ["../preact-core"] 19 | } 20 | }, 21 | "include": ["src", "index.ts"] 22 | } -------------------------------------------------------------------------------- /.github/workflows/unittest.yaml: -------------------------------------------------------------------------------- 1 | name: unittest 2 | on: 3 | push: 4 | workflow_dispatch: 5 | 6 | env: 7 | TZ: 'Asia/Tokyo' 8 | 9 | jobs: 10 | run-unit-tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: setup pnpm 15 | uses: pnpm/action-setup@v4 16 | - name: cache 17 | uses: actions/cache@v3 18 | with: 19 | path: | 20 | node_modules 21 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 22 | - name: install 23 | run: | 24 | pnpm install 25 | - name: test 26 | run: | 27 | pnpm jest 28 | -------------------------------------------------------------------------------- /packages/preact-core/example/style.css: -------------------------------------------------------------------------------- 1 | main { 2 | padding: 1rem; 3 | } 4 | 5 | h1 { 6 | color: #333; 7 | margin-bottom: 1rem; 8 | } 9 | 10 | .labeler-control { 11 | background: #f8f9fa; 12 | padding: 1rem; 13 | border-radius: 8px; 14 | margin-top: 1rem; 15 | border: 1px solid #e9ecef; 16 | } 17 | 18 | .labeler-control label { 19 | display: flex; 20 | align-items: center; 21 | gap: 0.5rem; 22 | font-weight: 500; 23 | color: #495057; 24 | cursor: pointer; 25 | } 26 | 27 | .labeler-control input[type="checkbox"] { 28 | margin: 0; 29 | } 30 | 31 | .grid-container { 32 | border: 1px solid #e9ecef; 33 | border-radius: 8px; 34 | overflow: hidden; 35 | } -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: typecheck 2 | on: 3 | push: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | type-check: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: setup pnpm 13 | uses: pnpm/action-setup@v4 14 | 15 | - name: cache 16 | uses: actions/cache@v3 17 | with: 18 | path: | 19 | node_modules 20 | packages/*/node_modules 21 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 22 | 23 | - name: install 24 | run: | 25 | pnpm install 26 | 27 | - name: check 28 | run: | 29 | pnpm typecheck:all 30 | -------------------------------------------------------------------------------- /packages/react-core/src/components/Fixed.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, FC, ReactNode } from 'react'; 2 | import { useBrowser } from './hooks'; 3 | import { createPortal } from 'react-dom'; 4 | 5 | type Props = { 6 | className?: string; 7 | style?: CSSProperties; 8 | children: ReactNode; 9 | [attr: string]: any; 10 | }; 11 | 12 | export const Fixed: FC = ({ children, style, className = '', ...attrs }) => { 13 | const { document } = useBrowser(); 14 | if (document == null) { 15 | return null; 16 | } 17 | return createPortal( 18 |
19 | {children} 20 |
, 21 | document.body, 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/react-core/src/lib/events.ts: -------------------------------------------------------------------------------- 1 | export const isTouching = (e: React.TouchEvent | React.MouseEvent): boolean => { 2 | if (e.type.startsWith('touch')) { 3 | return (e as React.TouchEvent).touches.length > 0; 4 | } 5 | if (e.type.startsWith('mouse')) { 6 | const mouseEvent = e as React.MouseEvent; 7 | // left click only 8 | return !!(mouseEvent.buttons & 1) && mouseEvent.button === 0; 9 | } 10 | return false; 11 | }; 12 | 13 | /** 14 | * Safely call preventDefault to avoid errors on touch events 15 | */ 16 | export const safePreventDefault = (e: React.MouseEvent | React.TouchEvent): void => { 17 | if (!e.type.startsWith('touch')) { 18 | e.preventDefault(); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/storybook/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react').Preview } */ 2 | const preview = { 3 | parameters: { 4 | docs: { 5 | disable: true, 6 | }, 7 | controls: { 8 | disable: true, 9 | }, 10 | // Add GitHub repository link 11 | links: { 12 | github: { 13 | title: 'GitHub', 14 | url: 'https://github.com/walkframe/gridsheet', 15 | }, 16 | }, 17 | // Add GitHub link to toolbar 18 | toolbar: { 19 | 'github-link': { 20 | title: 'View on GitHub', 21 | icon: 'github', 22 | url: 'https://github.com/walkframe/gridsheet', 23 | }, 24 | }, 25 | }, 26 | }; 27 | 28 | export default preview; -------------------------------------------------------------------------------- /packages/preact-core/exports.ts: -------------------------------------------------------------------------------- 1 | export { 2 | useState, 3 | useReducer, 4 | useEffect, 5 | useLayoutEffect, 6 | useRef, 7 | useImperativeHandle, 8 | useMemo, 9 | useCallback, 10 | useContext, 11 | useDebugValue, 12 | createElement, 13 | createContext, 14 | createRef, 15 | Fragment, 16 | Component, 17 | version, 18 | Children, 19 | render as compatRender, 20 | hydrate, 21 | unmountComponentAtNode, 22 | createPortal, 23 | createFactory, 24 | cloneElement, 25 | isValidElement, 26 | findDOMNode, 27 | PureComponent, 28 | memo, 29 | forwardRef, 30 | unstable_batchedUpdates, 31 | StrictMode, 32 | Suspense, 33 | SuspenseList, 34 | lazy, 35 | } from 'preact/compat'; 36 | -------------------------------------------------------------------------------- /packages/docs/pages/getting-started/react.mdx: -------------------------------------------------------------------------------- 1 | # React 2 | 3 | GridSheet provides a comprehensive React component that brings Excel-like functionality to your React applications. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install @gridsheet/react-core 9 | # or 10 | yarn add @gridsheet/react-core 11 | # or 12 | pnpm add @gridsheet/react-core 13 | ``` 14 | 15 | ## Basic Usage 16 | 17 |