├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .github
└── workflows
│ ├── beta.js.yml
│ ├── node.js.yml
│ ├── release.js.yml
│ └── storybook.js.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierrc
├── .storybook
├── .babelrc.json
├── addons
│ └── expand-all
│ │ └── register.mjs
├── main.js
├── main.ts
├── manager.ts
├── preview-body.html
├── preview-head.html
└── preview.ts
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.json
├── config
├── build-util.sh
└── linaria.json
├── data-grid.jpg
├── icon.png
├── media
├── data-grid-dark.png
├── data-grid.png
└── icon.png
├── package-lock.json
├── package.json
├── packages
├── cells
│ ├── .eslintignore
│ ├── .eslintrc
│ ├── .npmignore
│ ├── LICENSE
│ ├── README.md
│ ├── build.sh
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── cell.stories.tsx
│ │ ├── cells
│ │ │ ├── article-cell-editor.tsx
│ │ │ ├── article-cell-types.ts
│ │ │ ├── article-cell.tsx
│ │ │ ├── button-cell.tsx
│ │ │ ├── date-picker-cell.tsx
│ │ │ ├── dropdown-cell.tsx
│ │ │ ├── links-cell.tsx
│ │ │ ├── multi-select-cell.tsx
│ │ │ ├── range-cell.tsx
│ │ │ ├── sparkline-cell.tsx
│ │ │ ├── spinner-cell.tsx
│ │ │ ├── star-cell.tsx
│ │ │ ├── tags-cell.tsx
│ │ │ ├── tree-view-cell.tsx
│ │ │ └── user-profile-cell.tsx
│ │ ├── draw-fns.ts
│ │ └── index.ts
│ ├── test
│ │ ├── date-picker-cell.test.tsx
│ │ └── multi-select-cell.test.tsx
│ ├── tsconfig.cjs.json
│ ├── tsconfig.esm.json
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ └── vitest.setup.ts
├── core
│ ├── .eslintignore
│ ├── .eslintrc
│ ├── .npmignore
│ ├── API.md
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── build.sh
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── cells
│ │ │ ├── boolean-cell.tsx
│ │ │ ├── bubble-cell.tsx
│ │ │ ├── cell-types.ts
│ │ │ ├── drilldown-cell.tsx
│ │ │ ├── image-cell.tsx
│ │ │ ├── index.ts
│ │ │ ├── loading-cell.tsx
│ │ │ ├── markdown-cell.tsx
│ │ │ ├── marker-cell.tsx
│ │ │ ├── new-row-cell.tsx
│ │ │ ├── number-cell.tsx
│ │ │ ├── protected-cell.tsx
│ │ │ ├── row-id-cell.tsx
│ │ │ ├── text-cell.tsx
│ │ │ └── uri-cell.tsx
│ │ ├── common
│ │ │ ├── browser-detect.ts
│ │ │ ├── image-window-loader.ts
│ │ │ ├── is-hotkey.ts
│ │ │ ├── math.ts
│ │ │ ├── render-state-provider.ts
│ │ │ ├── resize-detector.ts
│ │ │ ├── styles.ts
│ │ │ ├── support.ts
│ │ │ └── utils.tsx
│ │ ├── data-editor-all.tsx
│ │ ├── data-editor
│ │ │ ├── copy-paste.ts
│ │ │ ├── data-editor-fns.ts
│ │ │ ├── data-editor-keybindings.ts
│ │ │ ├── data-editor.tsx
│ │ │ ├── group-rename.tsx
│ │ │ ├── row-grouping-api.ts
│ │ │ ├── row-grouping.ts
│ │ │ ├── stories
│ │ │ │ ├── data-editor-repros.stories.tsx
│ │ │ │ ├── data-editor.stories.tsx
│ │ │ │ └── utils.tsx
│ │ │ ├── use-autoscroll.ts
│ │ │ ├── use-cells-for-selection.ts
│ │ │ ├── use-column-sizer.ts
│ │ │ ├── use-initial-scroll-offset.ts
│ │ │ ├── use-rem-adjuster.ts
│ │ │ └── visible-region.ts
│ │ ├── docs
│ │ │ ├── 00-faq.stories.tsx
│ │ │ ├── 01-getting-started.stories.tsx
│ │ │ ├── 02-editing-data.stories.tsx
│ │ │ ├── 03-grid-column.stories.tsx
│ │ │ ├── 04-streaming-data.stories.tsx
│ │ │ ├── 05-copy-paste.stories.tsx.tsx
│ │ │ ├── 06-search.stories.tsx
│ │ │ ├── 07-column-grouping.stories.tsx
│ │ │ ├── 08-theming.stories.tsx
│ │ │ ├── 09-menus.stories.tsx
│ │ │ ├── doc-wrapper.tsx
│ │ │ ├── examples
│ │ │ │ ├── add-column.stories.tsx
│ │ │ │ ├── add-data-to-middle.stories.tsx
│ │ │ │ ├── add-data-to-top.stories.tsx
│ │ │ │ ├── add-data.stories.tsx
│ │ │ │ ├── all-cell-kinds.stories.tsx
│ │ │ │ ├── append-row-handle.stories.tsx
│ │ │ │ ├── automatic-row-markers.stories.tsx
│ │ │ │ ├── built-in-search.stories.tsx
│ │ │ │ ├── cell-activated-event.stories.tsx
│ │ │ │ ├── column-group-collapse.stories.tsx
│ │ │ │ ├── column-groups.stories.tsx
│ │ │ │ ├── content-alignment.stories.tsx
│ │ │ │ ├── controlled-search.stories.tsx
│ │ │ │ ├── controlled-selection.stories.tsx
│ │ │ │ ├── copy-support.stories.tsx
│ │ │ │ ├── custom-event-target.stories.tsx
│ │ │ │ ├── custom-header-icons.stories.tsx
│ │ │ │ ├── custom-header.stories.tsx
│ │ │ │ ├── drag-source.stories.tsx
│ │ │ │ ├── drop-events.stories.tsx
│ │ │ │ ├── fill-handle.stories.tsx
│ │ │ │ ├── freeze-columns.stories.tsx
│ │ │ │ ├── freeze-rows.stories.tsx
│ │ │ │ ├── header-menus.stories.tsx
│ │ │ │ ├── highlight-cells.stories.tsx
│ │ │ │ ├── imperative-scroll.stories.tsx
│ │ │ │ ├── input-blending.stories.tsx
│ │ │ │ ├── keybindings.stories.tsx
│ │ │ │ ├── layout-integration.stories.tsx
│ │ │ │ ├── multi-select-columns.stories.tsx
│ │ │ │ ├── new-column-button.stories.tsx
│ │ │ │ ├── obscured-grid.stories.tsx
│ │ │ │ ├── observe-visible-region.stories.tsx
│ │ │ │ ├── one-hundred-thousand-columns.stories.tsx
│ │ │ │ ├── one-million-rows.stories.tsx
│ │ │ │ ├── overscroll.stories.tsx
│ │ │ │ ├── padding.stories.tsx
│ │ │ │ ├── paste-support.stories.tsx
│ │ │ │ ├── prevent-diagonal-scroll.stories.tsx
│ │ │ │ ├── rapid-updates.stories.tsx
│ │ │ │ ├── rearrange-columns.stories.tsx
│ │ │ │ ├── reorder-rows.stories.tsx
│ │ │ │ ├── resizable-columns.stories.tsx
│ │ │ │ ├── right-element.stories.tsx
│ │ │ │ ├── right-to-left.stories.tsx
│ │ │ │ ├── row-and-header-sizes.stories.tsx
│ │ │ │ ├── row-grouping.stories.tsx
│ │ │ │ ├── row-hover.stories.tsx
│ │ │ │ ├── row-markers.stories.tsx
│ │ │ │ ├── row-selections.stories.tsx
│ │ │ │ ├── scaled-view.stories.tsx
│ │ │ │ ├── scroll-offset.stories.tsx
│ │ │ │ ├── scroll-shadows.stories.tsx
│ │ │ │ ├── search-as-filter.stories.tsx
│ │ │ │ ├── server-side-data.stories.tsx
│ │ │ │ ├── shadow-dom.stories.tsx
│ │ │ │ ├── silly-numbers.stories.tsx
│ │ │ │ ├── small-editable-grid.stories.tsx
│ │ │ │ ├── smooth-scrolling-grid.stories.tsx
│ │ │ │ ├── span-cell.stories.tsx
│ │ │ │ ├── stretch-column-size.stories.tsx
│ │ │ │ ├── ten-million-cells.stories.tsx
│ │ │ │ ├── theme-per-column.stories.tsx
│ │ │ │ ├── theme-per-row.stories.tsx
│ │ │ │ ├── theme-support.stories.tsx
│ │ │ │ ├── tooltips.stories.tsx
│ │ │ │ ├── trailing-row-options.stories.tsx
│ │ │ │ ├── uneven-rows.stories.tsx
│ │ │ │ ├── validate-data.stories.tsx
│ │ │ │ └── wrapping-text.stories.tsx
│ │ │ └── template.tsx
│ │ ├── index.ts
│ │ ├── internal
│ │ │ ├── click-outside-container
│ │ │ │ └── click-outside-container.tsx
│ │ │ ├── data-editor-container
│ │ │ │ └── data-grid-container.tsx
│ │ │ ├── data-grid-dnd
│ │ │ │ └── data-grid-dnd.tsx
│ │ │ ├── data-grid-overlay-editor
│ │ │ │ ├── data-grid-overlay-editor-style.tsx
│ │ │ │ ├── data-grid-overlay-editor.tsx
│ │ │ │ ├── private
│ │ │ │ │ ├── bubbles-overlay-editor-style.tsx
│ │ │ │ │ ├── bubbles-overlay-editor.tsx
│ │ │ │ │ ├── drilldown-overlay-editor.tsx
│ │ │ │ │ ├── image-overlay-editor-style.tsx
│ │ │ │ │ ├── image-overlay-editor.tsx
│ │ │ │ │ ├── markdown-overlay-editor-style.tsx
│ │ │ │ │ ├── markdown-overlay-editor.tsx
│ │ │ │ │ ├── number-overlay-editor-style.tsx
│ │ │ │ │ ├── number-overlay-editor.tsx
│ │ │ │ │ ├── uri-overlay-editor-style.tsx
│ │ │ │ │ └── uri-overlay-editor.tsx
│ │ │ │ └── use-stay-on-screen.ts
│ │ │ ├── data-grid-search
│ │ │ │ ├── data-grid-search-style.tsx
│ │ │ │ └── data-grid-search.tsx
│ │ │ ├── data-grid
│ │ │ │ ├── animation-manager.ts
│ │ │ │ ├── cell-set.ts
│ │ │ │ ├── color-parser.ts
│ │ │ │ ├── data-grid-sprites.ts
│ │ │ │ ├── data-grid-types.ts
│ │ │ │ ├── data-grid.stories.tsx
│ │ │ │ ├── data-grid.tsx
│ │ │ │ ├── event-args.ts
│ │ │ │ ├── image-window-loader-interface.ts
│ │ │ │ ├── render
│ │ │ │ │ ├── data-grid-lib.ts
│ │ │ │ │ ├── data-grid-render.blit.ts
│ │ │ │ │ ├── data-grid-render.cells.ts
│ │ │ │ │ ├── data-grid-render.header.ts
│ │ │ │ │ ├── data-grid-render.lines.ts
│ │ │ │ │ ├── data-grid-render.ts
│ │ │ │ │ ├── data-grid-render.walk.ts
│ │ │ │ │ ├── data-grid.render.rings.ts
│ │ │ │ │ ├── draw-checkbox.ts
│ │ │ │ │ ├── draw-edit-hover-indicator.ts
│ │ │ │ │ └── draw-grid-arg.ts
│ │ │ │ ├── sprites.ts
│ │ │ │ ├── use-animation-queue.ts
│ │ │ │ └── use-selection-behavior.ts
│ │ │ ├── growing-entry
│ │ │ │ ├── growing-entry-style.tsx
│ │ │ │ └── growing-entry.tsx
│ │ │ ├── markdown-div
│ │ │ │ ├── markdown-div.tsx
│ │ │ │ └── private
│ │ │ │ │ └── markdown-container.tsx
│ │ │ └── scrolling-data-grid
│ │ │ │ ├── infinite-scroller.tsx
│ │ │ │ ├── scrolling-data-grid.stories.tsx
│ │ │ │ ├── scrolling-data-grid.tsx
│ │ │ │ └── use-kinetic-scroll.ts
│ │ └── stories
│ │ │ └── story-utils.tsx
│ ├── test
│ │ ├── animation-manager.test.ts
│ │ ├── cells.test.tsx
│ │ ├── click-outside-container.test.tsx
│ │ ├── color-parser.test.ts
│ │ ├── common.test.ts
│ │ ├── copy-paste.test.ts
│ │ ├── data-editor-fns.test.ts
│ │ ├── data-editor-input.test.tsx
│ │ ├── data-editor-resize.test.tsx
│ │ ├── data-editor.test.tsx
│ │ ├── data-grid-lib.test.ts
│ │ ├── data-grid-overlay.test.tsx
│ │ ├── data-grid-types.test.ts
│ │ ├── data-grid.test.tsx
│ │ ├── image-window-loader.test.ts
│ │ ├── math.test.ts
│ │ ├── render-state-provider.test.ts
│ │ ├── row-grouping.test.ts
│ │ ├── test-utils.tsx
│ │ ├── uri-cell.test.ts
│ │ ├── use-animation-queue.test.ts
│ │ ├── use-autoscroll.test.tsx
│ │ ├── use-column-sizer.test.tsx
│ │ ├── use-deep-memo.test.ts
│ │ ├── use-kinetic-scroll.test.ts
│ │ ├── use-rem-adjuster.test.ts
│ │ └── utils.test.ts
│ ├── tsconfig.cjs.json
│ ├── tsconfig.esm.json
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ └── vitest.setup.ts
└── source
│ ├── .eslintignore
│ ├── .eslintrc
│ ├── .npmignore
│ ├── LICENSE
│ ├── README.md
│ ├── build.sh
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── index.ts
│ ├── stories
│ │ ├── use-data-source.stories.tsx
│ │ └── utils.tsx
│ ├── use-async-data-source.ts
│ ├── use-collapsing-groups.ts
│ ├── use-column-sort.ts
│ ├── use-movable-columns.ts
│ └── use-undo-redo.ts
│ ├── test
│ ├── use-column-sort.test.tsx
│ └── use-data-source.test.tsx
│ ├── tsconfig.cjs.json
│ ├── tsconfig.esm.json
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ └── vitest.setup.ts
├── setup-react-18-test.sh
├── test-projects
├── bootstrap-projects.sh
├── cra5-gdg
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── App.css
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── components
│ │ │ └── Grid.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── logo.svg
│ │ ├── react-app-env.d.ts
│ │ ├── reportWebVitals.ts
│ │ └── setupTests.ts
│ └── tsconfig.json
└── next-gdg
│ ├── .babelrc
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ └── Grid.tsx
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── hello.ts
│ └── index.tsx
│ ├── public
│ ├── favicon.ico
│ └── vercel.svg
│ ├── styles
│ ├── Home.module.css
│ └── globals.css
│ └── tsconfig.json
├── tsconfig.json
└── update-version.sh
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/typescript-node/.devcontainer/base.Dockerfile
2 |
3 | # [Choice] Node.js version: 14, 12, 10
4 | ARG VARIANT="14-buster"
5 | FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
6 |
7 | RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg
8 | RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
9 |
10 | RUN apt-get update && DEBIAN_FRONTEND=non-interactive apt-get install gh jq -y
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/typescript-node
3 | {
4 | "name": "Node.js & TypeScript",
5 | "build": {
6 | "dockerfile": "Dockerfile",
7 | // Update 'VARIANT' to pick a Node version: 10, 12, 14
8 | "args": {
9 | "VARIANT": "14"
10 | }
11 | },
12 |
13 | // Set *default* container specific settings.json values on container create.
14 | "settings": {
15 | "terminal.integrated.shell.linux": "/bin/bash"
16 | },
17 |
18 | // Add the IDs of extensions you want installed when the container is created.
19 | "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"],
20 |
21 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
22 | "forwardPorts": [9009],
23 |
24 | // Use 'postCreateCommand' to run commands after the container is created.
25 | "postCreateCommand": "./.devcontainer/run.sh",
26 |
27 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
28 | "remoteUser": "node"
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Build
5 |
6 | on: [push, pull_request]
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Use Node.js 20
14 | uses: actions/setup-node@v4
15 | with:
16 | node-version: 20.10.0
17 | - run: npm install
18 | - run: npm run build
19 | - run: npm run test -- --coverage
20 | - run: npm run test-source
21 | - run: npm run test-cells
22 | - run: npm run test-projects
23 | - name: Coveralls
24 | uses: coverallsapp/github-action@master
25 | with:
26 | github-token: ${{ secrets.GITHUB_TOKEN }}
27 | path-to-lcov: ./packages/core/coverage/lcov.info
28 | base-path: ./packages/core
29 | test-react-18:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: actions/checkout@v4
33 | - uses: actions/setup-node@v4
34 | with:
35 | node-version: 20.10.0
36 | - run: npm install
37 | - run: npm run test-18
38 |
--------------------------------------------------------------------------------
/.github/workflows/release.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Release CI
5 |
6 | on:
7 | push:
8 | tags:
9 | - "v*"
10 | - "!v*-*"
11 |
12 | jobs:
13 | publish-core:
14 | defaults:
15 | run:
16 | working-directory: ./packages/core
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 | - uses: actions/setup-node@v4
21 | with:
22 | node-version: 20.10.0
23 | - name: Bootstrap
24 | run: npm install && npm run build
25 | working-directory: .
26 | - uses: JS-DevTools/npm-publish@v1
27 | with:
28 | token: ${{ secrets.NPM_TOKEN }}
29 | access: public
30 | package: ./packages/core/package.json
31 | publish-cells:
32 | defaults:
33 | run:
34 | working-directory: ./packages/cells
35 | runs-on: ubuntu-latest
36 | steps:
37 | - uses: actions/checkout@v4
38 | - uses: actions/setup-node@v4
39 | with:
40 | node-version: 20.10.0
41 | - name: Bootstrap
42 | run: npm install && npm run build
43 | working-directory: .
44 | - uses: JS-DevTools/npm-publish@v1
45 | with:
46 | token: ${{ secrets.NPM_TOKEN }}
47 | access: public
48 | package: ./packages/cells/package.json
49 | publish-source:
50 | defaults:
51 | run:
52 | working-directory: ./packages/source
53 | runs-on: ubuntu-latest
54 | steps:
55 | - uses: actions/checkout@v4
56 | - uses: actions/setup-node@v4
57 | with:
58 | node-version: 20.10.0
59 | - name: Bootstrap
60 | run: npm install && npm run build
61 | working-directory: .
62 | - uses: JS-DevTools/npm-publish@v1
63 | with:
64 | token: ${{ secrets.NPM_TOKEN }}
65 | access: public
66 | package: ./packages/source/package.json
67 |
--------------------------------------------------------------------------------
/.github/workflows/storybook.js.yml:
--------------------------------------------------------------------------------
1 | name: Storybook Build and Deploy
2 | on:
3 | push:
4 | branches: ["main"]
5 | paths: ["packages/core/**", "packages/cells/**", "packages/source/**"]
6 | jobs:
7 | build-and-deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout 🛎️
11 | uses: actions/checkout@v4
12 | with:
13 | persist-credentials: false
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: 20.10.0
17 | - name: Install and Build 🔧
18 | run: |
19 | npm install
20 | npm run build-storybook
21 | - name: "Add .nojekyll 📄"
22 | run: cd storybook-build && touch .nojekyll
23 | - name: Deploy 🚀
24 | uses: JamesIves/github-pages-deploy-action@3.6.2
25 | with:
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 | BRANCH: gh-pages
28 | FOLDER: storybook-build
29 | CLEAN: true
30 | TARGET_FOLDER: docs
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Dd]ist/
2 | node_modules*
3 | .DS_Store
4 | .tool-versions
5 | .idea
6 | .cache
7 | pubsub-debug.log
8 | storybook-build/
9 | coverage/
10 | *.tsbuildinfo
11 | gen-docs/
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20.10.0
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "printWidth": 120,
4 | "semi": true,
5 | "jsxBracketSameLine": true,
6 | "arrowParens": "avoid",
7 | "bracketSpacing": true,
8 | "cursorOffset": -1,
9 | "endOfLine": "lf",
10 | "htmlWhitespaceSensitivity": "css",
11 | "insertPragma": false,
12 | "jsxSingleQuote": false,
13 | "proseWrap": "preserve",
14 | "quoteProps": "as-needed",
15 | "rangeStart": 0,
16 | "requirePragma": false,
17 | "singleQuote": false,
18 | "trailingComma": "es5",
19 | "useTabs": false,
20 | "vueIndentScriptAndStyle": false
21 | }
22 |
--------------------------------------------------------------------------------
/.storybook/.babelrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "assumptions": {
3 | "setPublicClassFields": true,
4 | "setSpreadProperties": true
5 | },
6 | "sourceType": "unambiguous",
7 | "presets": [
8 | [
9 | "@babel/preset-env",
10 | {
11 | "targets": {
12 | "firefox": "60",
13 | "chrome": "67",
14 | "safari": "12.1"
15 | },
16 | "exclude": ["@babel/plugin-transform-template-literals"]
17 | }
18 | ],
19 | "@babel/preset-react",
20 | "@babel/preset-typescript",
21 | "@linaria"
22 | ],
23 | "plugins": ["@babel/plugin-proposal-class-properties"]
24 | }
25 |
--------------------------------------------------------------------------------
/.storybook/addons/expand-all/register.mjs:
--------------------------------------------------------------------------------
1 | import { STORY_RENDERED } from "@storybook/core-events";
2 | import { addons } from "@storybook/addons";
3 |
4 | let hasExpanded = false;
5 |
6 | addons.register("expand-all", api => {
7 | const emitter = addons.getChannel();
8 |
9 | emitter.on(STORY_RENDERED, () => {
10 | if (!hasExpanded) {
11 | setTimeout(api.expandAll); // Calling on the next tick after storyRendered seems to work reliably.
12 | hasExpanded = true;
13 | }
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | module.exports = {
4 | stories: ["../**/src/**/*.stories.tsx"],
5 | addons: [getAbsolutePath("@storybook/addon-storysource"), getAbsolutePath("@storybook/addon-controls")],
6 |
7 | typescript: {
8 | reactDocgen: false,
9 | },
10 |
11 | async viteFinal(config) {
12 | // We need to dynamically import these since they use ESM
13 | const { mergeConfig } = await import("vite");
14 | const linaria = (await import("@linaria/vite")).default;
15 |
16 | return mergeConfig(config, {
17 | plugins: [linaria()],
18 | });
19 | },
20 |
21 | framework: {
22 | name: getAbsolutePath("@storybook/react-vite"),
23 | options: {},
24 | },
25 |
26 | docs: {
27 | autodocs: false,
28 | },
29 | };
30 |
31 | function getAbsolutePath(value) {
32 | return path.dirname(require.resolve(path.join(value, "package.json")));
33 | }
34 |
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import { dirname, join } from "path";
2 | import linaria from "@linaria/vite";
3 | import { mergeConfig } from "vite";
4 |
5 | export default {
6 | stories: ["../**/src/**/*.stories.tsx"],
7 | addons: [getAbsolutePath("@storybook/addon-storysource"), getAbsolutePath("@storybook/addon-controls")],
8 |
9 | typescript: {
10 | reactDocgen: false,
11 | },
12 |
13 | async viteFinal(config) {
14 | return mergeConfig(config, {
15 | plugins: [linaria()],
16 | });
17 | },
18 |
19 | framework: {
20 | name: getAbsolutePath("@storybook/react-vite"),
21 | options: {},
22 | },
23 |
24 | docs: {
25 | autodocs: false,
26 | },
27 | };
28 |
29 | function getAbsolutePath(value) {
30 | return dirname(require.resolve(join(value, "package.json")));
31 | }
32 |
--------------------------------------------------------------------------------
/.storybook/manager.ts:
--------------------------------------------------------------------------------
1 | import { addons } from "@storybook/addons";
2 | import { create } from "@storybook/theming";
3 |
4 | const glideTheme = create({
5 | base: "dark",
6 | brandTitle: "Glide Data Grid",
7 | brandUrl: "https://grid.glideapps.com",
8 | brandImage: "https://res.cloudinary.com/glide/image/upload/c_scale,w_45/v1634058004/glidehq/glide-transparent.png",
9 | });
10 |
11 | addons.setConfig({
12 | isFullscreen: false,
13 | showNav: true,
14 | showPanel: false,
15 | panelPosition: "right",
16 | enableShortcuts: true,
17 | isToolshown: false,
18 | theme: glideTheme,
19 | selectedPanel: undefined,
20 | initialActive: "sidebar",
21 | sidebar: {
22 | showRoots: true,
23 | collapsedRoots: ["Subcomponents", "TestCases"],
24 | },
25 | toolbar: {
26 | title: { hidden: false },
27 | zoom: { hidden: false },
28 | eject: { hidden: false },
29 | copy: { hidden: false },
30 | fullscreen: { hidden: false },
31 | },
32 | });
33 |
--------------------------------------------------------------------------------
/.storybook/preview-body.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
--------------------------------------------------------------------------------
/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | const preview = {
2 | parameters: {
3 | layout: "fullscreen",
4 | options: {
5 | storySort: {
6 | method: "alphabetical",
7 | order: ["Glide-Data-Grid", "Extra Packages", "Subcomponents"],
8 | locales: "en-US",
9 | },
10 | },
11 | },
12 | };
13 |
14 | export default preview;
15 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "msedge",
6 | "request": "launch",
7 | "name": "Launch Glide Data Grid",
8 | "url": "http://localhost:9009",
9 | "webRoot": "${workspaceRoot}/",
10 | "preLaunchTask": "npm: start",
11 | "sourceMaps": true
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.workingDirectories": ["packages/core", "packages/cells", "packages/source"],
3 | "typescript.tsdk": "node_modules/typescript/lib"
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "npm: start",
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 |
10 | "problemMatcher": {
11 | "pattern": {
12 | "regexp": ".*"
13 | },
14 | "background": {
15 | "activeOnStart": true,
16 | "beginsPattern": "^.*Starting Storybook.*$", // Adjust if there's a more specific start message
17 | "endsPattern": "^.*Storybook \\d+.\\d+.\\d+ for react-webpack5 started.*$"
18 | }
19 | },
20 | "group": {
21 | "kind": "build",
22 | "isDefault": true
23 | },
24 | "presentation": {
25 | "reveal": "always",
26 | "panel": "new"
27 | }
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Getting set up to work on Glide Data Grid
2 |
3 | ### Setting Up Codespaces
4 |
5 | If you'd like to set up glide data grid locally and contribute, the easiest way to get up and running
6 | is to use Codespaces if you have access to it. If you do not, simply cloning the repo and running `npm install` also works!
7 |
8 | #### Steps
9 |
10 | - Click the green dropdown labeled code, there should be two tabs: local and codespaces.
11 | - Click on codespaces.
12 | - If this is your first time, then create a new codespace. It will open a new browser tab and build the docker container for it - there will be a button to open the environment in VSCode if you'd prefer to run it that way
13 | - You should see a screen that says `Setting up your codespace` As soon as that's done, you should see a VSCode like UI with files on the left.
14 |
15 | Once codespaces is up and running make sure `jq` is installed and then:
16 |
17 | ```bash
18 | npm run install && npm run storybook
19 | ```
20 |
21 | ## Forking the data grid?
22 |
23 | Please consider submitting your work for review. We are a small project, but we are super enthused when anyone comes by to help us build the best damned data grid on the internet.
24 |
25 | ## Contributing new cells
26 |
27 | If you wish to contribute new cells, please add them to the `cells` package. There are already other cells in that package which can be used as an example. If your cell editor requires additional third party dependencies please consider using a React.lazy to allow for code splitting.
28 |
29 | ## Any contributions you make will be under the MIT Software License
30 |
31 | In short, when you submit code changes, your submissions are understood to be under the same MIT License that covers the project. Feel free to contact the maintainers if that's a concern.
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 typeguard, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "assumptions": {
3 | "setPublicClassFields": true,
4 | "setSpreadProperties": true
5 | },
6 | "presets": [
7 | [
8 | "@babel/preset-env",
9 | {
10 | "modules": false,
11 | "targets": {
12 | "firefox": "60",
13 | "chrome": "67",
14 | "safari": "12.1"
15 | },
16 | "exclude": ["@babel/plugin-transform-template-literals"]
17 | }
18 | ],
19 | "@babel/preset-react",
20 | "@babel/preset-typescript"
21 | ],
22 | "plugins": ["@babel/plugin-proposal-class-properties"],
23 | "comments": false,
24 | "sourceMaps": true
25 | }
26 |
--------------------------------------------------------------------------------
/config/linaria.json:
--------------------------------------------------------------------------------
1 | {
2 | "classNameSlug": "gdg-[hash]"
3 | }
4 |
--------------------------------------------------------------------------------
/data-grid.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/data-grid.jpg
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/icon.png
--------------------------------------------------------------------------------
/media/data-grid-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/media/data-grid-dark.png
--------------------------------------------------------------------------------
/media/data-grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/media/data-grid.png
--------------------------------------------------------------------------------
/media/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/media/icon.png
--------------------------------------------------------------------------------
/packages/cells/.eslintignore:
--------------------------------------------------------------------------------
1 | build.cjs
2 | node_modules/
3 | dist/
4 | vitest.*.ts
--------------------------------------------------------------------------------
/packages/cells/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:react/recommended",
5 | "plugin:@typescript-eslint/recommended",
6 | "plugin:import/recommended",
7 | "plugin:import/typescript",
8 | "plugin:react-hooks/recommended"
9 | ],
10 | "plugins": ["react", "@typescript-eslint", "import"],
11 | "parser": "@typescript-eslint/parser",
12 | "parserOptions": {
13 | "project": ["./tsconfig.json"]
14 | },
15 | "rules": {
16 | "no-console": "warn",
17 | "guard-for-in": "error",
18 | "no-empty": "error",
19 | "no-shadow": "off",
20 | "import/no-cycle": "off",
21 | "import/namespace": "off",
22 | "import/named": "off",
23 | "import/no-unresolved": "off", // FIXME: Figure out how to make this enabled
24 | "@typescript-eslint/no-unused-vars": "off",
25 | "@typescript-eslint/explicit-function-return-type": "off",
26 | "@typescript-eslint/no-empty-interface": "off",
27 | "@typescript-eslint/no-inferrable-types": "off",
28 | "@typescript-eslint/no-explicit-any": "off",
29 | "@typescript-eslint/ban-ts-ignore": "off",
30 | "@typescript-eslint/camelcase": "off",
31 | "@typescript-eslint/interface-name-prefix": "off",
32 | "@typescript-eslint/no-floating-promises": "error",
33 | "@typescript-eslint/no-shadow": "error",
34 | "@typescript-eslint/no-misused-promises": [
35 | "error",
36 | {
37 | "checksVoidReturn": false
38 | }
39 | ],
40 | "@typescript-eslint/ban-ts-comment": "off",
41 | "@typescript-eslint/explicit-module-boundary-types": "off",
42 | "@typescript-eslint/ban-types": "off"
43 | },
44 | "overrides": [
45 | {
46 | "files": ["*.ts"],
47 | "rules": {
48 | "@typescript-eslint/strict-boolean-expressions": "error"
49 | }
50 | }
51 | ],
52 | "settings": {
53 | "react": {
54 | "version": "detect"
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/cells/.npmignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | .eslintrc
3 | tsconfig.json
4 | tsconfig.types.json
--------------------------------------------------------------------------------
/packages/cells/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 typeguard, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/cells/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Glide Data Grid Cells
4 |
5 | Additional cells and features for Glide Data Grid
6 |
7 | [](https://github.com/glideapps/glide-data-grid/releases)
8 | [](https://reactjs.org)
9 | [](https://bundlephobia.com/package/@glideapps/glide-data-grid-cells)
10 | [](https://github.com/glideapps/glide-data-grid/blob/main/LICENSE)
11 | [](https://www.glideapps.com/jobs)
12 |
13 | 
14 |
15 | Current cells
16 |
17 | - Star (Rating) Cell
18 | - Sparklines
19 | - Article
20 | - Dropdown
21 | - Range
22 | - User profile
23 | - Tags
24 |
25 | # Usage
26 |
27 | Step 1: Add the extra cells to your grid.
28 |
29 | ```tsx
30 | import { useExtraCells } from "@glideapps/glide-data-grid-cells";
31 |
32 | const Grid = () => {
33 | const { customRenderers } = useExtraCells();
34 | return ;
35 | };
36 | ```
37 |
38 | Step 2: Use the cells in your `getCellContent` callback
39 |
40 | ```ts
41 | import type { StarCell } from "@glideapps/glide-data-grid-cells";
42 |
43 | const getCellContent = React.useCallback(() => {
44 | const starCell: StarCell = {
45 | kind: GridCellKind.Custom,
46 | allowOverlay: true,
47 | copyData: "4 out of 5",
48 | data: {
49 | kind: "star-cell",
50 | label: "Test",
51 | rating: 4,
52 | },
53 | };
54 |
55 | return starCell;
56 | }, []);
57 | ```
58 |
59 | ## Note on ArticleCell
60 |
61 | The ArticleCell uses `@toast-ui/editor` to provide its editor. To make sure it works correctly your project will need to import the css file it depends on.
62 |
63 | ```
64 | import "@toast-ui/editor/dist/toastui-editor.css";
65 | ```
66 |
--------------------------------------------------------------------------------
/packages/cells/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -em
3 | source ../../config/build-util.sh
4 |
5 | ensure_bash_4
6 |
7 | shopt -s globstar
8 |
9 | echo -e "\033[0;36m🏗️ Building Glide Data Grid Cells 🏗️\033[0m"
10 |
11 | compile_esm() {
12 | compile esm true
13 | }
14 |
15 | compile_cjs() {
16 | compile cjs false
17 | }
18 |
19 | run_in_parallel compile_esm compile_cjs
20 |
21 | generate_index_css
22 |
23 | echo -e "\033[0;36m🎉 Cells Build Complete 🎉\033[0m"
--------------------------------------------------------------------------------
/packages/cells/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@glideapps/glide-data-grid-cells",
3 | "version": "6.0.4-alpha9",
4 | "description": "Extra cells for glide-data-grid",
5 | "sideEffects": [
6 | "**/*.css"
7 | ],
8 | "type": "module",
9 | "browser": "dist/esm/index.js",
10 | "main": "dist/cjs/index.js",
11 | "module": "dist/esm/index.js",
12 | "types": "dist/dts/index.d.ts",
13 | "exports": {
14 | ".": {
15 | "types": "./dist/dts/index.d.ts",
16 | "import": "./dist/esm/index.js",
17 | "require": "./dist/cjs/index.js"
18 | },
19 | "./dist/index.css": {
20 | "import": "./dist/index.css",
21 | "require": "./dist/index.css"
22 | }
23 | },
24 | "files": [
25 | "dist"
26 | ],
27 | "scripts": {
28 | "build": "./build.sh",
29 | "lint": "eslint src --ext .ts,.tsx",
30 | "test": "vitest"
31 | },
32 | "repository": {
33 | "type": "git",
34 | "url": "git+https://github.com/glideapps/glide-data-grid.git",
35 | "directory": "packages/cells"
36 | },
37 | "homepage": "https://github.com/glideapps/glide-data-grid/tree/main/cells",
38 | "author": "Glide",
39 | "license": "MIT",
40 | "keywords": [
41 | "react",
42 | "datagrid",
43 | "data-grid",
44 | "editor",
45 | "reactjs",
46 | "scrolling",
47 | "data",
48 | "table",
49 | "cell",
50 | "canvas"
51 | ],
52 | "dependencies": {
53 | "@glideapps/glide-data-grid": "6.0.4-alpha9",
54 | "@linaria/react": "^4.5.3",
55 | "@toast-ui/editor": "3.1.10",
56 | "@toast-ui/react-editor": "3.1.10",
57 | "react-select": "^5.8.0"
58 | },
59 | "devDependencies": {
60 | "@babel/cli": "^7.16.0",
61 | "@types/prosemirror-commands": "^1.0.4",
62 | "@types/react": "16.14.21",
63 | "eslint": "^8.19.0",
64 | "eslint-plugin-import": "^2.22.0",
65 | "eslint-plugin-react": "^7.21.5",
66 | "eslint-plugin-react-hooks": "^4.2.0",
67 | "react-resize-detector": "^7.1.2",
68 | "tsc-esm-fix": "^2.7.8",
69 | "typescript": "^5.1.6"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/packages/cells/src/cells/article-cell-types.ts:
--------------------------------------------------------------------------------
1 | import type { CustomCell } from "@glideapps/glide-data-grid";
2 |
3 | interface ArticleCellProps {
4 | readonly kind: "article-cell";
5 | readonly markdown: string;
6 | }
7 |
8 | export type ArticleCell = CustomCell;
9 |
--------------------------------------------------------------------------------
/packages/cells/src/cells/article-cell.tsx:
--------------------------------------------------------------------------------
1 | import type { ArticleCell } from "./article-cell-types.js";
2 | import * as React from "react";
3 | import { type CustomRenderer, getMiddleCenterBias, GridCellKind } from "@glideapps/glide-data-grid";
4 |
5 | const ArticleCellEditor = React.lazy(async () => await import("./article-cell-editor.js"));
6 |
7 | const renderer: CustomRenderer = {
8 | kind: GridCellKind.Custom,
9 | isMatch: (c): c is ArticleCell => (c.data as any).kind === "article-cell",
10 | draw: (args, cell) => {
11 | const { ctx, theme, rect } = args;
12 | const { markdown } = cell.data;
13 |
14 | let data = markdown;
15 | if (data.includes("\n")) {
16 | // new lines are rare and split is relatively expensive compared to the search
17 | // it pays off to not do the split contantly.
18 | data = data.split(/\r?\n/)[0];
19 | }
20 | const max = rect.width / 4; // no need to round, slice will just truncate this
21 | if (data.length > max) {
22 | data = data.slice(0, max);
23 | }
24 |
25 | ctx.fillStyle = theme.textDark;
26 | ctx.fillText(
27 | data,
28 | rect.x + theme.cellHorizontalPadding,
29 | rect.y + rect.height / 2 + getMiddleCenterBias(ctx, theme)
30 | );
31 |
32 | return true;
33 | },
34 | provideEditor: () => ({
35 | editor: p => {
36 | return (
37 |
38 |
39 |
40 | );
41 | },
42 | styleOverride: {
43 | position: "fixed",
44 | left: "12.5vw",
45 | top: "12.5vh",
46 | width: "75vw",
47 | borderRadius: "9px",
48 | maxWidth: "unset",
49 | maxHeight: "unset",
50 | },
51 | disablePadding: true,
52 | }),
53 | onPaste: (val, d) => ({
54 | ...d,
55 | markdown: val,
56 | }),
57 | };
58 |
59 | export default renderer;
60 |
--------------------------------------------------------------------------------
/packages/cells/src/cells/spinner-cell.tsx:
--------------------------------------------------------------------------------
1 | import { type CustomCell, type CustomRenderer, GridCellKind } from "@glideapps/glide-data-grid";
2 |
3 | interface SpinnerCellProps {
4 | readonly kind: "spinner-cell";
5 | }
6 |
7 | export type SpinnerCell = CustomCell;
8 |
9 | const renderer: CustomRenderer = {
10 | kind: GridCellKind.Custom,
11 | isMatch: (cell: CustomCell): cell is SpinnerCell => (cell.data as any).kind === "spinner-cell",
12 | draw: args => {
13 | const { ctx, theme, rect, requestAnimationFrame } = args;
14 |
15 | const progress = (window.performance.now() % 1000) / 1000;
16 |
17 | const x = rect.x + rect.width / 2;
18 | const y = rect.y + rect.height / 2;
19 | ctx.beginPath();
20 | ctx.arc(x, y, Math.min(12, rect.height / 6), Math.PI * 2 * progress, Math.PI * 2 * progress + Math.PI * 1.5);
21 |
22 | ctx.strokeStyle = theme.textMedium;
23 | ctx.lineWidth = 1.5;
24 | ctx.stroke();
25 |
26 | ctx.lineWidth = 1;
27 |
28 | requestAnimationFrame();
29 |
30 | return true;
31 | },
32 | provideEditor: () => undefined,
33 | };
34 |
35 | export default renderer;
36 |
--------------------------------------------------------------------------------
/packages/cells/src/draw-fns.ts:
--------------------------------------------------------------------------------
1 | interface CornerRadius {
2 | tl: number;
3 | tr: number;
4 | bl: number;
5 | br: number;
6 | }
7 |
8 | export function roundedRect(
9 | ctx: CanvasRenderingContext2D,
10 | x: number,
11 | y: number,
12 | width: number,
13 | height: number,
14 | radius: number | CornerRadius
15 | ) {
16 | if (width <= 0 || height <= 0) return;
17 | if (typeof radius === "number" && radius <= 0) {
18 | ctx.rect(x, y, width, height);
19 | return;
20 | }
21 | if (typeof radius === "number") {
22 | radius = { tl: radius, tr: radius, br: radius, bl: radius };
23 | }
24 |
25 | // restrict radius to a reasonable max
26 | radius = {
27 | tl: Math.min(radius.tl, height / 2, width / 2),
28 | tr: Math.min(radius.tr, height / 2, width / 2),
29 | bl: Math.min(radius.bl, height / 2, width / 2),
30 | br: Math.min(radius.br, height / 2, width / 2),
31 | };
32 |
33 | radius.tl = Math.max(0, radius.tl);
34 | radius.tr = Math.max(0, radius.tr);
35 | radius.br = Math.max(0, radius.br);
36 | radius.bl = Math.max(0, radius.bl);
37 |
38 | ctx.moveTo(x + radius.tl, y);
39 | ctx.arcTo(x + width, y, x + width, y + radius.tr, radius.tr);
40 | ctx.arcTo(x + width, y + height, x + width - radius.br, y + height, radius.br);
41 | ctx.arcTo(x, y + height, x, y + height - radius.bl, radius.bl);
42 | ctx.arcTo(x, y, x + radius.tl, y, radius.tl);
43 | }
44 |
--------------------------------------------------------------------------------
/packages/cells/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx"],
4 | "exclude": [
5 | "./src/**/*.stories.tsx",
6 | "./src/stories/*.tsx",
7 | "./src/docs/*.tsx",
8 | "./src/setupTests.ts",
9 | "./test/**/*.test.ts",
10 | "./test/**/*.test.tsx"
11 | ],
12 | "references": [{ "path": "../core" }],
13 | "compilerOptions": {
14 | "rootDir": "./src",
15 | "outDir": "./dist/cjs",
16 | "declarationDir": "./dist/dts",
17 | "module": "CommonJS",
18 | "verbatimModuleSyntax": false,
19 | "sourceMap": true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/cells/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx"],
4 | "exclude": [
5 | "./src/**/*.stories.tsx",
6 | "./src/stories/*.tsx",
7 | "./src/docs/*.tsx",
8 | "./src/setupTests.ts",
9 | "./test/**/*.test.ts",
10 | "./test/**/*.test.tsx"
11 | ],
12 | "references": [{ "path": "../core" }],
13 | "compilerOptions": {
14 | "rootDir": "./src",
15 | "outDir": "./dist/esm",
16 | "declarationDir": "./dist/dts",
17 | "sourceMap": true,
18 | "declaration": true,
19 | "declarationMap": true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/cells/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx", "./test/**/*.ts", "./test/**/*.tsx"],
4 | "references": [{ "path": "../core" }]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/cells/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react";
2 | import { defineConfig, configDefaults } from "vitest/config";
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | include: ["test/**/*.test.tsx", "test/**/*.test.ts"],
8 | environment: "jsdom",
9 | setupFiles: "vitest.setup.ts",
10 | threads: false,
11 | singleThread: true,
12 | watch: false,
13 | clearMocks: true,
14 | maxConcurrency: 5,
15 | fakeTimers: {
16 | toFake: [
17 | ...(configDefaults.fakeTimers.toFake ?? []),
18 | "performance",
19 | "requestAnimationFrame",
20 | "cancelAnimationFrame",
21 | ],
22 | },
23 | deps: {
24 | optimizer: {
25 | web: {
26 | include: ["vitest-canvas-mock"],
27 | },
28 | },
29 | },
30 | environmentOptions: {
31 | jsdom: {
32 | resources: "usable",
33 | },
34 | },
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/packages/cells/vitest.setup.ts:
--------------------------------------------------------------------------------
1 | import "vitest-canvas-mock";
2 | import { vi } from "vitest";
3 |
4 | // this is needed to make the canvas mock work for some reason
5 | global.jest = vi;
6 |
7 | global.ResizeObserver = vi.fn().mockImplementation(() => ({
8 | observe: jest.fn(),
9 | unobserve: jest.fn(),
10 | disconnect: jest.fn(),
11 | }));
12 |
13 | Image.prototype.decode = () => new Promise(resolve => window.setTimeout(resolve, 10));
14 |
--------------------------------------------------------------------------------
/packages/core/.eslintignore:
--------------------------------------------------------------------------------
1 | jest.config.js
2 | node_modules/
3 | build.cjs
4 | vitest.*.ts
--------------------------------------------------------------------------------
/packages/core/.npmignore:
--------------------------------------------------------------------------------
1 | tsconfig*
2 | coverage/*
--------------------------------------------------------------------------------
/packages/core/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 typeguard, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/core/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -em
3 | source ../../config/build-util.sh
4 |
5 | ensure_bash_4
6 |
7 | shopt -s globstar
8 |
9 | echo -e "\033[0;36m🏗️ Building Glide Data Grid 🏗️\033[0m"
10 |
11 | compile_esm() {
12 | compile esm true
13 | }
14 |
15 | compile_cjs() {
16 | compile cjs false
17 | }
18 |
19 | run_in_parallel compile_esm compile_cjs
20 |
21 | generate_index_css
22 |
23 | echo -e "\033[0;36m🎉 Core Build Complete 🎉\033[0m"
--------------------------------------------------------------------------------
/packages/core/src/cells/index.ts:
--------------------------------------------------------------------------------
1 | import { type InnerGridCell } from "../internal/data-grid/data-grid-types.js";
2 | import { booleanCellRenderer } from "./boolean-cell.js";
3 | import { bubbleCellRenderer } from "./bubble-cell.js";
4 | import type { InternalCellRenderer } from "./cell-types.js";
5 | import { drilldownCellRenderer } from "./drilldown-cell.js";
6 | import { imageCellRenderer } from "./image-cell.js";
7 | import { loadingCellRenderer } from "./loading-cell.js";
8 | import { markdownCellRenderer } from "./markdown-cell.js";
9 | import { markerCellRenderer } from "./marker-cell.js";
10 | import { newRowCellRenderer } from "./new-row-cell.js";
11 | import { numberCellRenderer } from "./number-cell.js";
12 | import { protectedCellRenderer } from "./protected-cell.js";
13 | import { rowIDCellRenderer } from "./row-id-cell.js";
14 | import { textCellRenderer } from "./text-cell.js";
15 | import { uriCellRenderer } from "./uri-cell.js";
16 |
17 | export const AllCellRenderers = [
18 | markerCellRenderer,
19 | newRowCellRenderer,
20 | booleanCellRenderer,
21 | bubbleCellRenderer,
22 | drilldownCellRenderer,
23 | imageCellRenderer,
24 | loadingCellRenderer,
25 | markdownCellRenderer,
26 | numberCellRenderer,
27 | protectedCellRenderer,
28 | rowIDCellRenderer,
29 | textCellRenderer,
30 | uriCellRenderer,
31 | ] as InternalCellRenderer[];
32 |
--------------------------------------------------------------------------------
/packages/core/src/cells/loading-cell.tsx:
--------------------------------------------------------------------------------
1 | import { withAlpha } from "../internal/data-grid/color-parser.js";
2 | import { roundedRect } from "../internal/data-grid/render/data-grid-lib.js";
3 | import { GridCellKind, type LoadingCell } from "../internal/data-grid/data-grid-types.js";
4 | import type { InternalCellRenderer } from "./cell-types.js";
5 |
6 | // returns a "random" number between -1 and 1
7 | function getRandomNumber(x: number, y: number): number {
8 | let seed = x * 49_632 + y * 325_176;
9 |
10 | // Inline Xorshift algorithm
11 | seed ^= seed << 13;
12 | seed ^= seed >> 17;
13 | seed ^= seed << 5;
14 |
15 | // eslint-disable-next-line unicorn/number-literal-case
16 | return (seed / 0xff_ff_ff_ff) * 2;
17 | }
18 |
19 | export const loadingCellRenderer: InternalCellRenderer = {
20 | getAccessibilityString: () => "",
21 | kind: GridCellKind.Loading,
22 | needsHover: false,
23 | useLabel: false,
24 | needsHoverPosition: false,
25 | measure: () => 120,
26 | draw: a => {
27 | const { cell, col, row, ctx, rect, theme } = a;
28 | if (cell.skeletonWidth === undefined || cell.skeletonWidth === 0) {
29 | return;
30 | }
31 |
32 | let width = cell.skeletonWidth;
33 | if (cell.skeletonWidthVariability !== undefined && cell.skeletonWidthVariability > 0) {
34 | width += Math.round(getRandomNumber(col, row) * cell.skeletonWidthVariability);
35 | }
36 |
37 | const hpad = theme.cellHorizontalPadding;
38 | if (width + hpad * 2 >= rect.width) {
39 | width = rect.width - hpad * 2 - 1;
40 | }
41 |
42 | const rectHeight = cell.skeletonHeight ?? Math.min(18, rect.height - 2 * theme.cellVerticalPadding);
43 |
44 | roundedRect(
45 | ctx,
46 | rect.x + hpad,
47 | rect.y + (rect.height - rectHeight) / 2,
48 | width,
49 | rectHeight,
50 | theme.roundingRadius ?? 3
51 | );
52 | ctx.fillStyle = withAlpha(theme.textDark, 0.1);
53 | ctx.fill();
54 | },
55 | onPaste: () => undefined,
56 | };
57 |
--------------------------------------------------------------------------------
/packages/core/src/cells/markdown-cell.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/display-name */
2 | import * as React from "react";
3 | import { MarkdownOverlayEditor } from "../internal/data-grid-overlay-editor/private/markdown-overlay-editor.js";
4 | import { drawTextCell, prepTextCell } from "../internal/data-grid/render/data-grid-lib.js";
5 | import { GridCellKind, type MarkdownCell } from "../internal/data-grid/data-grid-types.js";
6 | import type { InternalCellRenderer } from "./cell-types.js";
7 |
8 | export const markdownCellRenderer: InternalCellRenderer = {
9 | getAccessibilityString: c => c.data?.toString() ?? "",
10 | kind: GridCellKind.Markdown,
11 | needsHover: false,
12 | needsHoverPosition: false,
13 | drawPrep: prepTextCell,
14 | measure: (ctx, cell, t) => {
15 | const firstLine = cell.data.split("\n")[0];
16 | return ctx.measureText(firstLine).width + 2 * t.cellHorizontalPadding;
17 | },
18 | draw: a => drawTextCell(a, a.cell.data, a.cell.contentAlign),
19 | onDelete: c => ({
20 | ...c,
21 | data: "",
22 | }),
23 | provideEditor: () => p => {
24 | const { onChange, value, target, onFinishedEditing, markdownDivCreateNode, forceEditMode, validatedSelection } =
25 | p;
26 | return (
27 |
33 | onChange({
34 | ...value,
35 | data: e.target.value,
36 | })
37 | }
38 | forceEditMode={forceEditMode}
39 | createNode={markdownDivCreateNode}
40 | />
41 | );
42 | },
43 | onPaste: (toPaste, cell) => (toPaste === cell.data ? undefined : { ...cell, data: toPaste }),
44 | };
45 |
--------------------------------------------------------------------------------
/packages/core/src/cells/protected-cell.tsx:
--------------------------------------------------------------------------------
1 | import { degreesToRadians } from "../common/utils.js";
2 | import { GridCellKind, type ProtectedCell } from "../internal/data-grid/data-grid-types.js";
3 | import type { BaseDrawArgs, InternalCellRenderer } from "./cell-types.js";
4 |
5 | export const protectedCellRenderer: InternalCellRenderer = {
6 | getAccessibilityString: () => "",
7 | measure: () => 108,
8 | kind: GridCellKind.Protected,
9 | needsHover: false,
10 | needsHoverPosition: false,
11 | draw: drawProtectedCell,
12 | onPaste: () => undefined,
13 | };
14 |
15 | function drawProtectedCell(args: BaseDrawArgs) {
16 | const { ctx, theme, rect } = args;
17 | const { x, y, height: h } = rect;
18 |
19 | ctx.beginPath();
20 |
21 | const radius = 2.5;
22 | let xStart = x + theme.cellHorizontalPadding + radius;
23 | const center = y + h / 2;
24 | const p = Math.cos(degreesToRadians(30)) * radius;
25 | const q = Math.sin(degreesToRadians(30)) * radius;
26 |
27 | for (let i = 0; i < 12; i++) {
28 | ctx.moveTo(xStart, center - radius);
29 | ctx.lineTo(xStart, center + radius);
30 |
31 | ctx.moveTo(xStart + p, center - q);
32 | ctx.lineTo(xStart - p, center + q);
33 |
34 | ctx.moveTo(xStart - p, center - q);
35 | ctx.lineTo(xStart + p, center + q);
36 | xStart += 8;
37 | }
38 | ctx.lineWidth = 1.1;
39 | ctx.lineCap = "square";
40 | ctx.strokeStyle = theme.textLight;
41 | ctx.stroke();
42 | }
43 |
--------------------------------------------------------------------------------
/packages/core/src/cells/row-id-cell.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { GrowingEntry } from "../internal/growing-entry/growing-entry.js";
3 | import { drawTextCell, prepTextCell } from "../internal/data-grid/render/data-grid-lib.js";
4 | import { GridCellKind, type RowIDCell } from "../internal/data-grid/data-grid-types.js";
5 | import type { InternalCellRenderer } from "./cell-types.js";
6 |
7 | export const rowIDCellRenderer: InternalCellRenderer = {
8 | getAccessibilityString: c => c.data?.toString() ?? "",
9 | kind: GridCellKind.RowID,
10 | needsHover: false,
11 | needsHoverPosition: false,
12 | drawPrep: (a, b) => prepTextCell(a, b, a.theme.textLight),
13 | draw: a => drawTextCell(a, a.cell.data, a.cell.contentAlign),
14 | measure: (ctx, cell, theme) => ctx.measureText(cell.data).width + theme.cellHorizontalPadding * 2,
15 | // eslint-disable-next-line react/display-name
16 | provideEditor: () => p => {
17 | const { isHighlighted, onChange, value, validatedSelection } = p;
18 | return (
19 |
26 | onChange({
27 | ...value,
28 | data: e.target.value,
29 | })
30 | }
31 | />
32 | );
33 | },
34 | onPaste: () => undefined,
35 | };
36 |
--------------------------------------------------------------------------------
/packages/core/src/common/browser-detect.ts:
--------------------------------------------------------------------------------
1 | class Lazy {
2 | private fn: () => T;
3 | private val: T | undefined;
4 | constructor(fn: () => T) {
5 | this.fn = fn;
6 | }
7 |
8 | public get value() {
9 | return this.val ?? (this.val = this.fn());
10 | }
11 | }
12 |
13 | function lazy(fn: () => T) {
14 | return new Lazy(fn);
15 | }
16 |
17 | // next.js apps don't have window available at import time, so this will fail if its not lazy.
18 | export const browserIsFirefox = lazy(() => window.navigator.userAgent.includes("Firefox"));
19 | export const browserIsSafari = lazy(
20 | () =>
21 | window.navigator.userAgent.includes("Mac OS") &&
22 | window.navigator.userAgent.includes("Safari") &&
23 | !window.navigator.userAgent.includes("Chrome")
24 | );
25 | export const browserIsOSX = lazy(() => window.navigator.platform.toLowerCase().startsWith("mac"));
26 |
--------------------------------------------------------------------------------
/packages/core/src/common/resize-detector.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/strict-boolean-expressions */
2 | import { useLayoutEffect, useState, useRef, type MutableRefObject } from "react";
3 | interface ReactResizeDetectorDimensions {
4 | height?: number;
5 | width?: number;
6 | }
7 |
8 | export function useResizeDetector(
9 | initialSize?: readonly [width: number, height: number]
10 | ): UseResizeDetectorReturn {
11 | const ref = useRef(null);
12 |
13 | const [size, setSize] = useState({
14 | width: initialSize?.[0],
15 | height: initialSize?.[1],
16 | });
17 |
18 | useLayoutEffect(() => {
19 | const resizeCallback: ResizeObserverCallback = entries => {
20 | for (const entry of entries) {
21 | const { width, height } = (entry && entry.contentRect) || {};
22 | setSize(cv => (cv.width === width && cv.height === height ? cv : { width, height }));
23 | }
24 | };
25 |
26 | const resizeObserver = new window.ResizeObserver(resizeCallback);
27 |
28 | if (ref.current) {
29 | resizeObserver.observe(ref.current, undefined);
30 | }
31 |
32 | return () => {
33 | resizeObserver.disconnect();
34 | };
35 | // eslint-disable-next-line react-hooks/exhaustive-deps
36 | }, [ref.current]);
37 |
38 | return { ref, ...size };
39 | }
40 |
41 | export interface UseResizeDetectorReturn extends ReactResizeDetectorDimensions {
42 | ref: MutableRefObject;
43 | }
44 |
--------------------------------------------------------------------------------
/packages/core/src/data-editor-all.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { DataEditor, type DataEditorProps, type DataEditorRef } from "./data-editor/data-editor.js";
3 | import { AllCellRenderers } from "./cells/index.js";
4 | import { sprites } from "./internal/data-grid/sprites.js";
5 | import ImageWindowLoaderImpl from "./common/image-window-loader.js";
6 | import type { ImageWindowLoader } from "./internal/data-grid/image-window-loader-interface.js";
7 |
8 | export interface DataEditorAllProps extends Omit {
9 | imageWindowLoader?: ImageWindowLoader;
10 | }
11 |
12 | const DataEditorAllImpl: React.ForwardRefRenderFunction = (p, ref) => {
13 | const allSprites = React.useMemo(() => {
14 | return { ...sprites, ...p.headerIcons };
15 | }, [p.headerIcons]);
16 |
17 | const imageWindowLoader = React.useMemo(() => {
18 | return p.imageWindowLoader ?? new ImageWindowLoaderImpl();
19 | }, [p.imageWindowLoader]);
20 |
21 | return (
22 |
29 | );
30 | };
31 |
32 | export const DataEditorAll = React.forwardRef(DataEditorAllImpl);
33 |
--------------------------------------------------------------------------------
/packages/core/src/data-editor/use-autoscroll.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import type { GridMouseCellEventArgs } from "../internal/data-grid/event-args.js";
3 |
4 | const maxPxPerMs = 2;
5 | const msToFullSpeed = 1300;
6 |
7 | export function useAutoscroll(
8 | scrollDirection: GridMouseCellEventArgs["scrollEdge"] | undefined,
9 | scrollRef: React.MutableRefObject,
10 | onScroll?: () => void
11 | ) {
12 | const speedScalar = React.useRef(0);
13 | const [xDir, yDir] = scrollDirection ?? [0, 0];
14 | React.useEffect(() => {
15 | if (xDir === 0 && yDir === 0) {
16 | speedScalar.current = 0;
17 | return;
18 | }
19 | let cancelled = false;
20 |
21 | let lastTime = 0;
22 | const scrollFn = (curTime: number) => {
23 | if (cancelled) return;
24 | if (lastTime === 0) {
25 | lastTime = curTime;
26 | } else {
27 | const step = curTime - lastTime;
28 | speedScalar.current = Math.min(1, speedScalar.current + step / msToFullSpeed);
29 | const motion = speedScalar.current ** 1.618 * step * maxPxPerMs;
30 | scrollRef.current?.scrollBy(xDir * motion, yDir * motion);
31 | lastTime = curTime;
32 | onScroll?.();
33 | }
34 | window.requestAnimationFrame(scrollFn);
35 | };
36 | window.requestAnimationFrame(scrollFn);
37 | return () => {
38 | cancelled = true;
39 | };
40 | }, [scrollRef, xDir, yDir, onScroll]);
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/data-editor/use-rem-adjuster.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getDataEditorTheme, type Theme } from "../common/styles.js";
3 |
4 | interface DataEditorDimensions {
5 | rowHeight: number | ((n: number) => number);
6 | headerHeight: number;
7 | groupHeaderHeight: number;
8 | theme: Partial | undefined;
9 | overscrollX: number | undefined;
10 | overscrollY: number | undefined;
11 | }
12 |
13 | interface DataEditorProps {
14 | rowHeight: number | ((n: number) => number);
15 | headerHeight: number;
16 | groupHeaderHeight: number;
17 | theme?: Partial;
18 | overscrollX?: number;
19 | overscrollY?: number;
20 | scaleToRem: boolean;
21 | remSize: number;
22 | }
23 |
24 | export function useRemAdjuster({
25 | rowHeight: rowHeightIn,
26 | headerHeight: headerHeightIn,
27 | groupHeaderHeight: groupHeaderHeightIn,
28 | theme: themeIn,
29 | overscrollX: overscrollXIn,
30 | overscrollY: overscrollYIn,
31 | scaleToRem,
32 | remSize,
33 | }: DataEditorProps): DataEditorDimensions {
34 | const [rowHeight, headerHeight, groupHeaderHeight, theme, overscrollX, overscrollY] = React.useMemo(() => {
35 | if (!scaleToRem || remSize === 16)
36 | return [rowHeightIn, headerHeightIn, groupHeaderHeightIn, themeIn, overscrollXIn, overscrollYIn];
37 | const scaler = remSize / 16;
38 | const rh = rowHeightIn;
39 | const bt = getDataEditorTheme();
40 | return [
41 | typeof rh === "number" ? rh * scaler : (n: number) => Math.ceil(rh(n) * scaler),
42 | Math.ceil(headerHeightIn * scaler),
43 | Math.ceil(groupHeaderHeightIn * scaler),
44 | {
45 | ...themeIn,
46 | headerIconSize: (themeIn?.headerIconSize ?? bt.headerIconSize) * scaler,
47 | cellHorizontalPadding: (themeIn?.cellHorizontalPadding ?? bt.cellHorizontalPadding) * scaler,
48 | cellVerticalPadding: (themeIn?.cellVerticalPadding ?? bt.cellVerticalPadding) * scaler,
49 | },
50 | Math.ceil((overscrollXIn ?? 0) * scaler),
51 | Math.ceil((overscrollYIn ?? 0) * scaler),
52 | ];
53 | }, [groupHeaderHeightIn, headerHeightIn, overscrollXIn, overscrollYIn, remSize, rowHeightIn, scaleToRem, themeIn]);
54 |
55 | return { rowHeight, headerHeight, groupHeaderHeight, theme, overscrollX, overscrollY };
56 | }
57 |
--------------------------------------------------------------------------------
/packages/core/src/data-editor/visible-region.ts:
--------------------------------------------------------------------------------
1 | import { type Rectangle, type Item } from "../internal/data-grid/data-grid-types.js";
2 |
3 | export type VisibleRegion = Rectangle & {
4 | /** value in px */
5 | tx?: number;
6 | /** value in px */
7 | ty?: number;
8 | extras?: {
9 | selected?: Item;
10 | /**
11 | * @deprecated
12 | */
13 | freezeRegion?: Rectangle;
14 |
15 | /**
16 | * All visible freeze regions
17 | */
18 | freezeRegions?: readonly Rectangle[];
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/packages/core/src/docs/00-faq.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { SimpleThemeWrapper } from "../stories/story-utils.js";
4 | import { DocWrapper, Marked } from "./doc-wrapper.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/Docs",
8 | decorators: [
9 | (Story: React.ComponentType) => (
10 |
11 |
12 |
13 | ),
14 | ],
15 | };
16 |
17 | export const FAQ: React.VFC = () => {
18 | return (
19 |
20 |
21 | {`
22 | # FAQ
23 |
24 | ### Nothing shows up? It crashes when I edit a cell?
25 |
26 | Please read the [Prerequisites section in the docs](https://github.com/glideapps/glide-data-grid/blob/main/packages/core/API.md).
27 |
28 | ### Does it work with screen readers and other a11y tools?
29 |
30 | Yes. Unfortunately none of the primary developers are accessibility users so there are likely flaws in the implementation we are not aware of. Bug reports welcome!
31 |
32 | ### Does it support my data source?
33 |
34 | Yes.
35 |
36 | Data Grid is agnostic about the way you load/store/generate/mutate your data. What it requires is that you tell it which columns you have, how many rows, and to give it a function it can call to get the data for a cell in a specific row and column.
37 |
38 | ### Does it do sorting?
39 |
40 | Yes through the [glide-data-grid-source](https://www.npmjs.com/package/@glideapps/glide-data-grid-source) package.
41 |
42 | ### Does it do search?
43 |
44 | Yes, built in! There are examples in the storybook.
45 |
46 | ### Can it filter?
47 |
48 | Nothing built in yet. It is planned for the \`glide-data-grid-source\`.
49 |
50 | ### Can it do frozen columns?
51 |
52 | Yes
53 |
54 | ### Can I render my own cells?
55 |
56 | Yes
57 |
58 | `}
59 |
60 |
61 | );
62 | };
63 | (FAQ as any).storyName = "00. FAQ";
64 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/add-column.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | MoreInfo,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 | You can add and remove columns at your disposal
23 | Use the story's controls to change the number of columns
24 | >
25 | }>
26 |
27 |
28 |
29 | ),
30 | ],
31 | };
32 |
33 | interface AddColumnsProps {
34 | columnsCount: number;
35 | }
36 |
37 | export const AddColumns: React.FC = p => {
38 | const { cols, getCellContent } = useMockDataGenerator(p.columnsCount);
39 |
40 | return (
41 |
49 | );
50 | };
51 | (AddColumns as any).args = {
52 | columnsCount: 10,
53 | };
54 | (AddColumns as any).argTypes = {
55 | columnsCount: {
56 | control: {
57 | type: "range",
58 | min: 2,
59 | max: 200,
60 | },
61 | },
62 | };
63 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/add-data-to-top.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | useMockDataGenerator,
7 | defaultProps,
8 | clearCell,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 |
23 | You can return a different location to have the new row append take place.
24 |
25 | >
26 | }>
27 |
28 |
29 |
30 | ),
31 | ],
32 | };
33 |
34 | export const AddDataToTop: React.VFC = () => {
35 | const { cols, getCellContent, setCellValueRaw, setCellValue } = useMockDataGenerator(60, false);
36 |
37 | const [numRows, setNumRows] = React.useState(50);
38 |
39 | const onRowAppended = React.useCallback(async () => {
40 | // shift all of the existing cells down
41 | for (let y = numRows; y > 0; y--) {
42 | for (let x = 0; x < 6; x++) {
43 | setCellValueRaw([x, y], getCellContent([x, y - 1]));
44 | }
45 | }
46 | for (let c = 0; c < 6; c++) {
47 | const cell = getCellContent([c, 0]);
48 | setCellValueRaw([c, 0], clearCell(cell));
49 | }
50 | setNumRows(cv => cv + 1);
51 | return "top" as const;
52 | }, [getCellContent, numRows, setCellValueRaw]);
53 |
54 | return (
55 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/all-cell-kinds.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | defaultProps,
8 | useAllMockedKinds,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 | Data grid supports plenty cell kinds. Anything under GridCellKind .
23 |
24 | }>
25 |
26 |
27 |
28 | ),
29 | ],
30 | };
31 |
32 | export const AllCellKinds: React.VFC = () => {
33 | const { cols, getCellContent, onColumnResize, setCellValue } = useAllMockedKinds();
34 |
35 | return (
36 |
60 | );
61 | };
62 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/automatic-row-markers.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | MoreInfo,
7 | PropName,
8 | useMockDataGenerator,
9 | KeyName,
10 | defaultProps,
11 | } from "../../data-editor/stories/utils.js";
12 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
13 |
14 | export default {
15 | title: "Glide-Data-Grid/DataEditor Demos",
16 |
17 | decorators: [
18 | (Story: React.ComponentType) => (
19 |
20 |
21 |
22 | ),
23 | ],
24 | };
25 |
26 | export const AutomaticRowMarkers: React.VFC = () => {
27 | const { cols, getCellContent } = useMockDataGenerator(6);
28 |
29 | const dataEditor = (
30 |
37 | );
38 |
39 | return (
40 |
44 |
45 | You can enable row markers with rich selection behavior using the{" "}
46 | rowMarkers prop.
47 |
48 |
49 | Use ⇧ + click to make range selections, and Ctrl (
50 | ⌘ on Mac) + click to add/remove individual rows.
51 |
52 | >
53 | }>
54 | {dataEditor}
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/column-groups.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { GridColumnIcon } from "../../internal/data-grid/data-grid-types.js";
11 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
12 |
13 | export default {
14 | title: "Glide-Data-Grid/DataEditor Demos",
15 |
16 | decorators: [
17 | (Story: React.ComponentType) => (
18 |
19 |
23 | Columns in the data grid may be grouped by setting their group {" "}
24 | property.
25 |
26 | }>
27 |
28 |
29 |
30 | ),
31 | ],
32 | };
33 |
34 | export const ColumnGroups: React.VFC = () => {
35 | const { cols, getCellContent } = useMockDataGenerator(20, true, true);
36 |
37 | return (
38 | window.alert(`Please rename group ${x} to ${y}`)}
42 | columns={cols}
43 | rows={1000}
44 | getGroupDetails={g => ({
45 | name: g,
46 | icon: g === "" ? undefined : GridColumnIcon.HeaderCode,
47 | })}
48 | rowMarkers="both"
49 | />
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/content-alignment.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | defaultProps,
8 | useAllMockedKinds,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 | You can customize the content alignment by setting contentAlign of a
23 | cell to left , right or center
24 | .
25 |
26 | }>
27 |
28 |
29 |
30 | ),
31 | ],
32 | };
33 |
34 | export const ContentAlignment: React.VFC = () => {
35 | const { cols, getCellContent } = useAllMockedKinds();
36 |
37 | const mangledGetCellContent = React.useCallback(
38 | cell => {
39 | const [col, _row] = cell;
40 | if (col === 3) {
41 | return {
42 | ...getCellContent(cell),
43 | contentAlign: "center",
44 | };
45 | }
46 | if (col === 4) {
47 | return {
48 | ...getCellContent(cell),
49 | contentAlign: "right",
50 | };
51 | }
52 | if (col === 5) {
53 | return {
54 | ...getCellContent(cell),
55 | contentAlign: "left",
56 | };
57 | }
58 | return getCellContent(cell);
59 | },
60 | [getCellContent]
61 | );
62 |
63 | return ;
64 | };
65 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/copy-support.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | MoreInfo,
7 | PropName,
8 | useMockDataGenerator,
9 | defaultProps,
10 | } from "../../data-editor/stories/utils.js";
11 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
12 |
13 | export default {
14 | title: "Glide-Data-Grid/DataEditor Demos",
15 |
16 | decorators: [
17 | (Story: React.ComponentType) => (
18 |
19 |
23 |
24 | Large amounts of data can be copied and customized using{" "}
25 | getCellsForSelection .
26 |
27 |
28 | The data is copied into a format ready to be pasted into Excel or Google Sheets
29 |
30 |
40 | >
41 | }>
42 |
43 |
44 |
45 | ),
46 | ],
47 | };
48 |
49 | export const CopySupport: React.VFC = () => {
50 | const { cols, getCellContent, onColumnResize, setCellValue } = useMockDataGenerator(10, false);
51 |
52 | return (
53 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/drag-source.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 |
23 | Setting the isDraggable prop can allow for more granular control
24 | over what is draggable in the grid via HTML drag and drop.
25 |
26 | >
27 | }>
28 |
29 |
30 |
31 | ),
32 | ],
33 | };
34 |
35 | export const DragSource: React.VFC<{ isDraggable: boolean | "header" | "cell" }> = p => {
36 | const { cols, getCellContent, onColumnResize } = useMockDataGenerator(200);
37 |
38 | return (
39 | window.alert(`Moved row ${s} to ${e}`)}
46 | onColumnMoved={(s, e) => window.alert(`Moved col ${s} to ${e}`)}
47 | onColumnResize={onColumnResize}
48 | isDraggable={p.isDraggable}
49 | onDragStart={e => {
50 | e.setData("text/plain", "Drag data here!");
51 | }}
52 | />
53 | );
54 | };
55 | (DragSource as any).argTypes = {
56 | isDraggable: {
57 | control: { type: "select" },
58 | options: [true, false, "cell", "header"],
59 | },
60 | };
61 | (DragSource as any).args = {
62 | isDraggable: false,
63 | };
64 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/freeze-columns.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 | Columns at the start of your grid can be frozen in place by settings{" "}
23 | freezeColumns to a number greater than 0.
24 |
25 | }>
26 |
27 |
28 |
29 | ),
30 | ],
31 | };
32 |
33 | export const FreezeColumns: React.VFC = (p: { freezeColumns: number }) => {
34 | const { cols, getCellContent } = useMockDataGenerator(100);
35 |
36 | return (
37 |
46 | );
47 | };
48 | (FreezeColumns as any).argTypes = {
49 | freezeColumns: {
50 | control: {
51 | type: "range",
52 | min: 0,
53 | max: 10,
54 | },
55 | },
56 | };
57 | (FreezeColumns as any).args = {
58 | freezeColumns: 1,
59 | };
60 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/layout-integration.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import { Description, useMockDataGenerator, defaultProps, BeautifulStyle } from "../../data-editor/stories/utils.js";
4 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/DataEditor Demos",
8 |
9 | decorators: [
10 | (Story: React.ComponentType) => (
11 |
12 |
13 | Layout Integration
14 | Trying the grid in different situations
15 |
16 |
17 |
18 | ),
19 | ],
20 | };
21 |
22 | export const LayoutIntegration: React.VFC = () => {
23 | const { cols, getCellContent } = useMockDataGenerator(1000, true, true);
24 |
25 | return (
26 | <>
27 |
35 |
36 |
37 |
44 |
This is some text what happens here?
45 |
46 | >
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/multi-select-columns.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | MoreInfo,
7 | PropName,
8 | useMockDataGenerator,
9 | KeyName,
10 | defaultProps,
11 | } from "../../data-editor/stories/utils.js";
12 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
13 |
14 | export default {
15 | title: "Glide-Data-Grid/DataEditor Demos",
16 |
17 | decorators: [
18 | (Story: React.ComponentType) => (
19 |
20 |
24 |
25 | You can select multiple columns by using the selectedColumns and{" "}
26 | onSelectedColumnsChange props
27 |
28 |
29 | Here you can multi select columns by using Ctrl (on Windows) or{" "}
30 | ⌘ (on Mac)
31 |
32 | >
33 | }>
34 |
35 |
36 |
37 | ),
38 | ],
39 | };
40 |
41 | export const MultiSelectColumns: React.VFC = () => {
42 | const { cols, getCellContent } = useMockDataGenerator(100);
43 |
44 | return (
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/new-column-button.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | ColumnAddButton,
10 | } from "../../data-editor/stories/utils.js";
11 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
12 |
13 | export default {
14 | title: "Glide-Data-Grid/DataEditor Demos",
15 |
16 | decorators: [
17 | (Story: React.ComponentType) => (
18 |
19 |
23 | A new column button can be created using the rightElement .
24 |
25 | }>
26 |
27 |
28 |
29 | ),
30 | ],
31 | };
32 |
33 | export const NewColumnButton: React.VFC = () => {
34 | const { cols, getCellContent } = useMockDataGenerator(10, true);
35 |
36 | const columns = React.useMemo(() => cols.map(c => ({ ...c, grow: 1 })), [cols]);
37 |
38 | return (
39 |
45 | window.alert("Add a column!")}>+
46 |
47 | }
48 | rightElementProps={{
49 | fill: false,
50 | sticky: false,
51 | }}
52 | rows={3000}
53 | rowMarkers="both"
54 | />
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/observe-visible-region.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | MoreInfo,
7 | PropName,
8 | useMockDataGenerator,
9 | KeyName,
10 | defaultProps,
11 | } from "../../data-editor/stories/utils.js";
12 | import type { Rectangle } from "../../internal/data-grid/data-grid-types.js";
13 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
14 |
15 | export default {
16 | title: "Glide-Data-Grid/DataEditor Demos",
17 |
18 | decorators: [
19 | (Story: React.ComponentType) => (
20 |
21 |
22 |
23 | ),
24 | ],
25 | };
26 |
27 | export const ObserveVisibleRegion: React.VFC = () => {
28 | const { cols, getCellContent } = useMockDataGenerator(100);
29 |
30 | const [visibleRegion, setVisibleRegion] = React.useState({ x: 0, y: 0, width: 0, height: 0 });
31 |
32 | return (
33 |
37 |
38 | The visible region can be observed using onVisibleRegionChanged
39 |
40 |
41 | Then current visible region is x:{visibleRegion.x} y:
42 | {visibleRegion.y} width:
43 | {visibleRegion.width} height:{visibleRegion.height}
44 |
45 | >
46 | }>
47 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/one-hundred-thousand-columns.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import { BeautifulWrapper, Description, useMockDataGenerator, defaultProps } from "../../data-editor/stories/utils.js";
4 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/DataEditor Demos",
8 |
9 | decorators: [
10 | (Story: React.ComponentType) => (
11 |
12 |
16 | Data grid supports way more columns than you will ever need. Also this is rendering 10
17 | million cells but that's not important.
18 |
19 | }>
20 |
21 |
22 |
23 | ),
24 | ],
25 | };
26 |
27 | export const OneHundredThousandCols: React.VFC = () => {
28 | const { cols, getCellContent } = useMockDataGenerator(100_000);
29 |
30 | return ;
31 | };
32 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/one-million-rows.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import { BeautifulWrapper, Description, useMockDataGenerator, defaultProps } from "../../data-editor/stories/utils.js";
4 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/DataEditor Demos",
8 |
9 | decorators: [
10 | (Story: React.ComponentType) => (
11 |
12 | Data grid supports over 1 million rows. Your limit is mostly RAM.
16 | }>
17 |
18 |
19 |
20 | ),
21 | ],
22 | };
23 |
24 | export const OneMillionRows: React.VFC = () => {
25 | const { cols, getCellContent } = useMockDataGenerator(6);
26 |
27 | return (
28 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/overscroll.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 |
23 | You can allocate extra space at the ends of the grid by setting the{" "}
24 | overscrollX and overscrollY props
25 |
26 | >
27 | }>
28 |
29 |
30 |
31 | ),
32 | ],
33 | };
34 |
35 | interface OverscrollProps {
36 | overscrollX: number;
37 | overscrollY: number;
38 | }
39 |
40 | export const Overscroll: React.VFC = p => {
41 | const { overscrollX, overscrollY } = p;
42 | const { cols, getCellContent } = useMockDataGenerator(20);
43 |
44 | return (
45 |
53 | );
54 | };
55 | (Overscroll as any).argTypes = {
56 | overscrollX: {
57 | control: {
58 | type: "range",
59 | min: 0,
60 | max: 600,
61 | },
62 | },
63 | overscrollY: {
64 | control: {
65 | type: "range",
66 | min: 0,
67 | max: 600,
68 | },
69 | },
70 | };
71 | (Overscroll as any).args = {
72 | overscrollX: 200,
73 | overscrollY: 200,
74 | };
75 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/padding.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 |
23 | You can add padding at the ends of the grid by setting the{" "}
24 | paddingRight and paddingBottom props
25 |
26 | >
27 | }>
28 |
29 |
30 |
31 | ),
32 | ],
33 | };
34 |
35 | interface PaddingProps {
36 | paddingRight: number;
37 | paddingBottom: number;
38 | }
39 |
40 | export const Padding: React.VFC = p => {
41 | const { paddingRight, paddingBottom } = p;
42 | const { cols, getCellContent } = useMockDataGenerator(20);
43 |
44 | return (
45 |
53 | );
54 | };
55 | (Padding as any).argTypes = {
56 | paddingRight: {
57 | control: {
58 | type: "range",
59 | min: 0,
60 | max: 600,
61 | },
62 | },
63 | paddingBottom: {
64 | control: {
65 | type: "range",
66 | min: 0,
67 | max: 600,
68 | },
69 | },
70 | };
71 | (Padding as any).args = {
72 | paddingRight: 200,
73 | paddingBottom: 200,
74 | };
75 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/prevent-diagonal-scroll.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 |
23 | Diagonal scrolling can be prevented by setting{" "}
24 | preventDiagonalScrolling .
25 |
26 | >
27 | }>
28 |
29 |
30 |
31 | ),
32 | ],
33 | };
34 |
35 | export const PreventDiagonalScroll: React.VFC = () => {
36 | const { cols, getCellContent } = useMockDataGenerator(200);
37 |
38 | return (
39 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/row-and-header-sizes.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | MoreInfo,
7 | PropName,
8 | useMockDataGenerator,
9 | defaultProps,
10 | } from "../../data-editor/stories/utils.js";
11 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
12 |
13 | export default {
14 | title: "Glide-Data-Grid/DataEditor Demos",
15 |
16 | decorators: [
17 | (Story: React.ComponentType) => (
18 |
19 |
23 |
24 | The row size can be controlled with rowHeight and the header size
25 | with headerHeight .
26 |
27 | Use the story's controls to resize them
28 | >
29 | }>
30 |
31 |
32 |
33 | ),
34 | ],
35 | };
36 |
37 | interface RowAndHeaderSizesProps {
38 | rowHeight: number;
39 | headerHeight: number;
40 | }
41 | export const RowAndHeaderSizes: React.VFC = p => {
42 | const { cols, getCellContent } = useMockDataGenerator(6);
43 |
44 | return (
45 |
54 | );
55 | };
56 | (RowAndHeaderSizes as any).args = {
57 | rowHeight: 34,
58 | headerHeight: 34,
59 | };
60 | (RowAndHeaderSizes as any).argTypes = {
61 | rowHeight: {
62 | control: {
63 | type: "range",
64 | min: 20,
65 | max: 200,
66 | },
67 | },
68 | headerHeight: {
69 | control: {
70 | type: "range",
71 | min: 20,
72 | max: 200,
73 | },
74 | },
75 | };
76 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/row-hover.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | defaultProps,
8 | useAllMockedKinds,
9 | } from "../../data-editor/stories/utils.js";
10 | import type { GetRowThemeCallback } from "../../internal/data-grid/render/data-grid-render.cells.js";
11 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
12 | import type { GridMouseEventArgs } from "../../internal/data-grid/event-args.js";
13 |
14 | export default {
15 | title: "Glide-Data-Grid/DataEditor Demos",
16 |
17 | decorators: [
18 | (Story: React.ComponentType) => (
19 |
20 |
24 | Through careful usage of the onItemHovered callback it is possible to
25 | easily create a row hover effect.
26 |
27 | }>
28 |
29 |
30 |
31 | ),
32 | ],
33 | };
34 |
35 | export const RowHover: React.VFC = () => {
36 | const { cols, getCellContent } = useAllMockedKinds();
37 |
38 | const [hoverRow, setHoverRow] = React.useState(undefined);
39 |
40 | const onItemHovered = React.useCallback((args: GridMouseEventArgs) => {
41 | const [_, row] = args.location;
42 | setHoverRow(args.kind !== "cell" ? undefined : row);
43 | }, []);
44 |
45 | const getRowThemeOverride = React.useCallback(
46 | row => {
47 | if (row !== hoverRow) return undefined;
48 | return {
49 | bgCell: "#f7f7f7",
50 | bgCellMedium: "#f0f0f0",
51 | };
52 | },
53 | [hoverRow]
54 | );
55 |
56 | return (
57 |
66 | );
67 | };
68 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/row-markers.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { type RowMarkerOptions } from "../../data-editor/data-editor.js";
3 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
4 | import {
5 | BeautifulWrapper,
6 | Description,
7 | PropName,
8 | useMockDataGenerator,
9 | defaultProps,
10 | } from "../../data-editor/stories/utils.js";
11 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
12 |
13 | export default {
14 | title: "Glide-Data-Grid/DataEditor Demos",
15 |
16 | decorators: [
17 | (Story: React.ComponentType) => (
18 |
19 |
23 |
24 | Row Markers can be controlled by setting the rowMarkers prop.
25 |
26 | >
27 | }>
28 |
29 |
30 |
31 | ),
32 | ],
33 | };
34 |
35 | interface RowMarkersProps {
36 | markers: RowMarkerOptions["kind"];
37 | }
38 |
39 | export const RowMarkers: React.VFC = p => {
40 | const { cols, getCellContent } = useMockDataGenerator(10, false);
41 |
42 | return (
43 |
58 | );
59 | };
60 | (RowMarkers as any).args = {
61 | markers: "both",
62 | };
63 | (RowMarkers as any).argTypes = {
64 | markers: {
65 | control: { type: "select" },
66 | options: ["both", "checkbox", "number", "none", "clickable-number", "checkbox-visible"],
67 | },
68 | };
69 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/scaled-view.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import { BeautifulWrapper, Description, useMockDataGenerator, defaultProps } from "../../data-editor/stories/utils.js";
4 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/DataEditor Demos",
8 |
9 | decorators: [
10 | (Story: React.ComponentType) => (
11 |
12 | The data editor supports being scaled.}
15 | scale="0.5">
16 |
17 |
18 |
19 | ),
20 | ],
21 | };
22 |
23 | export const ScaledView: React.VFC = () => {
24 | const { cols, getCellContent, onColumnResize } = useMockDataGenerator(60);
25 |
26 | return (
27 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/scroll-offset.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 | import _ from "lodash";
12 |
13 | export default {
14 | title: "Glide-Data-Grid/DataEditor Demos",
15 |
16 | decorators: [
17 | (Story: React.ComponentType) => (
18 |
19 |
23 | The rowGrouping prop can be used to group and even fold rows.
24 |
25 | }>
26 |
27 |
28 |
29 | ),
30 | ],
31 | };
32 |
33 | export const ScrollOffset: React.VFC = () => {
34 | const { cols, getCellContent } = useMockDataGenerator(100);
35 | const rows = 1000;
36 |
37 | return (
38 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/silly-numbers.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import { BeautifulWrapper, Description, useMockDataGenerator, defaultProps } from "../../data-editor/stories/utils.js";
4 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/DataEditor Demos",
8 |
9 | decorators: [
10 | (Story: React.ComponentType) => (
11 |
12 |
16 | 100 million rows is silly. Once we cross about 33 million pixels in height we can no longer
17 | trust the browser to scroll accurately.
18 |
19 | }>
20 |
21 |
22 |
23 | ),
24 | ],
25 | };
26 |
27 | export const SillyNumbers: React.VFC = () => {
28 | const { cols, getCellContent } = useMockDataGenerator(6);
29 |
30 | return (
31 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/small-editable-grid.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import { BeautifulWrapper, Description, useMockDataGenerator, defaultProps } from "../../data-editor/stories/utils.js";
4 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/DataEditor Demos",
8 |
9 | decorators: [
10 | (Story: React.ComponentType) => (
11 |
12 |
16 | Data grid supports overlay editors for changing values. There are bespoke editors for
17 | numbers, strings, images, booleans, markdown, and uri.
18 |
19 | }>
20 |
21 |
22 |
23 | ),
24 | ],
25 | };
26 |
27 | export const SmallEditableGrid = () => {
28 | const { cols, getCellContent, setCellValue } = useMockDataGenerator(6, false);
29 |
30 | return (
31 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/smooth-scrolling-grid.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 | You can enable smooth scrolling with the smoothScrollX and{" "}
23 | smoothScrollY props. Disabling smooth scrolling can dramatically
24 | increase performance and improve visual stability during rapid scrolling.
25 |
26 | }>
27 |
28 |
29 |
30 | ),
31 | ],
32 | };
33 |
34 | interface SmoothScrollingGridProps {
35 | smoothScrollX: boolean;
36 | smoothScrollY: boolean;
37 | }
38 |
39 | export const SmoothScrollingGrid: React.FC = p => {
40 | const { cols, getCellContent } = useMockDataGenerator(30);
41 |
42 | return (
43 |
51 | );
52 | };
53 | (SmoothScrollingGrid as any).args = {
54 | smoothScrollX: false,
55 | smoothScrollY: false,
56 | };
57 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/stretch-column-size.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 | Columns in the data grid may be set to grow to fill space by setting the{" "}
23 | grow prop.
24 |
25 | }>
26 |
27 |
28 |
29 | ),
30 | ],
31 | };
32 |
33 | export const StretchColumnSize: React.VFC = () => {
34 | const { cols, getCellContent, onColumnResize } = useMockDataGenerator(5, true, true);
35 |
36 | const hasResized = React.useRef(new Set());
37 |
38 | const columns = React.useMemo(() => {
39 | return cols.map((x, i) => ({ ...x, grow: hasResized.current.has(i) ? undefined : (5 + i) / 5 }));
40 | }, [cols]);
41 |
42 | return (
43 | {
49 | hasResized.current.add(colIndex);
50 | onColumnResize(col, newSizeWithGrow);
51 | }}
52 | rowMarkers="both"
53 | />
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/ten-million-cells.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import { BeautifulWrapper, Description, useMockDataGenerator, defaultProps } from "../../data-editor/stories/utils.js";
4 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
5 |
6 | export default {
7 | title: "Glide-Data-Grid/DataEditor Demos",
8 |
9 | decorators: [
10 | (Story: React.ComponentType) => (
11 |
12 | Data grid supports over 10 million cells. Go nuts with it.}>
15 |
16 |
17 |
18 | ),
19 | ],
20 | };
21 |
22 | export const TenMillionCells: React.VFC = () => {
23 | const { cols, getCellContent } = useMockDataGenerator(100);
24 |
25 | return (
26 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/theme-per-row.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 |
23 | Each row can provide theme overrides for rendering that row using the{" "}
24 | getRowThemeOverride callback.
25 |
26 | >
27 | }>
28 |
29 |
30 |
31 | ),
32 | ],
33 | };
34 |
35 | export const ThemePerRow: React.VFC = () => {
36 | const { cols, getCellContent, onColumnResize, setCellValue } = useMockDataGenerator(5);
37 |
38 | const realCols = React.useMemo(() => {
39 | const c = [...cols];
40 | c[3] = {
41 | ...c[3],
42 | themeOverride: {
43 | bgCell: "#d6fafd",
44 | },
45 | };
46 | return c;
47 | }, [cols]);
48 |
49 | return (
50 | undefined}
60 | getRowThemeOverride={i =>
61 | i % 2 === 0
62 | ? undefined
63 | : {
64 | bgCell: "#e0f0ff88",
65 | // borderColor: "#3f90e0",
66 | }
67 | }
68 | onCellEdited={setCellValue}
69 | onColumnResize={onColumnResize}
70 | rows={10}
71 | />
72 | );
73 | };
74 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/uneven-rows.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | PropName,
7 | useMockDataGenerator,
8 | defaultProps,
9 | } from "../../data-editor/stories/utils.js";
10 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
11 |
12 | export default {
13 | title: "Glide-Data-Grid/DataEditor Demos",
14 |
15 | decorators: [
16 | (Story: React.ComponentType) => (
17 |
18 |
22 | Rows can be made uneven by passing a callback to the rowHeight prop
23 |
24 | }>
25 |
26 |
27 |
28 | ),
29 | ],
30 | };
31 |
32 | export const UnevenRows: React.VFC = () => {
33 | const { cols, getCellContent } = useMockDataGenerator(6);
34 |
35 | return (
36 | (r % 3 === 0 ? 30 : r % 2 ? 50 : 60)}
39 | getCellContent={getCellContent}
40 | columns={cols}
41 | rows={1000}
42 | />
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/packages/core/src/docs/examples/validate-data.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
3 | import {
4 | BeautifulWrapper,
5 | Description,
6 | MoreInfo,
7 | PropName,
8 | useMockDataGenerator,
9 | defaultProps,
10 | } from "../../data-editor/stories/utils.js";
11 | import { GridCellKind } from "../../internal/data-grid/data-grid-types.js";
12 | import { SimpleThemeWrapper } from "../../stories/story-utils.js";
13 |
14 | export default {
15 | title: "Glide-Data-Grid/DataEditor Demos",
16 |
17 | decorators: [
18 | (Story: React.ComponentType) => (
19 |
20 |
24 |
25 | Data can be validated using the validateCell callback
26 |
27 | This example only allows the word "Valid" inside text cells.
28 | >
29 | }>
30 |
31 |
32 |
33 | ),
34 | ],
35 | };
36 |
37 | export const ValidateData: React.VFC = () => {
38 | const { cols, getCellContent, setCellValue } = useMockDataGenerator(60, false);
39 |
40 | return (
41 | {
50 | if (newValue.kind !== GridCellKind.Text) return true;
51 | if (newValue.data === "Valid") return true;
52 | if (newValue.data.toLowerCase() === "valid") {
53 | return {
54 | ...newValue,
55 | data: "Valid",
56 | selectionRange: [0, 3],
57 | };
58 | }
59 | return false;
60 | }}
61 | />
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/packages/core/src/docs/template.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { type GridCell, GridCellKind, type Item } from "../internal/data-grid/data-grid-types.js";
4 | import { DataEditorAll as DataEditor } from "../data-editor-all.js";
5 | import { SimpleThemeWrapper } from "../stories/story-utils.js";
6 | import { DocWrapper, Highlight, Marked, Wrapper } from "./doc-wrapper.js";
7 |
8 | export default {
9 | title: "Glide-Data-Grid/Docs",
10 | decorators: [
11 | (Story: React.ComponentType) => (
12 |
13 |
14 |
15 | ),
16 | ],
17 | };
18 |
19 | export const Template: React.VFC = () => {
20 | const basicGetCellContent = React.useCallback((cell: Item): GridCell => {
21 | return {
22 | kind: GridCellKind.Text,
23 | allowOverlay: false,
24 | displayData: cell.toString(),
25 | data: cell.toString(),
26 | };
27 | }, []);
28 |
29 | const cols = React.useMemo(() => {
30 | return [
31 | {
32 | title: "First",
33 | width: 150,
34 | },
35 | {
36 | title: "Second",
37 | width: 150,
38 | },
39 | ];
40 | }, []);
41 |
42 | return (
43 |
44 |
45 | {`
46 | # Section Title
47 |
48 | > A nice little callout block
49 |
50 | Tell me what you're going to tell me.`}
51 |
52 |
53 | {`
54 | // Put the example code here
55 | const columns: GridColumn[] = [
56 | { title: "First", width: 150 },
57 | { title: "Second", width: 150 }
58 | ];
59 |
60 |
61 | `}
62 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 | (Template as any).storyName = "00. Template";
70 |
--------------------------------------------------------------------------------
/packages/core/src/internal/click-outside-container/click-outside-container.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | interface Props extends React.HTMLAttributes {
3 | onClickOutside: () => void;
4 | isOutsideClick?: (event: MouseEvent | TouchEvent) => boolean;
5 | // If provided, it will use the provided element as the event target
6 | // instead of document.
7 | customEventTarget?: HTMLElement | Window | Document;
8 | }
9 |
10 | export default class ClickOutsideContainer extends React.PureComponent {
11 | private wrapperRef = React.createRef();
12 |
13 | public componentDidMount() {
14 | const eventTarget = this.props.customEventTarget ?? document;
15 | eventTarget.addEventListener("touchend", this.clickOutside, true);
16 | eventTarget.addEventListener("mousedown", this.clickOutside, true);
17 | eventTarget.addEventListener("contextmenu", this.clickOutside, true);
18 | }
19 |
20 | public componentWillUnmount() {
21 | const eventTarget = this.props.customEventTarget ?? document;
22 | eventTarget.removeEventListener("touchend", this.clickOutside, true);
23 | eventTarget.removeEventListener("mousedown", this.clickOutside, true);
24 | eventTarget.removeEventListener("contextmenu", this.clickOutside, true);
25 | }
26 |
27 | private clickOutside = (event: Event) => {
28 | if (this.props.isOutsideClick && !this.props.isOutsideClick(event as MouseEvent | TouchEvent)) {
29 | return;
30 | }
31 | if (this.wrapperRef.current !== null && !this.wrapperRef.current.contains(event.target as Node | null)) {
32 | let node = event.target as Element | null;
33 | while (node !== null) {
34 | if (node.classList.contains("click-outside-ignore")) {
35 | return;
36 | }
37 |
38 | node = node.parentElement;
39 | }
40 | this.props.onClickOutside();
41 | }
42 | };
43 |
44 | public render(): React.ReactNode {
45 | const { onClickOutside, isOutsideClick, customEventTarget, ...rest } = this.props;
46 | return (
47 |
48 | {this.props.children}
49 |
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-editor-container/data-grid-container.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 | import * as React from "react";
3 |
4 | interface WrapperProps {
5 | inWidth: number | string;
6 | inHeight: number | string;
7 | }
8 |
9 | function toCss(x: number | string) {
10 | if (typeof x === "string") return x;
11 | return `${x}px`;
12 | }
13 |
14 | const Wrapper = styled.div<{ innerWidth: string; innerHeight: string }>`
15 | position: relative;
16 |
17 | min-width: 10px;
18 | min-height: 10px;
19 | max-width: 100%;
20 | max-height: 100%;
21 |
22 | width: ${p => p.innerWidth};
23 | height: ${p => p.innerHeight};
24 |
25 | overflow: hidden;
26 | overflow: clip;
27 |
28 | direction: ltr;
29 |
30 | > :first-child {
31 | position: absolute;
32 | left: 0;
33 | top: 0;
34 | width: 100%;
35 | height: 100%;
36 | }
37 | `;
38 |
39 | interface Props extends WrapperProps, React.HTMLAttributes {}
40 |
41 | export const DataEditorContainer: React.FunctionComponent> = p => {
42 | const { inWidth, inHeight, children, ...rest } = p;
43 | return (
44 |
45 | {children}
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | interface Props {
4 | targetX: number;
5 | targetY: number;
6 | targetWidth: number;
7 | targetHeight: number;
8 | }
9 | export const DataGridOverlayEditorStyle = styled.div`
10 | position: absolute;
11 |
12 | display: flex;
13 | flex-direction: column;
14 | overflow: hidden;
15 | box-sizing: border-box;
16 |
17 | --overlay-top: ${p => p.targetY}px;
18 |
19 | left: ${p => p.targetX}px;
20 | top: ${p => p.targetY}px;
21 | min-width: ${p => p.targetWidth}px;
22 | min-height: ${p => p.targetHeight}px;
23 | width: max-content;
24 | max-width: 400px;
25 | max-height: calc(100vh - ${p => p.targetY + 10}px);
26 |
27 | font-family: var(--gdg-font-family);
28 | font-size: var(--gdg-editor-font-size);
29 |
30 | @keyframes glide_fade_in {
31 | from {
32 | opacity: 0%;
33 | }
34 |
35 | to {
36 | opacity: 100%;
37 | }
38 | }
39 |
40 | &.gdg-style {
41 | border-radius: 2px;
42 | background-color: var(--gdg-bg-cell);
43 |
44 | box-shadow:
45 | 0 0 0 1px var(--gdg-accent-color),
46 | 0px 0px 1px rgba(62, 65, 86, 0.4),
47 | 0px 6px 12px rgba(62, 65, 86, 0.15);
48 |
49 | animation: glide_fade_in 60ms 1;
50 | }
51 |
52 | &.gdg-pad {
53 | padding: ${p => Math.max(0, (p.targetHeight - 28) / 2)}px 8.5px 3px;
54 | }
55 |
56 | .gdg-clip-region {
57 | display: flex;
58 | flex-direction: column;
59 | overflow-y: auto;
60 | overflow-x: hidden;
61 | border-radius: 2px;
62 | flex-grow: 1;
63 |
64 | .gdg-growing-entry {
65 | height: 100%;
66 | }
67 |
68 | & input.gdg-input {
69 | width: 100%;
70 | border: none;
71 | border-width: 0;
72 | outline: none;
73 | }
74 |
75 | & textarea.gdg-input {
76 | border: none;
77 | border-width: 0;
78 | outline: none;
79 | }
80 | }
81 |
82 | text-align: start;
83 | `;
84 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/bubbles-overlay-editor-style.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | const BUBBLE_HEIGHT = 20;
4 |
5 | export const BubblesOverlayEditorStyle = styled.div`
6 | display: flex;
7 | flex-wrap: wrap;
8 | margin-top: auto;
9 | margin-bottom: auto;
10 |
11 | .boe-bubble {
12 | display: flex;
13 | justify-content: center;
14 | align-items: center;
15 |
16 | border-radius: var(--gdg-rounding-radius, ${BUBBLE_HEIGHT / 2}px);
17 |
18 | padding: 0 8px;
19 | height: ${BUBBLE_HEIGHT}px;
20 | background-color: var(--gdg-bg-bubble);
21 | color: var(--gdg-text-dark);
22 | margin: 2px;
23 | }
24 |
25 | textarea {
26 | position: absolute;
27 | top: 0px;
28 | left: 0px;
29 | width: 0px;
30 | height: 0px;
31 |
32 | opacity: 0;
33 | }
34 | `;
35 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/bubbles-overlay-editor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { BubblesOverlayEditorStyle } from "./bubbles-overlay-editor-style.js";
3 |
4 | interface Props {
5 | readonly bubbles: readonly string[];
6 | }
7 |
8 | const BubblesOverlayEditor: React.FunctionComponent = p => {
9 | const { bubbles } = p;
10 | return (
11 |
12 | {bubbles.map((b, i) => (
13 |
14 | {b}
15 |
16 | ))}
17 |
18 |
19 | );
20 | };
21 | export default BubblesOverlayEditor;
22 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/drilldown-overlay-editor.tsx:
--------------------------------------------------------------------------------
1 | import type { DrilldownCellData } from "../../data-grid/data-grid-types.js";
2 | import * as React from "react";
3 | import { styled } from "@linaria/react";
4 |
5 | const DrilldownOverlayEditorStyle = styled.div`
6 | display: flex;
7 | flex-wrap: wrap;
8 |
9 | .doe-bubble {
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 |
14 | padding: 0 8px;
15 | height: 24px;
16 |
17 | background-color: var(--gdg-bg-cell);
18 | color: var(--gdg-text-dark);
19 | margin: 2px;
20 |
21 | border-radius: var(--gdg-rounding-radius, 6px);
22 |
23 | box-shadow:
24 | 0 0 1px rgba(62, 65, 86, 0.4),
25 | 0 1px 3px rgba(62, 65, 86, 0.4);
26 |
27 | img {
28 | height: 16px;
29 | object-fit: contain;
30 |
31 | margin-right: 4px;
32 | }
33 | }
34 |
35 | textarea {
36 | position: absolute;
37 | top: 0px;
38 | left: 0px;
39 | width: 0px;
40 | height: 0px;
41 |
42 | opacity: 0;
43 | }
44 | `;
45 |
46 | interface Props {
47 | readonly drilldowns: readonly DrilldownCellData[];
48 | }
49 |
50 | const DrilldownOverlayEditor: React.FunctionComponent = p => {
51 | const { drilldowns } = p;
52 | return (
53 |
54 | {drilldowns.map((d, i) => (
55 |
56 | {d.img !== undefined &&
}
57 |
{d.text}
58 |
59 | ))}
60 |
61 | );
62 | };
63 | export default DrilldownOverlayEditor;
64 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/image-overlay-editor-style.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | export const ImageOverlayEditorStyle = styled.div`
4 | display: flex;
5 |
6 | height: 100%;
7 |
8 | .gdg-centering-container {
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 |
13 | height: 100%;
14 |
15 | img,
16 | canvas {
17 | max-height: calc(100vh - var(--overlay-top) - 20px);
18 | object-fit: contain;
19 | user-select: none;
20 | }
21 |
22 | canvas {
23 | max-width: 380px;
24 | }
25 | }
26 |
27 | .gdg-edit-icon {
28 | position: absolute;
29 | top: 12px;
30 | right: 0;
31 | width: 48px;
32 | height: 48px;
33 | color: var(--gdg-accent-color);
34 |
35 | cursor: pointer;
36 |
37 | display: flex;
38 | justify-content: center;
39 | align-items: center;
40 |
41 | > * {
42 | width: 24px;
43 | height: 24px;
44 | }
45 | }
46 |
47 | textarea {
48 | position: absolute;
49 | top: 0px;
50 | left: 0px;
51 | width: 0px;
52 | height: 0px;
53 |
54 | opacity: 0;
55 | }
56 | `;
57 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/image-overlay-editor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { ImageOverlayEditorStyle } from "./image-overlay-editor-style.js";
3 | import { Carousel } from "react-responsive-carousel";
4 | import { EditPencil } from "../../../common/utils.js";
5 |
6 | /** @category Types */
7 | export interface OverlayImageEditorProps {
8 | readonly urls: readonly string[];
9 | readonly canWrite: boolean;
10 | readonly onCancel: () => void;
11 | readonly onChange: (newImage: string) => void;
12 | readonly onEditClick?: () => void;
13 | readonly renderImage?: (url: string) => React.ReactNode;
14 | }
15 |
16 | /** @category Renderers */
17 | export const ImageOverlayEditor: React.FunctionComponent = p => {
18 | const { urls, canWrite, onEditClick, renderImage } = p;
19 |
20 | const filtered = urls.filter(u => u !== "");
21 |
22 | if (filtered.length === 0) {
23 | return null;
24 | }
25 |
26 | const allowMove = filtered.length > 1;
27 | return (
28 |
29 |
35 | {filtered.map(url => {
36 | const innerContent = renderImage?.(url) ?? ;
37 | return (
38 |
39 | {innerContent}
40 |
41 | );
42 | })}
43 |
44 | {canWrite && onEditClick && (
45 |
46 |
47 |
48 | )}
49 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/markdown-overlay-editor-style.tsx:
--------------------------------------------------------------------------------
1 | import { GrowingEntryStyle } from "../../growing-entry/growing-entry-style.js";
2 | import { styled } from "@linaria/react";
3 |
4 | interface Props {
5 | targetWidth: number;
6 | }
7 |
8 | export const MarkdownOverlayEditorStyle = styled.div`
9 | min-width: ${p => p.targetWidth}px;
10 | width: 100%;
11 | display: flex;
12 | align-items: flex-start;
13 | justify-content: space-between;
14 | position: relative;
15 | color: var(--gdg-text-dark);
16 |
17 | ${GrowingEntryStyle} {
18 | flex-shrink: 1;
19 | min-width: 0;
20 | }
21 |
22 | .gdg-spacer {
23 | flex: 1;
24 | }
25 |
26 | .gdg-edit-icon {
27 | position: relative;
28 | cursor: pointer;
29 |
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 |
34 | color: var(--gdg-accent-color);
35 |
36 | padding: 0;
37 |
38 | height: 24px;
39 | width: 24px;
40 | flex-shrink: 0;
41 |
42 | transition: all "0.125s ease";
43 |
44 | border-radius: 6px;
45 |
46 | > * {
47 | width: 16px;
48 | height: 16px;
49 | }
50 | }
51 |
52 | .gdg-edit-hover {
53 | :hover {
54 | background-color: var(--gdg-accent-light);
55 | transition: background-color 150ms;
56 | }
57 | }
58 |
59 | .gdg-checkmark-hover {
60 | :hover {
61 | color: #ffffff;
62 | background-color: var(--gdg-accent-color);
63 | }
64 | }
65 |
66 | .gdg-md-edit-textarea {
67 | position: relative;
68 | top: 0px;
69 | left: 0px;
70 | width: 0px;
71 | height: 0px;
72 | margin-top: 25px;
73 | opacity: 0;
74 | padding: 0;
75 | }
76 |
77 | .gdg-ml-6 {
78 | margin-left: 6px;
79 | }
80 | `;
81 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/number-overlay-editor-style.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | export const NumberOverlayEditorStyle = styled.div`
4 | display: flex;
5 | margin: 6px 0 3px;
6 | color: var(--gdg-text-dark);
7 |
8 | > input {
9 | font-size: var(--gdg-editor-font-size);
10 | padding: 0;
11 | font-family: var(--gdg-font-family);
12 | color: var(--gdg-text-dark);
13 | background-color: var(--gdg-bg-cell);
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/uri-overlay-editor-style.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | export const UriOverlayEditorStyle = styled.div`
4 | display: flex;
5 |
6 | flex-grow: 1;
7 |
8 | align-items: center;
9 |
10 | min-height: 21px;
11 |
12 | .gdg-link-area {
13 | flex-grow: 1;
14 | flex-shrink: 1;
15 |
16 | cursor: pointer;
17 |
18 | margin-right: 8px;
19 | overflow: hidden;
20 | text-overflow: ellipsis;
21 | white-space: nowrap;
22 |
23 | color: var(--gdg-link-color);
24 | text-decoration: underline !important;
25 | }
26 |
27 | .gdg-edit-icon {
28 | flex-shrink: 0;
29 | width: 32px;
30 | color: var(--gdg-accent-color);
31 |
32 | cursor: pointer;
33 |
34 | display: flex;
35 | justify-content: center;
36 | align-items: center;
37 |
38 | > * {
39 | width: 24px;
40 | height: 24px;
41 | }
42 | }
43 |
44 | textarea {
45 | position: absolute;
46 | top: 0px;
47 | left: 0px;
48 | width: 0px;
49 | height: 0px;
50 |
51 | opacity: 0;
52 | }
53 | `;
54 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/private/uri-overlay-editor.tsx:
--------------------------------------------------------------------------------
1 | import { EditPencil } from "../../../common/utils.js";
2 | import * as React from "react";
3 | import { GrowingEntry } from "../../growing-entry/growing-entry.js";
4 | import { UriOverlayEditorStyle } from "./uri-overlay-editor-style.js";
5 | import type { SelectionRange } from "../../data-grid/data-grid-types.js";
6 |
7 | interface Props {
8 | readonly uri: string;
9 | readonly onChange: (ev: React.ChangeEvent) => void;
10 | readonly forceEditMode: boolean;
11 | readonly readonly: boolean;
12 | readonly preview: string;
13 | readonly validatedSelection?: SelectionRange;
14 | }
15 |
16 | const UriOverlayEditor: React.FunctionComponent = p => {
17 | const { uri, onChange, forceEditMode, readonly, validatedSelection, preview } = p;
18 |
19 | const [editMode, setEditMode] = React.useState(!readonly && (uri === "" || forceEditMode));
20 |
21 | const onEditClick = React.useCallback(() => {
22 | setEditMode(true);
23 | }, []);
24 |
25 | if (editMode) {
26 | return (
27 |
34 | );
35 | }
36 |
37 | return (
38 |
39 |
40 | {preview}
41 |
42 | {!readonly && (
43 |
44 |
45 |
46 | )}
47 |
48 |
49 | );
50 | };
51 |
52 | export default UriOverlayEditor;
53 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-overlay-editor/use-stay-on-screen.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | function useRefState(): [HTMLElement | undefined, React.RefCallback] {
4 | const [refState, setRefState] = React.useState();
5 | return [refState ?? undefined, setRefState];
6 | }
7 |
8 | interface StayOnScreen {
9 | ref: React.RefCallback;
10 | style: React.CSSProperties;
11 | }
12 |
13 | export function useStayOnScreen(): StayOnScreen {
14 | const [ref, setRef] = useRefState();
15 | const [xOffset, setXOffset] = React.useState(0);
16 | const [isIntersecting, setIsIntersecting] = React.useState(true);
17 |
18 | React.useLayoutEffect(() => {
19 | if (ref === undefined) return;
20 | if (!("IntersectionObserver" in window)) return;
21 |
22 | const observer = new IntersectionObserver(
23 | ents => {
24 | if (ents.length === 0) return;
25 | setIsIntersecting(ents[0].isIntersecting);
26 | },
27 | { threshold: 1 }
28 | );
29 | observer.observe(ref);
30 |
31 | return () => observer.disconnect();
32 | }, [ref]);
33 |
34 | React.useEffect(() => {
35 | if (isIntersecting || ref === undefined) return;
36 |
37 | let rafHandle: number | undefined;
38 | const fn = () => {
39 | const { right: refRight } = ref.getBoundingClientRect();
40 |
41 | setXOffset(cv => Math.min(cv + window.innerWidth - refRight - 10, 0));
42 | rafHandle = requestAnimationFrame(fn);
43 | };
44 |
45 | rafHandle = requestAnimationFrame(fn);
46 | return () => {
47 | if (rafHandle !== undefined) {
48 | cancelAnimationFrame(rafHandle);
49 | }
50 | };
51 | }, [ref, isIntersecting]);
52 |
53 | const style = React.useMemo(() => {
54 | return { transform: `translateX(${xOffset}px)` };
55 | }, [xOffset]);
56 |
57 | return {
58 | ref: setRef,
59 | style,
60 | };
61 | }
62 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid-search/data-grid-search-style.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | export const SearchWrapper = styled.div`
4 | position: absolute;
5 | top: 4px;
6 | right: 20px;
7 |
8 | background-color: var(--gdg-bg-cell);
9 | color: var(--gdg-text-dark);
10 |
11 | padding: 8px;
12 | border: 1px solid var(--gdg-border-color);
13 | border-radius: 6px;
14 |
15 | font-size: var(--gdg-editor-font-size);
16 |
17 | &.out {
18 | animation: gdg-search-fadeout 0.15s forwards;
19 | }
20 | animation: gdg-search-fadein 0.15s forwards;
21 |
22 | .gdg-search-bar-inner {
23 | display: flex;
24 | }
25 |
26 | .gdg-search-status {
27 | padding-top: 4px;
28 | font-size: 11px;
29 | }
30 |
31 | .gdg-search-progress {
32 | position: absolute;
33 | height: 4px;
34 | left: 0;
35 | bottom: 0;
36 |
37 | background-color: var(--gdg-text-light);
38 | }
39 |
40 | input {
41 | width: 220px;
42 | color: var(--gdg-textDark);
43 | background-color: var(--gdg-bg-cell);
44 | border: none;
45 | border-width: 0;
46 | outline: none;
47 | }
48 |
49 | button {
50 | width: 24px;
51 | height: 24px;
52 | padding: 0;
53 |
54 | border: none;
55 | outline: none;
56 | background: none;
57 |
58 | display: flex;
59 | justify-content: center;
60 | align-items: center;
61 | cursor: pointer;
62 | color: var(--gdg-text-medium);
63 |
64 | :hover {
65 | color: var(--gdg-text-dark);
66 | }
67 |
68 | .button-icon {
69 | width: 16px;
70 | height: 16px;
71 | }
72 |
73 | :disabled {
74 | opacity: 0.4;
75 | pointer-events: none;
76 | }
77 | }
78 |
79 | @keyframes gdg-search-fadeout {
80 | from {
81 | transform: translateX(0);
82 | }
83 | to {
84 | transform: translateX(400px);
85 | }
86 | }
87 |
88 | @keyframes gdg-search-fadein {
89 | from {
90 | transform: translateX(400px);
91 | }
92 | to {
93 | transform: translateX(0);
94 | }
95 | }
96 | `;
97 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid/cell-set.ts:
--------------------------------------------------------------------------------
1 | import { packColRowToNumber, unpackNumberToColRow, unpackRow } from "../../common/render-state-provider.js";
2 | import type { Item, Rectangle } from "./data-grid-types.js";
3 |
4 | export class CellSet {
5 | private readonly cells: Set;
6 |
7 | constructor(items: Item[] = []) {
8 | this.cells = new Set(items.map(x => packColRowToNumber(x[0], x[1])));
9 | }
10 |
11 | public add(cell: Item): void {
12 | this.cells.add(packColRowToNumber(cell[0], cell[1]));
13 | }
14 |
15 | public has(cell: Item | undefined): boolean {
16 | if (cell === undefined) return false;
17 | return this.cells.has(packColRowToNumber(cell[0], cell[1]));
18 | }
19 |
20 | public remove(cell: Item): void {
21 | this.cells.delete(packColRowToNumber(cell[0], cell[1]));
22 | }
23 |
24 | public clear(): void {
25 | this.cells.clear();
26 | }
27 |
28 | public get size(): number {
29 | return this.cells.size;
30 | }
31 |
32 | public hasHeader(): boolean {
33 | for (const cellNumber of this.cells) {
34 | const row = unpackRow(cellNumber);
35 | if (row < 0) return true;
36 | }
37 | return false;
38 | }
39 |
40 | public hasItemInRectangle(rect: Rectangle): boolean {
41 | for (let row = rect.y; row < rect.y + rect.height; row++) {
42 | for (let col = rect.x; col < rect.x + rect.width; col++) {
43 | if (this.cells.has(packColRowToNumber(col, row))) {
44 | return true;
45 | }
46 | }
47 | }
48 | return false;
49 | }
50 |
51 | public hasItemInRegion(rect: readonly (Rectangle & { when?: boolean })[]): boolean {
52 | for (const r of rect) {
53 | if (this.hasItemInRectangle(r)) {
54 | return true;
55 | }
56 | }
57 | return false;
58 | }
59 |
60 | public *values(): IterableIterator- {
61 | for (const cellNumber of this.cells) {
62 | yield unpackNumberToColRow(cellNumber);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid/image-window-loader-interface.ts:
--------------------------------------------------------------------------------
1 | import type { CellSet } from "./cell-set.js";
2 | import type { Rectangle } from "./data-grid-types.js";
3 |
4 | /** @category Types */
5 | export interface ImageWindowLoader {
6 | setWindow(newWindow: Rectangle, freezeCols: number, freezeRows: number[]): void;
7 | loadOrGetImage(url: string, col: number, row: number): HTMLImageElement | ImageBitmap | undefined;
8 | setCallback(imageLoaded: (locations: CellSet) => void): void;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid/render/draw-edit-hover-indicator.ts:
--------------------------------------------------------------------------------
1 | import type { FullTheme } from "../../../common/styles.js";
2 | import type { Rectangle, HoverEffectTheme } from "../../../index.js";
3 | import { roundedRect, measureTextCached } from "./data-grid-lib.js";
4 | import { withAlpha } from "../color-parser.js";
5 |
6 | export function drawEditHoverIndicator(
7 | ctx: CanvasRenderingContext2D,
8 | theme: FullTheme,
9 | effectTheme: HoverEffectTheme | undefined,
10 | displayData: string,
11 | rect: Rectangle,
12 | hoverAmount: number,
13 | overrideCursor: ((cursor: React.CSSProperties["cursor"] | undefined) => void) | undefined
14 | ) {
15 | ctx.textBaseline = "alphabetic";
16 |
17 | const effectRect = getHoverEffectRect(ctx, rect, displayData, theme, effectTheme?.fullSize ?? false);
18 |
19 | ctx.beginPath();
20 | roundedRect(ctx, effectRect.x, effectRect.y, effectRect.width, effectRect.height, theme.roundingRadius ?? 4);
21 | ctx.globalAlpha = hoverAmount;
22 | ctx.fillStyle = effectTheme?.bgColor ?? withAlpha(theme.textDark, 0.1);
23 | ctx.fill();
24 |
25 | // restore
26 | ctx.globalAlpha = 1;
27 | ctx.fillStyle = theme.textDark;
28 | ctx.textBaseline = "middle";
29 |
30 | overrideCursor?.("text");
31 | }
32 |
33 | function getHoverEffectRect(
34 | ctx: CanvasRenderingContext2D,
35 | cellRect: Rectangle,
36 | displayData: string,
37 | theme: FullTheme,
38 | fullSize: boolean
39 | ): Rectangle {
40 | const padX = theme.cellHorizontalPadding;
41 | const padY = theme.cellVerticalPadding;
42 |
43 | if (fullSize) {
44 | return {
45 | x: cellRect.x + padX / 2,
46 | y: cellRect.y + padY / 2 + 1,
47 | width: cellRect.width - padX,
48 | height: cellRect.height - padY - 1,
49 | };
50 | }
51 |
52 | const m = measureTextCached(displayData, ctx, theme.baseFontFull, "alphabetic");
53 | const maxH = cellRect.height - padY;
54 | const h = Math.min(maxH, m.actualBoundingBoxAscent * 2.5);
55 | return {
56 | x: cellRect.x + padX / 2,
57 | y: cellRect.y + (cellRect.height - h) / 2 + 1,
58 | width: m.width + padX * 3,
59 | height: h - 1,
60 | };
61 | }
62 |
--------------------------------------------------------------------------------
/packages/core/src/internal/data-grid/use-animation-queue.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { type Item } from "./data-grid-types.js";
3 | import { CellSet } from "./cell-set.js";
4 | import { packColRowToNumber, unpackNumberToColRow } from "../../common/render-state-provider.js";
5 |
6 | export type EnqueueCallback = (item: Item) => void;
7 |
8 | export function useAnimationQueue(draw: (items: CellSet) => void): EnqueueCallback {
9 | const queue = React.useRef
([]);
10 | const seq = React.useRef(0);
11 | const drawRef = React.useRef(draw);
12 | drawRef.current = draw;
13 |
14 | const loop = React.useCallback(() => {
15 | const requeue = () => window.requestAnimationFrame(fn);
16 |
17 | const fn = () => {
18 | const toDraw = queue.current.map(unpackNumberToColRow);
19 |
20 | queue.current = [];
21 | drawRef.current(new CellSet(toDraw));
22 | if (queue.current.length > 0) {
23 | seq.current++;
24 | } else {
25 | seq.current = 0;
26 | }
27 | };
28 |
29 | window.requestAnimationFrame(seq.current > 600 ? requeue : fn);
30 | }, []);
31 |
32 | return React.useCallback(
33 | (item: Item) => {
34 | if (queue.current.length === 0) loop();
35 | const packed = packColRowToNumber(item[0], item[1]);
36 | if (queue.current.includes(packed)) return;
37 | queue.current.push(packed);
38 | },
39 | [loop]
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/internal/growing-entry/growing-entry-style.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | export const InputBox = styled.textarea`
4 | position: absolute;
5 | left: 0;
6 | right: 0;
7 | top: 0;
8 | bottom: 0;
9 | width: 100%;
10 | height: 100%;
11 |
12 | border-radius: 0px;
13 |
14 | resize: none;
15 | white-space: pre-wrap;
16 | min-width: 100%;
17 | overflow: hidden;
18 | border: 0;
19 | background-color: transparent;
20 |
21 | ::placeholder {
22 | color: var(--gdg-text-light);
23 | }
24 |
25 | font-size: var(--gdg-editor-font-size);
26 | line-height: 16px;
27 | font-family: var(--gdg-font-family);
28 | -webkit-text-fill-color: var(--gdg-text-dark);
29 | color: var(--gdg-text-dark);
30 | padding: 0;
31 | margin: 0;
32 |
33 | .gdg-invalid & {
34 | text-decoration: underline;
35 | text-decoration-color: #d60606;
36 | }
37 | `;
38 |
39 | export const ShadowBox = styled.div`
40 | visibility: hidden;
41 | white-space: pre-wrap;
42 | word-wrap: break-word;
43 |
44 | width: max-content;
45 | max-width: 100%;
46 |
47 | min-width: 100%;
48 |
49 | font-size: var(--gdg-editor-font-size);
50 | line-height: 16px;
51 | font-family: var(--gdg-font-family);
52 | color: var(--gdg-text-dark);
53 | padding: 0;
54 | margin: 0;
55 |
56 | padding-bottom: 2px;
57 | `;
58 |
59 | export const GrowingEntryStyle = styled.div`
60 | position: relative;
61 | margin-top: 6px;
62 | `;
63 |
--------------------------------------------------------------------------------
/packages/core/src/internal/markdown-div/markdown-div.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { marked } from "marked";
3 |
4 | import { MarkdownContainer } from "./private/markdown-container.js";
5 |
6 | /** @category Renderers */
7 | export interface MarkdownDivProps {
8 | contents: string;
9 | createNode?: (content: string) => DocumentFragment;
10 | }
11 |
12 | /** @category Renderers */
13 | export default class MarkdownDiv extends React.PureComponent {
14 | private targetElement: HTMLElement | null = null;
15 |
16 | private renderMarkdownIntoDiv() {
17 | const { targetElement, props } = this;
18 | if (targetElement === null) return;
19 |
20 | const { contents, createNode } = props;
21 |
22 | const innerHTML: string = (marked as any)(contents);
23 |
24 | const childRange = document.createRange();
25 | childRange.selectNodeContents(targetElement);
26 | childRange.deleteContents();
27 |
28 | let newChild: DocumentFragment | undefined = createNode?.(innerHTML);
29 | if (newChild === undefined) {
30 | const childDoc = document.createElement("template");
31 | childDoc.innerHTML = innerHTML;
32 | newChild = childDoc.content;
33 | }
34 | targetElement.append(newChild);
35 |
36 | const tags = targetElement.getElementsByTagName("a");
37 | for (const tag of tags) {
38 | tag.target = "_blank";
39 | tag.rel = "noreferrer noopener";
40 | }
41 | }
42 |
43 | private containerRefHook = (element: HTMLElement | null) => {
44 | this.targetElement = element;
45 | this.renderMarkdownIntoDiv();
46 | };
47 |
48 | public render() {
49 | // Doing this in the ref hook works great when we first render, but never again.
50 | // This only works great after the first render, but not in the first render.
51 | // Putting the two together makes the full solution.
52 | this.renderMarkdownIntoDiv();
53 | return ;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/core/src/internal/markdown-div/private/markdown-container.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@linaria/react";
2 |
3 | export const MarkdownContainer = styled.div`
4 | word-break: break-word;
5 | -webkit-touch-callout: default;
6 | padding-top: 6px;
7 |
8 | > * {
9 | margin: 0;
10 | }
11 |
12 | & *:last-child {
13 | margin-bottom: 0;
14 | }
15 |
16 | & p img {
17 | width: 100%;
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/packages/core/src/stories/story-utils.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { styled } from "@linaria/react";
3 | import "react-responsive-carousel/lib/styles/carousel.min.css";
4 |
5 | interface Props {
6 | width: number;
7 | height: number;
8 | useMoreTopPadding?: boolean;
9 | figmaDoc?: string;
10 | context?: any;
11 | }
12 |
13 | const BuilderWrapper = styled.div>`
14 | display: flex;
15 | height: 100vh;
16 | width: 100vw;
17 | position: relative;
18 |
19 | & > .content {
20 | display: block;
21 |
22 | width: ${p => p.width}px;
23 | height: ${p => p.height}px;
24 | align-self: center;
25 |
26 | position: relative;
27 |
28 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
29 |
30 | user-select: none;
31 |
32 | box-sizing: border-box;
33 |
34 | *,
35 | *::before,
36 | *::after {
37 | box-sizing: inherit;
38 | }
39 | }
40 | `;
41 |
42 | const SimpleWrapper = styled.div`
43 | box-sizing: border-box;
44 |
45 | *,
46 | *::before,
47 | *::after {
48 | box-sizing: inherit;
49 | }
50 | `;
51 |
52 | export class BuilderThemeWrapper extends React.PureComponent> {
53 | public render(): React.ReactNode {
54 | const { context, children, ...rest } = this.props;
55 | return (
56 | <>
57 |
58 | {children}
59 |
60 |
61 | >
62 | );
63 | }
64 | }
65 |
66 | export const SimpleThemeWrapper: React.FC = p => {
67 | return (
68 |
69 | {p.children}
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/packages/core/test/click-outside-container.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, cleanup } from "@testing-library/react";
2 | import * as React from "react";
3 | import { userEvent } from "@testing-library/user-event";
4 | import ClickOutsideContainer from "../src/internal/click-outside-container/click-outside-container.js";
5 | import { vi, expect, describe, it, afterEach } from "vitest";
6 |
7 | // skip this for now because github actions is failing
8 | describe.skip("click-outside-container", () => {
9 | afterEach(() => {
10 | cleanup();
11 | });
12 |
13 | it("Triggers onClose when clicking outside", async () => {
14 | const onClickOutside = vi.fn();
15 |
16 | const result = render(
17 |
18 |
21 |
22 |
23 | I am inside
24 |
25 |
26 | );
27 |
28 | const insideElement = await result.findByText("I am inside");
29 | const outsideElement = await result.findByText("I am outside");
30 |
31 | expect(onClickOutside).not.toHaveBeenCalled();
32 | await userEvent.click(insideElement);
33 | expect(onClickOutside).not.toHaveBeenCalled();
34 | await userEvent.click(outsideElement);
35 | expect(onClickOutside).toHaveBeenCalledTimes(1);
36 | });
37 |
38 | it(`Does not trigger onClose when clicking outside but 'isOutsideClick' returns false`, async () => {
39 | const onClickOutside = vi.fn();
40 | const isOutsideClick = vi.fn();
41 |
42 | const result = render(
43 |
44 |
47 |
48 |
49 | );
50 |
51 | const outsideElement = await result.findByText("I am outside");
52 |
53 | isOutsideClick.mockReturnValueOnce(true);
54 |
55 | expect(onClickOutside).not.toHaveBeenCalled();
56 | await userEvent.click(outsideElement);
57 | expect(onClickOutside).toHaveBeenCalledTimes(1);
58 | isOutsideClick.mockReturnValueOnce(false);
59 | await userEvent.click(outsideElement);
60 | expect(onClickOutside).toHaveBeenCalledTimes(1);
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/packages/core/test/color-parser.test.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable sonarjs/no-duplicate-string */
2 | import {
3 | blend,
4 | interpolateColors,
5 | parseToRgba,
6 | withAlpha,
7 | getLuminance,
8 | } from "../src/internal/data-grid/color-parser.js";
9 | import { expect, describe, test } from "vitest";
10 |
11 | describe("interpolateColors", () => {
12 | test("Smoke test", () => {
13 | expect(interpolateColors("rgba(0, 0, 0, 1)", "rgba(255, 255, 255, 1)", 0.5)).toEqual("rgba(127, 127, 127, 1)");
14 | });
15 | });
16 |
17 | describe("parseToRgba", () => {
18 | test("Smoke test", () => {
19 | expect(parseToRgba("rgba(255, 255, 255, 1)")).toEqual([255, 255, 255, 1]);
20 | });
21 | });
22 |
23 | describe("withAlpha", () => {
24 | test("Smoke test", () => {
25 | expect(withAlpha("rgba(255, 255, 255, 1)", 0.3)).toEqual("rgba(255, 255, 255, 0.3)");
26 | });
27 | });
28 |
29 | describe("blend", () => {
30 | test("Smoke test", () => {
31 | expect(blend("rgba(255, 255, 255, 0.2)", "rgba(123, 123, 123, 1)")).toEqual("rgba(149.4, 149.4, 149.4, 1)");
32 | });
33 | });
34 |
35 | describe("getLuminance", () => {
36 | test("Smoke test", () => {
37 | expect(getLuminance("rgba(255, 255, 255, 1)")).toEqual(1);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/packages/core/test/use-animation-queue.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook, cleanup } from "@testing-library/react-hooks";
2 | import { useAnimationQueue } from "../src/internal/data-grid/use-animation-queue.js";
3 | import { vi, expect, describe, it, beforeEach, afterEach } from "vitest";
4 | import { CellSet } from "../src/internal/data-grid/cell-set.js";
5 |
6 | // const OG_RAF = window.requestAnimationFrame;
7 | // const OG_CAF = window.cancelAnimationFrame;
8 |
9 | describe("use-cell-sizer", () => {
10 | beforeEach(() => {
11 | // Mock rAF with setTimeout. Not perfect by any means, but at least it mocks the asynchronicity.
12 | vi.useFakeTimers();
13 |
14 | // window.requestAnimationFrame = f => {
15 | // const timeoutID = setTimeout(() => {
16 | // const timestamp = performance.now();
17 | // f(timestamp);
18 | // }, 0);
19 | // return timeoutID as unknown as number;
20 | // };
21 |
22 | // window.cancelAnimationFrame = (rafID: number) => {
23 | // clearTimeout(rafID);
24 | // };
25 | });
26 | afterEach(async () => {
27 | vi.useRealTimers();
28 |
29 | await cleanup();
30 |
31 | // window.requestAnimationFrame = OG_RAF;
32 | // window.cancelAnimationFrame = OG_CAF;
33 | });
34 |
35 | it("Draws when an item is enqueued", async () => {
36 | const draw = vi.fn();
37 | const { result } = renderHook(() => useAnimationQueue(draw));
38 |
39 | result.current([1, 2]);
40 | result.current([2, 3]);
41 | result.current([3, 4]);
42 |
43 | vi.runAllTimers();
44 |
45 | expect(draw).toHaveBeenCalledWith(
46 | new CellSet([
47 | [1, 2],
48 | [2, 3],
49 | [3, 4],
50 | ])
51 | );
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/packages/core/test/use-deep-memo.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook, cleanup } from "@testing-library/react-hooks";
2 | import { useDeepMemo } from "../src/common/utils.js";
3 | import { expect, describe, it, afterEach } from "vitest";
4 |
5 | describe("useDeepMemo", () => {
6 | afterEach(cleanup);
7 |
8 | it("returns the initial value", () => {
9 | const initialValue = { a: 1 };
10 | const { result } = renderHook(() => useDeepMemo(initialValue));
11 | expect(result.current).toEqual(initialValue);
12 | });
13 |
14 | it("updates the value on deep change", () => {
15 | const initialValue = { a: 1 };
16 | const changedValue = { a: 2 };
17 | const { result, rerender } = renderHook(({ value }) => useDeepMemo(value), {
18 | initialProps: { value: initialValue },
19 | });
20 |
21 | expect(result.current).toEqual(initialValue);
22 |
23 | // Change the value deeply
24 | rerender({ value: changedValue });
25 | expect(result.current).toEqual(changedValue);
26 | });
27 |
28 | it("does not update the value on shallow/no change", () => {
29 | const initialValue = { a: 1 };
30 | const sameValue = { a: 1 };
31 | const { result, rerender } = renderHook(({ value }) => useDeepMemo(value), {
32 | initialProps: { value: initialValue },
33 | });
34 |
35 | expect(result.current).toEqual(initialValue);
36 |
37 | // Re-render with the same value (shallow change)
38 | rerender({ value: sameValue });
39 | expect(result.current).toEqual(initialValue);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/packages/core/test/use-kinetic-scroll.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook, act } from "@testing-library/react-hooks";
2 | import useKineticScroll from "../src/internal/scrolling-data-grid/use-kinetic-scroll.js";
3 | import { vi, expect, describe, it, afterEach, beforeEach } from "vitest";
4 | import { fireEvent } from "@testing-library/react";
5 |
6 | describe("useKineticScroll", () => {
7 | let targetScroller: { current: HTMLDivElement };
8 | let callback: () => void;
9 |
10 | beforeEach(() => {
11 | targetScroller = { current: document.createElement("div") };
12 | callback = vi.fn();
13 |
14 | vi.spyOn(targetScroller.current, "addEventListener");
15 | vi.spyOn(targetScroller.current, "removeEventListener");
16 | vi.useFakeTimers();
17 | });
18 |
19 | afterEach(() => {
20 | vi.clearAllMocks();
21 | vi.runOnlyPendingTimers();
22 | vi.useRealTimers();
23 | });
24 |
25 | it("registers and unregisters event listeners based on isEnabled", () => {
26 | const { rerender } = renderHook(({ isEnabled }) => useKineticScroll(isEnabled, callback, targetScroller), {
27 | initialProps: { isEnabled: false },
28 | });
29 |
30 | expect(targetScroller.current.addEventListener).not.toHaveBeenCalled();
31 | expect(targetScroller.current.removeEventListener).not.toHaveBeenCalled();
32 |
33 | rerender({ isEnabled: true });
34 |
35 | expect(targetScroller.current.addEventListener).toHaveBeenCalledTimes(2);
36 | expect(targetScroller.current.removeEventListener).not.toHaveBeenCalled();
37 |
38 | rerender({ isEnabled: false });
39 |
40 | expect(targetScroller.current.removeEventListener).toHaveBeenCalledTimes(2);
41 | });
42 |
43 | it("handles scroll events and triggers callback", () => {
44 | renderHook(() => useKineticScroll(true, callback, targetScroller));
45 |
46 | act(() => {
47 | targetScroller.current.dispatchEvent(new Event("touchstart"));
48 | vi.advanceTimersByTime(1000);
49 | fireEvent.touchEnd(targetScroller.current, {
50 | touches: [],
51 | });
52 | vi.advanceTimersByTime(1000 / 120);
53 | });
54 |
55 | expect(callback).toHaveBeenCalled();
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/packages/core/test/use-rem-adjuster.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook, cleanup } from "@testing-library/react-hooks";
2 | import { useRemAdjuster } from "../src/data-editor/use-rem-adjuster.js";
3 | import { expect, describe, it, afterEach } from "vitest";
4 |
5 | describe("useDataEditorDimensions", () => {
6 | afterEach(async () => {
7 | await cleanup();
8 | });
9 |
10 | it("should return default dimensions when no props are passed", () => {
11 | const { result } = renderHook(() =>
12 | useRemAdjuster({
13 | remSize: 16,
14 | groupHeaderHeight: 30,
15 | headerHeight: 30,
16 | rowHeight: 30,
17 | scaleToRem: false,
18 | })
19 | );
20 | expect(result.current).toEqual({
21 | rowHeight: 30,
22 | headerHeight: 30,
23 | groupHeaderHeight: 30,
24 | theme: undefined,
25 | overscrollX: undefined,
26 | overscrollY: undefined,
27 | });
28 | });
29 |
30 | it("should scale dimensions to rem units when scaleToRem is true", () => {
31 | const { result } = renderHook(() =>
32 | useRemAdjuster({
33 | remSize: 20,
34 | groupHeaderHeight: 30,
35 | headerHeight: 30,
36 | rowHeight: 30,
37 | scaleToRem: true,
38 | })
39 | );
40 | expect(result.current).toEqual({
41 | rowHeight: 37.5,
42 | headerHeight: 38,
43 | groupHeaderHeight: 38,
44 | theme: {
45 | headerIconSize: 22.5,
46 | cellHorizontalPadding: 10,
47 | cellVerticalPadding: 3.75,
48 | },
49 | overscrollX: 0,
50 | overscrollY: 0,
51 | });
52 | });
53 |
54 | it("should preserve the sticky property of the columns", () => {
55 | const { result } = renderHook(() =>
56 | useRemAdjuster({
57 | scaleToRem: true,
58 | remSize: 20,
59 | theme: {
60 | bgCell: "red",
61 | },
62 | groupHeaderHeight: 30,
63 | headerHeight: 30,
64 | rowHeight: 30,
65 | })
66 | );
67 | expect(result.current.theme?.bgCell).toBe("red");
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx"],
4 | "exclude": [
5 | "./src/**/*.stories.tsx",
6 | "./src/stories/*.tsx",
7 | "./src/docs/*.tsx",
8 | "./test/**/*.test.ts",
9 | "./test/**/*.test.tsx",
10 | "./src/**/stories/"
11 | ],
12 | "compilerOptions": {
13 | "rootDir": "./src",
14 | "outDir": "./dist/cjs",
15 | "declarationDir": "./dist/dts",
16 | "module": "CommonJS",
17 | "verbatimModuleSyntax": false,
18 | "sourceMap": true,
19 | "types": ["node"]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx"],
4 | "exclude": [
5 | "./src/**/*.stories.tsx",
6 | "./src/stories/*.tsx",
7 | "./src/docs/*.tsx",
8 | "./test/**/*.test.ts",
9 | "./test/**/*.test.tsx",
10 | "./src/**/stories/"
11 | ],
12 | "compilerOptions": {
13 | "rootDir": "./src",
14 | "outDir": "./dist/esm",
15 | "declarationDir": "./dist/dts",
16 | "sourceMap": true,
17 | "declaration": true,
18 | "declarationMap": true,
19 | "types": ["node"]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx", "./test/**/*.ts", "./test/**/*.tsx"],
4 | "typedocOptions": {
5 | "entryPoints": ["./src/index.ts"],
6 | "out": "gen-docs",
7 | "categorizeByGroup": false,
8 | "name": "Glide Data Grid",
9 | "categoryOrder": ["DataEditor", "*"]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react";
2 | import { defineConfig, configDefaults } from "vitest/config";
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | include: ["test/**/*.test.tsx", "test/**/*.test.ts"],
8 | environment: "jsdom",
9 | setupFiles: "vitest.setup.ts",
10 | // threads: false,
11 | // singleThread: true,
12 | watch: false,
13 | clearMocks: true,
14 | maxConcurrency: 8,
15 | coverage: {
16 | provider: "v8",
17 | reporter: ["text", "lcov"],
18 | },
19 | fakeTimers: {
20 | toFake: [
21 | ...(configDefaults.fakeTimers.toFake ?? []),
22 | "performance",
23 | "requestAnimationFrame",
24 | "cancelAnimationFrame",
25 | ],
26 | },
27 | deps: {
28 | optimizer: {
29 | web: {
30 | include: ["vitest-canvas-mock"],
31 | },
32 | },
33 | },
34 | environmentOptions: {
35 | jsdom: {
36 | resources: "usable",
37 | },
38 | },
39 | },
40 | });
41 |
--------------------------------------------------------------------------------
/packages/core/vitest.setup.ts:
--------------------------------------------------------------------------------
1 | import "vitest-canvas-mock";
2 | import { vi } from "vitest";
3 |
4 | // this is needed to make the canvas mock work for some reason
5 | global.jest = vi;
6 |
7 | global.ResizeObserver = vi.fn().mockImplementation(() => ({
8 | observe: jest.fn(),
9 | unobserve: jest.fn(),
10 | disconnect: jest.fn(),
11 | }));
12 |
13 | Image.prototype.decode = () => new Promise(resolve => window.setTimeout(resolve, 10));
14 |
--------------------------------------------------------------------------------
/packages/source/.eslintignore:
--------------------------------------------------------------------------------
1 | jest.config.js
2 | node_modules/
3 | vitest.*.ts
--------------------------------------------------------------------------------
/packages/source/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:react/recommended",
5 | "plugin:@typescript-eslint/recommended",
6 | "plugin:import/recommended",
7 | "plugin:import/typescript",
8 | "plugin:react-hooks/recommended"
9 | ],
10 | "plugins": ["react", "@typescript-eslint", "import"],
11 | "parser": "@typescript-eslint/parser",
12 | "parserOptions": {
13 | "project": ["./tsconfig.json"]
14 | },
15 | "rules": {
16 | "no-console": "warn",
17 | "guard-for-in": "error",
18 | "no-empty": "error",
19 | "no-shadow": "off",
20 | "import/no-cycle": "off",
21 | "import/namespace": "off",
22 | "import/named": "off",
23 | "import/no-unresolved": "off", // FIXME: Figure out how to make this enabled
24 | "@typescript-eslint/no-unused-vars": "off",
25 | "@typescript-eslint/explicit-function-return-type": "off",
26 | "@typescript-eslint/no-empty-interface": "off",
27 | "@typescript-eslint/no-inferrable-types": "off",
28 | "@typescript-eslint/no-explicit-any": "off",
29 | "@typescript-eslint/ban-ts-ignore": "off",
30 | "@typescript-eslint/camelcase": "off",
31 | "@typescript-eslint/interface-name-prefix": "off",
32 | "@typescript-eslint/no-floating-promises": "error",
33 | "@typescript-eslint/no-shadow": "error",
34 | "@typescript-eslint/no-misused-promises": [
35 | "error",
36 | {
37 | "checksVoidReturn": false
38 | }
39 | ],
40 | "@typescript-eslint/ban-ts-comment": "off",
41 | "@typescript-eslint/explicit-module-boundary-types": "off",
42 | "@typescript-eslint/ban-types": "off"
43 | },
44 | "overrides": [
45 | {
46 | "files": ["*.ts"],
47 | "rules": {
48 | "@typescript-eslint/strict-boolean-expressions": "error"
49 | }
50 | }
51 | ],
52 | "settings": {
53 | "react": {
54 | "version": "detect"
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/source/.npmignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | .eslintrc
3 | tsconfig.json
4 | tsconfig.types.json
5 | jest.config.js
--------------------------------------------------------------------------------
/packages/source/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 typeguard, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/source/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Glide Data Grid Source
4 |
5 | This provides an easy to use data source for the Glide Data Grid which supports many convenient features such as sorting and menus out of the box.
6 |
7 | [](https://github.com/glideapps/glide-data-grid/releases)
8 | [](https://reactjs.org)
9 | [](https://bundlephobia.com/package/@glideapps/glide-data-grid-source)
10 | [](https://github.com/glideapps/glide-data-grid/blob/main/LICENSE)
11 | [](https://www.glideapps.com/jobs)
12 |
13 | 
--------------------------------------------------------------------------------
/packages/source/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -em
3 | source ../../config/build-util.sh
4 |
5 | ensure_bash_4
6 |
7 | shopt -s globstar
8 |
9 | ## Delete the dist folder
10 | rm -rf dist
11 |
12 | echo -e "\033[0;36m🏗️ Building Glide Data Grid Source 🏗️\033[0m"
13 |
14 | compile_esm() {
15 | tsc -p tsconfig.esm.json
16 | }
17 |
18 | compile_cjs() {
19 | tsc -p tsconfig.cjs.json
20 | }
21 |
22 | run_in_parallel compile_esm compile_cjs
23 |
24 | echo -e "\033[0;36m🎉 Source Build Complete 🎉\033[0m"
25 |
--------------------------------------------------------------------------------
/packages/source/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@glideapps/glide-data-grid-source",
3 | "version": "6.0.4-alpha9",
4 | "description": "Useful data source hooks for Glide Data Grid",
5 | "sideEffects": false,
6 | "type": "module",
7 | "browser": "dist/esm/index.js",
8 | "main": "dist/cjs/index.js",
9 | "module": "dist/esm/index.js",
10 | "types": "dist/dts/index.d.ts",
11 | "exports": {
12 | "types": "./dist/dts/index.d.ts",
13 | "import": "./dist/esm/index.js",
14 | "require": "./dist/cjs/index.js"
15 | },
16 | "files": [
17 | "dist"
18 | ],
19 | "scripts": {
20 | "build": "./build.sh",
21 | "lint": "eslint src --ext .ts,.tsx",
22 | "test": "vitest"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/glideapps/glide-data-grid.git",
27 | "directory": "packages/source"
28 | },
29 | "homepage": "https://github.com/glideapps/glide-data-grid/tree/main/cells",
30 | "author": "Glide",
31 | "license": "MIT",
32 | "keywords": [
33 | "react",
34 | "datagrid",
35 | "data-grid",
36 | "editor",
37 | "reactjs",
38 | "scrolling",
39 | "data",
40 | "table",
41 | "cell",
42 | "canvas"
43 | ],
44 | "dependencies": {
45 | "@glideapps/glide-data-grid": "6.0.4-alpha9"
46 | },
47 | "peerDependencies": {
48 | "lodash": "^4.17.19",
49 | "react": "^16.12.0 || 17.x || 18.x",
50 | "react-dom": "^16.12.0 || 17.x || 18.x"
51 | },
52 | "devDependencies": {
53 | "@babel/cli": "^7.16.0",
54 | "eslint": "^8.19.0",
55 | "eslint-plugin-import": "^2.22.0",
56 | "eslint-plugin-react": "^7.21.5",
57 | "eslint-plugin-react-hooks": "^4.2.0",
58 | "react-resize-detector": "^7.1.2",
59 | "tsc-esm-fix": "^2.7.8"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/source/src/index.ts:
--------------------------------------------------------------------------------
1 | export { useCollapsingGroups } from "./use-collapsing-groups.js";
2 | export { useMoveableColumns } from "./use-movable-columns.js";
3 | export { useColumnSort } from "./use-column-sort.js";
4 | export { useAsyncDataSource } from "./use-async-data-source.js";
5 | export { useUndoRedo } from "./use-undo-redo.js";
6 |
--------------------------------------------------------------------------------
/packages/source/test/use-column-sort.test.tsx:
--------------------------------------------------------------------------------
1 | import { compareSmart } from "../src/use-column-sort.js";
2 | import { expect, describe, test } from "vitest";
3 |
4 | describe("use-column-sort", () => {
5 | describe("compareSmart", function () {
6 | test("it does not parse date formats into numbers when sorting", function () {
7 | expect(["2022-02-01", "2023-01-01", "2024-01-01"].sort(compareSmart)).toEqual([
8 | "2022-02-01",
9 | "2023-01-01",
10 | "2024-01-01",
11 | ]);
12 |
13 | expect(["2024-12-01", "2022-12-01", "2023-12-01"].sort(compareSmart)).toEqual([
14 | "2022-12-01",
15 | "2023-12-01",
16 | "2024-12-01",
17 | ]);
18 |
19 | // This is where parseFloat() starts to fool us
20 | expect(["2022-12-03", "2022-12-01", "2022-12-02"].sort(compareSmart)).toEqual([
21 | "2022-12-01",
22 | "2022-12-02",
23 | "2022-12-03",
24 | ]);
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/packages/source/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx"],
4 | "exclude": ["./src/**/*.stories.tsx", "./src/stories/*.tsx", "./src/docs/*.tsx"],
5 | "references": [{ "path": "../core" }],
6 | "compilerOptions": {
7 | "rootDir": "./src",
8 | "outDir": "./dist/cjs",
9 | "declarationDir": "./dist/dts",
10 | "module": "CommonJS",
11 | "verbatimModuleSyntax": false,
12 | "sourceMap": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/source/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["./src/**/*.ts", "./src/**/*.tsx"],
4 | "exclude": ["./src/**/*.stories.tsx", "./src/stories/*.tsx", "./src/docs/*.tsx"],
5 | "references": [{ "path": "../core" }],
6 | "compilerOptions": {
7 | "rootDir": "./src",
8 | "outDir": "./dist/esm",
9 | "declarationDir": "./dist/dts",
10 | "sourceMap": true,
11 | "declaration": true,
12 | "declarationMap": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/source/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "ES6", "ES2017"]
4 | },
5 | "extends": "../../tsconfig.json",
6 | "include": ["./src/**/*.ts", "./src/**/*.tsx", "test/use-data-source.test.tsx", "test/use-column-sort.test.tsx"],
7 | "references": [{ "path": "../core" }]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/source/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react";
2 | import { defineConfig, configDefaults } from "vitest/config";
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | include: ["test/**/*.test.tsx", "test/**/*.test.ts"],
8 | environment: "jsdom",
9 | setupFiles: "vitest.setup.ts",
10 | threads: false,
11 | singleThread: true,
12 | watch: false,
13 | clearMocks: true,
14 | maxConcurrency: 5,
15 | fakeTimers: {
16 | toFake: [
17 | ...(configDefaults.fakeTimers.toFake ?? []),
18 | "performance",
19 | "requestAnimationFrame",
20 | "cancelAnimationFrame",
21 | ],
22 | },
23 | deps: {
24 | optimizer: {
25 | web: {
26 | include: ["vitest-canvas-mock"],
27 | },
28 | },
29 | },
30 | environmentOptions: {
31 | jsdom: {
32 | resources: "usable",
33 | },
34 | },
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/packages/source/vitest.setup.ts:
--------------------------------------------------------------------------------
1 | import "vitest-canvas-mock";
2 | import { vi } from "vitest";
3 |
4 | // this is needed to make the canvas mock work for some reason
5 | global.jest = vi;
6 |
7 | global.ResizeObserver = vi.fn().mockImplementation(() => ({
8 | observe: jest.fn(),
9 | unobserve: jest.fn(),
10 | disconnect: jest.fn(),
11 | }));
12 |
13 | Image.prototype.decode = () => new Promise(resolve => window.setTimeout(resolve, 10));
14 |
--------------------------------------------------------------------------------
/setup-react-18-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | npm i -D react@latest react-dom@latest @testing-library/react@latest @testing-library/react-hooks@latest @testing-library/user-event@14.5.1 react-test-renderer@latest @testing-library/dom
--------------------------------------------------------------------------------
/test-projects/bootstrap-projects.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | for DIR in "next-gdg" "cra5-gdg"
6 | do
7 | pushd $DIR
8 | npm ci
9 | rm -rf node_modules/@glideapps/glide-data-grid
10 | ln -s ../../../../packages/core/ node_modules/@glideapps/glide-data-grid
11 | popd
12 | done
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cra5-gdg",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@glideapps/glide-data-grid": "^3.4.1",
7 | "@testing-library/jest-dom": "^5.16.3",
8 | "@testing-library/react": "^12.1.4",
9 | "@testing-library/user-event": "^13.5.0",
10 | "@types/jest": "^27.4.1",
11 | "@types/node": "^16.11.26",
12 | "@types/react": "^17.0.43",
13 | "@types/react-dom": "^17.0.14",
14 | "canvas-hypertxt": "^1.0.3",
15 | "lodash": "^4.17.21",
16 | "marked": "^4.0.12",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2",
19 | "react-number-format": "5.0.0",
20 | "react-resize-detector": "^7.1.2",
21 | "react-responsive-carousel": "^3.2.23",
22 | "react-scripts": "5.0.0",
23 | "typescript": "^4.6.3",
24 | "web-vitals": "^2.1.4"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/test-projects/cra5-gdg/public/favicon.ico
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/test-projects/cra5-gdg/public/logo192.png
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/test-projects/cra5-gdg/public/logo512.png
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render( );
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import logo from "./logo.svg";
3 | import "./App.css";
4 | import Grid from "./components/Grid";
5 |
6 | function App() {
7 | return (
8 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/components/Grid.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import DataEditor, { DataEditorProps, GridCellKind, GridColumn } from "@glideapps/glide-data-grid";
3 | import "@glideapps/glide-data-grid/dist/index.css";
4 |
5 | export default function Grid() {
6 | const getData = React.useCallback(
7 | cell => ({
8 | kind: GridCellKind.Text,
9 | allowOverlay: true,
10 | readonly: true,
11 | data: `${cell[0]},${cell[1]}`,
12 | displayData: `${cell[0]},${cell[1]}`,
13 | }),
14 | []
15 | );
16 |
17 | const cols = React.useMemo(
18 | () => [
19 | {
20 | width: 100,
21 | title: "A",
22 | },
23 | {
24 | width: 100,
25 | title: "B",
26 | },
27 | {
28 | width: 100,
29 | title: "C",
30 | },
31 | ],
32 | []
33 | );
34 |
35 | return ;
36 | }
37 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/test-projects/cra5-gdg/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"]
3 | }
4 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
36 |
37 | # typescript
38 | *.tsbuildinfo
39 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/components/Grid.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import DataEditor, { DataEditorProps, GridCellKind, GridColumn } from "@glideapps/glide-data-grid";
3 |
4 | export default function Grid() {
5 | const getData = React.useCallback(
6 | cell => ({
7 | kind: GridCellKind.Text,
8 | allowOverlay: true,
9 | readonly: true,
10 | data: `${cell[0]},${cell[1]}`,
11 | displayData: `${cell[0]},${cell[1]}`,
12 | }),
13 | []
14 | );
15 |
16 | const cols = React.useMemo(
17 | () => [
18 | {
19 | width: 100,
20 | title: "A",
21 | },
22 | {
23 | width: 100,
24 | title: "B",
25 | },
26 | {
27 | width: 100,
28 | title: "C",
29 | },
30 | ],
31 | []
32 | );
33 |
34 | return ;
35 | }
36 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-gdg",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@glideapps/glide-data-grid": "^3.4.1",
13 | "canvas-hypertxt": "^1.0.3",
14 | "lodash": "^4.17.21",
15 | "marked": "^4.0.12",
16 | "next": "12.1.0",
17 | "react": "17.0.2",
18 | "react-dom": "17.0.2",
19 | "react-number-format": "5.0.0",
20 | "react-resize-detector": "^7.1.2",
21 | "react-responsive-carousel": "^3.2.23"
22 | },
23 | "devDependencies": {
24 | "@types/babel__core": "^7.1.19",
25 | "@types/node": "17.0.23",
26 | "@types/react": "17.0.42",
27 | "eslint": "8.11.0",
28 | "eslint-config-next": "12.1.0",
29 | "typescript": "4.6.2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import type { AppProps } from "next/app";
3 |
4 | import "@glideapps/glide-data-grid/dist/index.css";
5 |
6 | function MyApp({ Component, pageProps }: AppProps) {
7 | return ;
8 | }
9 |
10 | export default MyApp;
11 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next'
3 |
4 | type Data = {
5 | name: string
6 | }
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import Head from "next/head";
3 | import Image from "next/image";
4 | import React from "react";
5 | import dynamic from "next/dynamic";
6 | import styles from "../styles/Home.module.css";
7 |
8 | import { CompactSelection } from "@glideapps/glide-data-grid";
9 |
10 | const Grid = dynamic(
11 | () => {
12 | return import("../components/Grid");
13 | },
14 | { ssr: false }
15 | );
16 |
17 | const Home: NextPage = () => {
18 | const cs = CompactSelection.fromSingleSelection(12);
19 | return (
20 |
21 |
22 |
Create Next App
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 | Get started by editing pages/index.tsx
33 |
34 |
35 |
36 |
37 |
48 |
49 | );
50 | };
51 |
52 | export default Home;
53 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glideapps/glide-data-grid/96d1830be921db97d0e8a50139fea3b3ead24536/test-projects/next-gdg/public/favicon.ico
--------------------------------------------------------------------------------
/test-projects/next-gdg/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | min-height: 100vh;
7 | padding: 4rem 0;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .footer {
16 | display: flex;
17 | flex: 1;
18 | padding: 2rem 0;
19 | border-top: 1px solid #eaeaea;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .footer a {
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | flex-grow: 1;
29 | }
30 |
31 | .title a {
32 | color: #0070f3;
33 | text-decoration: none;
34 | }
35 |
36 | .title a:hover,
37 | .title a:focus,
38 | .title a:active {
39 | text-decoration: underline;
40 | }
41 |
42 | .title {
43 | margin: 0;
44 | line-height: 1.15;
45 | font-size: 4rem;
46 | }
47 |
48 | .title,
49 | .description {
50 | text-align: center;
51 | }
52 |
53 | .description {
54 | margin: 4rem 0;
55 | line-height: 1.5;
56 | font-size: 1.5rem;
57 | }
58 |
59 | .code {
60 | background: #fafafa;
61 | border-radius: 5px;
62 | padding: 0.75rem;
63 | font-size: 1.1rem;
64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
65 | Bitstream Vera Sans Mono, Courier New, monospace;
66 | }
67 |
68 | .grid {
69 | display: flex;
70 | align-items: center;
71 | justify-content: center;
72 | flex-wrap: wrap;
73 | max-width: 800px;
74 | }
75 |
76 | .card {
77 | margin: 1rem;
78 | padding: 1.5rem;
79 | text-align: left;
80 | color: inherit;
81 | text-decoration: none;
82 | border: 1px solid #eaeaea;
83 | border-radius: 10px;
84 | transition: color 0.15s ease, border-color 0.15s ease;
85 | max-width: 300px;
86 | }
87 |
88 | .card:hover,
89 | .card:focus,
90 | .card:active {
91 | color: #0070f3;
92 | border-color: #0070f3;
93 | }
94 |
95 | .card h2 {
96 | margin: 0 0 1rem 0;
97 | font-size: 1.5rem;
98 | }
99 |
100 | .card p {
101 | margin: 0;
102 | font-size: 1.25rem;
103 | line-height: 1.5;
104 | }
105 |
106 | .logo {
107 | height: 1em;
108 | margin-left: 0.5rem;
109 | }
110 |
111 | @media (max-width: 600px) {
112 | .grid {
113 | width: 100%;
114 | flex-direction: column;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/test-projects/next-gdg/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "esModuleInterop": true,
5 | "composite": true,
6 | "jsx": "react",
7 | "moduleResolution": "Node16", // make node-16
8 | "module": "Node16",
9 | "noImplicitAny": true,
10 | "noUnusedLocals": true,
11 | "skipLibCheck": true,
12 | "verbatimModuleSyntax": true,
13 | "noUnusedParameters": true,
14 | "strict": true,
15 | "target": "ES2022", // es2022
16 | "types": ["vitest", "node"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/update-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | function update {
6 | echo $1 $2
7 | jq --indent 4 "$1" $2 > $2.tmp
8 | mv $2.tmp $2
9 | }
10 |
11 | CUR_VERSION=`jq ".version" package.json`
12 | VERSION=${1:-$CUR_VERSION}
13 |
14 | if [[ ${VERSION::1} != "\"" ]]
15 | then
16 | VERSION="\"$VERSION\""
17 | fi
18 |
19 | update ".version = $VERSION" package.json
20 |
21 | for DIR in "cells" "source" "core"
22 | do
23 | pushd packages/$DIR
24 | update ".version = $VERSION" package.json
25 | popd
26 | done
27 |
28 | for DIR in "cells" "source"
29 | do
30 | pushd packages/$DIR
31 | update ".dependencies.\"@glideapps/glide-data-grid\" = $VERSION" package.json
32 | popd
33 | done
--------------------------------------------------------------------------------