diff --git a/apps/frontend/package.json b/apps/frontend/package.json index cef280669..896956dc2 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -98,5 +98,9 @@ "xlsx": "^0.18.5", "zod": "^3.22.4" }, - "type": "module" + "type": "module", + "dependencies": { + "@zag-js/number-input": "^0.38.1", + "@zag-js/types": "^0.38.1" + } } diff --git a/apps/frontend/src/lib/cell/CellEditors/base-editor.ts b/apps/frontend/src/lib/cell/CellEditors/base-editor.ts index 2b11c3051..db76e227b 100644 --- a/apps/frontend/src/lib/cell/CellEditors/base-editor.ts +++ b/apps/frontend/src/lib/cell/CellEditors/base-editor.ts @@ -6,8 +6,8 @@ import { toast } from 'svelte-sonner' export type SaveCallback = (value: any, preventFocus: boolean) => void -export abstract class BaseEditor implements Edition.EditorBase { - element?: HTMLInputElement | null | undefined +export abstract class BaseEditor implements Edition.EditorBase { + element?: E | null | undefined editCell?: Edition.EditCell | undefined constructor( @@ -19,15 +19,19 @@ export abstract class BaseEditor implements Edition.EditorB return this.column.field as T } + // TODO: describe type onChange(value: V) { + // TODO: better check updates + if (value === this.editCell?.model[this.editCell.prop]) { + return + } + if (this.field.required && isEmpty(value)) { - this.element?.blur() return } const result = this.field.valueSchema.safeParse(value) if (result.success) { - this.element?.blur() this.saveCallback(result.data, false) } else { toast.warning(result.error.flatten((i) => i.message).formErrors.join('\n')) diff --git a/apps/frontend/src/lib/cell/CellEditors/date-editor.ts b/apps/frontend/src/lib/cell/CellEditors/date-editor.ts index 4674e1cb0..1aa90aedf 100644 --- a/apps/frontend/src/lib/cell/CellEditors/date-editor.ts +++ b/apps/frontend/src/lib/cell/CellEditors/date-editor.ts @@ -1,13 +1,11 @@ -import type { Edition, RevoGrid } from '@revolist/revogrid/dist/types/interfaces' +import type { RevoGrid } from '@revolist/revogrid/dist/types/interfaces' import type { VNode } from '@revolist/revogrid/dist/types/stencil-public-runtime' import type { DateField } from '@undb/core' import delay from 'delay' import htm from 'htm' import { BaseEditor } from './base-editor' -export type SaveCallback = (value: Edition.SaveData, preventFocus: boolean) => void - -export class DateEditor extends BaseEditor { +export class DateEditor extends BaseEditor { private initElement() { const element = this.element if (!element) return diff --git a/apps/frontend/src/lib/cell/CellEditors/email-editor.ts b/apps/frontend/src/lib/cell/CellEditors/email-editor.ts index f5b6718e2..bcb5ec379 100644 --- a/apps/frontend/src/lib/cell/CellEditors/email-editor.ts +++ b/apps/frontend/src/lib/cell/CellEditors/email-editor.ts @@ -5,7 +5,7 @@ import delay from 'delay' import htm from 'htm' import { BaseEditor } from './base-editor' -export class EmailEditor extends BaseEditor { +export class EmailEditor extends BaseEditor { public element: HTMLInputElement | null = null public editCell: Edition.EditCell | undefined = undefined diff --git a/apps/frontend/src/lib/cell/CellEditors/normalizer.ts b/apps/frontend/src/lib/cell/CellEditors/normalizer.ts new file mode 100644 index 000000000..ef3071df4 --- /dev/null +++ b/apps/frontend/src/lib/cell/CellEditors/normalizer.ts @@ -0,0 +1,3 @@ +import { createNormalizer } from '@zag-js/types' + +export const normalizer = createNormalizer((v) => v) diff --git a/apps/frontend/src/lib/cell/CellEditors/number-editor.ts b/apps/frontend/src/lib/cell/CellEditors/number-editor.ts index a541db04f..a999afc6d 100644 --- a/apps/frontend/src/lib/cell/CellEditors/number-editor.ts +++ b/apps/frontend/src/lib/cell/CellEditors/number-editor.ts @@ -1,24 +1,40 @@ import type { Edition, RevoGrid } from '@revolist/revogrid/dist/types/interfaces' import type { VNode } from '@revolist/revogrid/dist/types/stencil-public-runtime' import type { CurrencyField, NumberField } from '@undb/core' +import * as numberInput from '@zag-js/number-input' import delay from 'delay' import htm from 'htm' -import { BaseEditor } from './base-editor' +import { BaseEditor, type SaveCallback } from './base-editor' +import { normalizer } from './normalizer' -export class NumberEditor extends BaseEditor { +export class NumberEditor extends BaseEditor { public element: HTMLInputElement | null = null public editCell: Edition.EditCell | undefined = undefined + private api: numberInput.Api | null = null - private initElement() { - const element = this.element - if (!element) return + constructor( + public column: RevoGrid.ColumnRegular, + protected saveCallback: SaveCallback, + ) { + super(column, saveCallback) + + const service = numberInput.machine({ + id: this.column.prop as string, + }) + const machine = service.start() + + const api = numberInput.connect(machine.state, machine.send, normalizer) - element.focus() + this.api = api + } + private initElement() { const editCell = this.editCell if (!editCell) return - element.value = editCell.model[editCell.prop] as string + const value = editCell.model[editCell.prop] as string + this.api?.setValue(Number(value)) + this.api?.focus() } async componentDidRender() { @@ -28,12 +44,17 @@ export class NumberEditor extends BaseEditor { render(createComponent: RevoGrid.HyperFunc) { const html = htm.bind(createComponent) + const api = this.api + return html` - this.onChange(Number((e.target as HTMLInputElement).value))} - class="border-2 border-primary-300 rounded-none text-gray-900 text-sm focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5" - /> +
+ this.onChange(Number((e.target as HTMLInputElement).value))} + class="border-2 border-primary-300 rounded-none text-gray-900 text-sm focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5" + /> +
` } } diff --git a/apps/frontend/src/lib/cell/CellEditors/string-editor.ts b/apps/frontend/src/lib/cell/CellEditors/string-editor.ts index cc621cb6a..07b3dcedb 100644 --- a/apps/frontend/src/lib/cell/CellEditors/string-editor.ts +++ b/apps/frontend/src/lib/cell/CellEditors/string-editor.ts @@ -5,7 +5,7 @@ import delay from 'delay' import htm from 'htm' import { BaseEditor } from './base-editor' -export class StringEditor extends BaseEditor { +export class StringEditor extends BaseEditor { private initElement() { const element = this.element if (!element) return diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22abed73a..ec3bf6e41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -465,6 +465,13 @@ importers: version: 1.1.0 apps/frontend: + dependencies: + '@zag-js/number-input': + specifier: ^0.38.1 + version: 0.38.1 + '@zag-js/types': + specifier: ^0.38.1 + version: 0.38.1 devDependencies: '@playwright/test': specifier: ^1.40.1 @@ -757,7 +764,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -824,7 +831,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) type-fest: specifier: ^4.8.3 version: 4.8.3 @@ -891,7 +898,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -925,7 +932,7 @@ importers: version: link:../../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1068,7 +1075,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) type-fest: specifier: ^4.8.3 version: 4.8.3 @@ -1127,7 +1134,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1179,7 +1186,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1204,7 +1211,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1271,7 +1278,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1314,7 +1321,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -1357,7 +1364,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.0.1 - version: 8.0.1(typescript@5.3.3) + version: 8.0.1(@swc/core@1.4.2)(ts-node@10.9.2)(typescript@5.3.3) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -2940,6 +2947,12 @@ packages: '@swc/helpers': 0.5.6 dev: true + /@internationalized/number@3.5.1: + resolution: {integrity: sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==} + dependencies: + '@swc/helpers': 0.5.6 + dev: false + /@ioredis/commands@1.2.0: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} @@ -6136,7 +6149,6 @@ packages: resolution: {integrity: sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==} dependencies: tslib: 2.6.2 - dev: true /@swc/types@0.1.5: resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==} @@ -6547,7 +6559,7 @@ packages: /@types/ioredis@4.28.10: resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.20 dev: false /@types/istanbul-lib-coverage@2.0.4: @@ -6706,7 +6718,6 @@ packages: resolution: {integrity: sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==} dependencies: undici-types: 5.26.5 - dev: true /@types/nodemailer@6.4.14: resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==} @@ -7305,6 +7316,79 @@ packages: /@xtuc/long@4.2.2: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + /@zag-js/anatomy@0.38.1: + resolution: {integrity: sha512-y6foq9vX+EfjfCwi9DMt5gI9eU7kshFjioidACI+u8TjBG2Z3nOjvEq+3Ct+dy5v/aRwsvSquaXpB555e0OLNQ==} + dev: false + + /@zag-js/core@0.38.1: + resolution: {integrity: sha512-v1WKbIzOWN2yN450zxVA1QCnI8np1vvcpPwsor9ZLM8OnION6yeiKCyjI7DqmLebCMhN9wnjExkjcb8ZAmNDuA==} + dependencies: + '@zag-js/store': 0.38.1 + klona: 2.0.6 + dev: false + + /@zag-js/dom-event@0.38.1: + resolution: {integrity: sha512-+EkCNsS9oE8ZtgUU7G9EVXrZKcCaIJlSEVCliFjYUpDgo3Jxl1sOwbSIqaIi/yKIuJSl/lswz3jCxue0BW2bWw==} + dependencies: + '@zag-js/text-selection': 0.38.1 + '@zag-js/types': 0.38.1 + dev: false + + /@zag-js/dom-query@0.38.1: + resolution: {integrity: sha512-bGtc3hfBY4ngxHNTohT2XAwHpKkbPMeUF5JggSETMMtfv9e+FLy4F79j7OWjJjvQZKKcVesrw/1s0TlhkvZioA==} + dev: false + + /@zag-js/form-utils@0.38.1: + resolution: {integrity: sha512-o4G80b/XHhGUy4pJnTdJW9TwfOREXhiafSv6YQ5Zpk0EPi5lk1IroYL6ZJT6/vuvirc+HVzE+znvlYOfDl5PVQ==} + dependencies: + '@zag-js/mutation-observer': 0.38.1 + dev: false + + /@zag-js/mutation-observer@0.38.1: + resolution: {integrity: sha512-y3JdkLTxE+5J1BWIkVwyc+IDCSUW2DgHxR9UHUUW/uRPe3SG5/MDQGupdVy/dUH4WTi/bcFwXkpfBxBP1+idFw==} + dev: false + + /@zag-js/number-input@0.38.1: + resolution: {integrity: sha512-zjnAvF9IYHeg7aOcDbmXAKLOEykaHhBmkCzggAITHtL6JrZRCp6UftI8OpwltETsHpSMi+ANwj4oLdS0PWEhkg==} + dependencies: + '@internationalized/number': 3.5.1 + '@zag-js/anatomy': 0.38.1 + '@zag-js/core': 0.38.1 + '@zag-js/dom-event': 0.38.1 + '@zag-js/dom-query': 0.38.1 + '@zag-js/form-utils': 0.38.1 + '@zag-js/mutation-observer': 0.38.1 + '@zag-js/number-utils': 0.38.1 + '@zag-js/types': 0.38.1 + '@zag-js/utils': 0.38.1 + dev: false + + /@zag-js/number-utils@0.38.1: + resolution: {integrity: sha512-4DHzyxSxlRFhKoY7hUz3s2rRcjZhiqSBLQ8CE/QRQyQoHww/QaCFqZT6NT0d1ZOU0IRPvUszVr/2I8PbIr9LSA==} + dev: false + + /@zag-js/store@0.38.1: + resolution: {integrity: sha512-gaOsP8jjqZnivZSmzj3xHtpd8Vm9KMo3ESp41iinyry5suSgEn7BV12dTGG90+DBAfuCggqYv/dLjQCxbqQeMA==} + dependencies: + proxy-compare: 2.6.0 + dev: false + + /@zag-js/text-selection@0.38.1: + resolution: {integrity: sha512-FNcYOIlH21rBdiZCaFsaCRDtbmr5WBNEXiqfS8dFWx/x1MxNZ9iyDnBLRGy7OQnTGsyLdpCdHTtGjX65EkKGAw==} + dependencies: + '@zag-js/dom-query': 0.38.1 + dev: false + + /@zag-js/types@0.38.1: + resolution: {integrity: sha512-kSpjSE+d+5bucALf5DYaHloekk5V0OjlyOowqEEUEjCipGpPLWPxRXSKjiSxQIFftZij07sLxjn609b8PTKrEw==} + dependencies: + csstype: 3.1.3 + dev: false + + /@zag-js/utils@0.38.1: + resolution: {integrity: sha512-kJ15yxO5tWCxZSMVIBSYPE8nfHA9zqtaOlZ8IiFDPPPi137SJDjKz/VIXlSUyCd/af7kQTCnxqVyNFLiyxGaRw==} + dev: false + /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -9190,6 +9274,10 @@ packages: cssom: 0.3.8 dev: true + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: false + /cz-conventional-changelog@3.3.0(@types/node@20.10.5)(typescript@5.3.3): resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} engines: {node: '>= 10'} @@ -12669,6 +12757,11 @@ packages: engines: {node: '>=6'} dev: true + /klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + dev: false + /knex@2.5.1(better-sqlite3@8.7.0): resolution: {integrity: sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==} engines: {node: '>=12'} @@ -15392,22 +15485,6 @@ packages: yaml: 1.10.2 dev: true - /postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 3.0.0 - yaml: 2.3.4 - dev: true - /postcss-load-config@4.0.2(postcss@8.4.32): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} @@ -15751,6 +15828,10 @@ packages: - supports-color dev: false + /proxy-compare@2.6.0: + resolution: {integrity: sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==} + dev: false + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false @@ -18294,45 +18375,6 @@ packages: - ts-node dev: true - /tsup@8.0.1(typescript@5.3.3): - resolution: {integrity: sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - dependencies: - bundle-require: 4.0.1(esbuild@0.19.7) - cac: 6.7.14 - chokidar: 3.5.3 - debug: 4.3.4(supports-color@5.5.0) - esbuild: 0.19.7 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.2 - resolve-from: 5.0.0 - rollup: 4.5.1 - source-map: 0.8.0-beta.0 - sucrase: 3.32.0 - tree-kill: 1.2.2 - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - /tuf-js@2.1.0: resolution: {integrity: sha512-eD7YPPjVlMzdggrOeE8zwoegUaG/rt6Bt3jwoQPunRiNVzgcCE009UDFJKJjG+Gk9wFu6W/Vi+P5d/5QpdD9jA==} engines: {node: ^16.14.0 || >=18.0.0}