8000 feat: Improved Grid View by DarkPhoenix2704 · Pull Request #10520 · nocodb/nocodb · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: Improved Grid View #10520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 576 commits into from
Feb 17, 2025
Merged

feat: Improved Grid View #10520

merged 576 commits into from
Feb 17, 2025

Conversation

DarkPhoenix2704
Copy link
Member

Change Summary

Fixes Associated Performance issues with Grid View

Change type

  • feat: (new feature for the user, not a new feature for build script)

Copy link
Contributor
coderabbitai bot commented Feb 17, 2025
📝 Walkthrough

Walkthrough

This pull request introduces significant modifications across the application’s UI and grid functionality. Global style updates enhance dropdown visuals and conditional heading formatting in rich text. Several existing cell editor components have been refactored, including the removal of unused dependencies, updated prop access, and refined focus logic. Additionally, a large suite of new files has been added for the smart sheet grid’s canvas layer, including numerous cell renderers, composable functions for grid operations (e.g., column/row reordering, copy/paste, data fetching, fill handling, keyboard and mouse navigation), and supporting loaders and context components. Minor component adjustments and file removals are also included.

Changes

File(s) Change Summary
packages/nc-gui/assets/style.scss Added styles for .ant-select-item-option-selected (including nested active background) and conditional heading styling for .nc-rich-text-content when absent of grid class.
packages/nc-gui/components/account/UsersModal.vue, various */Editor.vue, TextArea.vue, cmd-l/index.vue, dashboard/FeatureExperimentation.vue, etc. Refactored cell editors and other UI components: removal of unused variables (e.g., appInfo), updated prop handling (switching to defineProps), refined focus and keyboard event logic, and minor layout adjustments.
packages/nc-gui/components/cell/GeoData*.vue Introduced a new standalone GeoData.vue component; removed legacy files (GeoData/Editor.vue, GeoData/Readonly.vue, and GeoData/index.vue) to consolidate geographical data handling.
packages/nc-gui/components/smartsheet/grid/canvas/cells/... (e.g., AILongText.ts, Attachment.ts, Barcode.ts, Button.ts, Checkbox.ts, Currency.ts, Date.ts, DateTime.ts, Decimal.ts, Duration.ts, Email.ts, Formula.ts, GenericReadonlyRenderer.ts, GeoData.ts, Json.ts, LTAR/*, Links.ts, LongText.ts, Lookup.ts, MultiSelect.ts, Number.ts, Percent.ts, PhoneNumber.ts, Plain.ts, QRCode.ts, Rating.ts, Rollup.ts, SingleLineText.ts, SingleSelect.ts, Time.ts, Url.ts, User.ts, Year.ts) Added a comprehensive suite of new cell renderer implementations for the grid’s canvas, each handling rendering, interaction (click, hover, key events), and editing logic for various data types and relationship models.
packages/nc-gui/components/smartsheet/grid/canvas/composables/... (e.g., useCanvasRender.ts, useCanvasTable.ts, useColumnReorder.ts, useColumnResize.ts, useCopyPaste.ts, useDataFetch.ts, useFillHandler.ts, useKeyboardNavigation.ts, useMouseSelection.ts, useRowReOrder.ts) and loaders (e.g., ActionManager.ts, ImageLoader.ts, SpriteLoader.ts, TableMetaLoader.ts) Introduced new composables and loaders to manage grid operations such as rendering, column/row reordering, resizing, copy-paste, data fetching, and image/sprite/table metadata loading.
packages/nc-gui/components/smartsheet/grid/canvas/context/... and packages/nc-gui/components/smartsheet/grid/canvas/index.vue Added new grid canvas context components (e.g., Aggregation.vue, Cell.vue) and a main canvas component to manage grid display, bulk operations, and context menu interactions.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant GC as Grid Canvas
    participant GCH as Grid Cell Handler
    participant CR as Specific Cell Renderer
    participant AM as ActionManager

    U->>GC: Interact (click/keydown)
    GC->>GCH: Process event & get cell position
    GCH->>CR: Delegate rendering based on column type
    CR-->>GCH: Render cell content (text/tag/icon)
    GCH->>AM: If action required, trigger update/save
    AM-->>GC: Update cell state and refresh canvas
    GC->>U: Render updated grid view
Loading

Possibly related PRs

Suggested labels

👓 Scope : View, size:XL, lgtm


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

🛑 Comments failed to post (57)
packages/nc-gui/components/smartsheet/grid/canvas/cells/Year.ts (3)

73-78: 🛠️ Refactor suggestion

Extract hardcoded dimensions and improve error handling.

The clickable area uses a hardcoded height and lacks error handling for invalid row/column combinations.

Consider these improvements:

+const CELL_HEIGHT = 33

+if (!bound) {
+  console.warn('Invalid cell position')
+  return false
+}

const clickableArea = {
  x: bound.x + padding,
  y: bound.y,
  width: textWidth,
-  height: 33,
+  height: CELL_HEIGHT,
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const CELL_HEIGHT = 33

    if (!bound) {
      console.warn('Invalid cell position')
      return false
    }

    const clickableArea = {
      x: bound.x + padding,
      y: bound.y,
      width: textWidth,
      height: CELL_HEIGHT,
    }

1-4: ⚠️ Potential issue

Add missing CellRenderer interface import.

The CellRenderer interface is used but not imported. This could lead to type checking issues.

Add the missing import:

import dayjs from 'dayjs'
import { defaultOffscreen2DContext, isBoxHovered, renderSingleLineText, renderTagLabel, truncateText } from '../utils/canvas'
+import type { CellRenderer } from '../types'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import dayjs from 'dayjs'
import { defaultOffscreen2DContext, isBoxHovered, renderSingleLineText, renderTagLabel, truncateText } from '../utils/canvas'
import type { CellRenderer } from '../types'

export const YearCellRenderer: CellRenderer = {

11-14: 🛠️ Refactor suggestion

Strengthen year validation.

The current year parsing is not strict enough. It will accept any string that starts with numbers and parse them as valid years.

Add strict parsing to ensure only valid year formats are accepted:

-const year = dayjs(value.toString(), 'YYYY')
+const year = dayjs(value.toString(), 'YYYY', true)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      const year = dayjs(value.toString(), 'YYYY', true)
      if (year.isValid()) {
        text = year.format('YYYY')
      }
packages/nc-gui/components/smartsheet/grid/canvas/cells/QRCode.ts (1)

32-54: ⚠️ Potential issue

Fix inconsistent y-coordinate calculations in return values.

The y-coordinate calculation is inconsistent between success and failure cases:

  • Success case: y: yPos * 2 (where yPos is centered)
  • Failure case: y: y + padding + size

This inconsistency could cause layout issues when switching between states.

       return {
         x: x + padding + size,
-        y: yPos * 2,
+        y: y + padding + size,
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const qrCanvas = imageLoader.loadOrGetQR(qrValue, size, {
      dark: meta.dark,
      light: meta.light,
    })

    if (qrCanvas) {
      const xPos = renderAsTag ? x + padding : x + (width - size) / 2
      const yPos = y + (height - size) / 2
      imageLoader.renderQRCode(ctx, qrCanvas, xPos, yPos, size)

      return {
        x: x + padding + size,
        y: y + padding + size,
      }
    } else {
      imageLoader.renderPlaceholder(ctx, x + padding, y + padding, size, 'qr_code')

      return {
        x: x + padding + size,
        y: y + padding + size,
      }
    }
  },
packages/nc-gui/components/smartsheet/grid/canvas/composables/useRowReOrder.ts (3)

56-72: 🛠️ Refactor suggestion

Optimize drag performance and edge scrolling.

The drag handler could benefit from performance optimizations and better configuration.

Consider implementing throttling and debouncing:

import { throttle, debounce } from 'lodash-es'

// Add to configuration
interface ScrollConfig {
  edgeThreshold: number
  scrollStep: number
}

// Throttle drag handler
const handleDrag = throttle((e: MouseEvent) => {
  currentDragY.value = e.clientY
  const rect = canvasRef.value?.getBoundingClientRect()
  if (!rect) return

  targetRowIndex.value = findRowFromPosition(e.clientY - rect.top)
  
  // Debounce canvas refresh for better performance
  debouncedRefreshCanvas()

  const mouseY = e.clientY - rect.top
  handleEdgeScroll(mouseY, rect.height)
}, 16) // ~60fps

// Separate edge scrolling logic
const handleEdgeScroll = (mouseY: number, rectHeight: number) => {
  const { edgeThreshold, scrollStep } = config.scroll
  
  if (mouseY < edgeThreshold) {
    scrollToCell(Math.max(0, targetRowIndex.value - scrollStep), 0)
  } else if (mouseY > rectHeight - edgeThreshold) {
    scrollToCell(Math.min(totalRows.value - 1, targetRowIndex.value + scrollStep), 0)
  }
}

const debouncedRefreshCanvas = debounce(triggerRefreshCanvas, 16)

36-54: 🛠️ Refactor suggestion

Optimize event listeners and add error handling.

The drag start handler could benefit from performance optimizations and better error handling.

Consider these improvements:

   const handleDragStart = (e: MouseEvent) => {
     const rect = canvasRef.value?.getBoundingClientRect()
     if (!rect) return
 
     const rowIndex = findRowFromPosition(e.clientY - rect.top) - 1
     const row = cachedRows.value.get(rowIndex)
-    if (!row) return
+    if (!row) {
+      console.warn(`Row ${rowIndex} not found in cache`)
+      return
+    }
 
-    row.rowMeta.isDragging = true
-    cachedRows.value.set(rowIndex, row)
+    try {
+      row.rowMeta.isDragging = true
+      cachedRows.value.set(rowIndex, row)
+    } catch (error) {
+      console.error('Failed to update row metadata:', error)
+      return
+    }
     isDragging.value = true
     draggedRowIndex.value = rowIndex
     targetRowIndex.value = rowIndex + 1
     dragStartY.value = e.clientY
     currentDragY.value = e.clientY
 
-    window.addEventListener('mousemove', handleDrag)
-    window.addEventListener('mouseup', handleDragEnd)
+    window.addEventListener('mousemove', handleDrag, { passive: true })
+    window.addEventListener('mouseup', handleDragEnd, { passive: true })
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const handleDragStart = (e: MouseEvent) => {
    const rect = canvasRef.value?.getBoundingClientRect()
    if (!rect) return

    const rowIndex = findRowFromPosition(e.clientY - rect.top) - 1
    const row = cachedRows.value.get(rowIndex)
    if (!row) {
      console.warn(`Row ${rowIndex} not found in cache`)
      return
    }

    try {
      row.rowMeta.isDragging = true
      cachedRows.value.set(rowIndex, row)
    } catch (error) {
      console.error('Failed to update row metadata:', error)
      return
    }
    isDragging.value = true
    draggedRowIndex.value = rowIndex
    targetRowIndex.value = rowIndex + 1
    dragStartY.value = e.clientY
    currentDragY.value = e.clientY

    window.addEventListener('mousemove', handleDrag, { passive: true })
    window.addEventListener('mouseup', handleDragEnd, { passive: true })
  }

73-96: 🛠️ Refactor suggestion

Enhance error handling and cleanup robustness.

The drag end and cleanup functions could benefit from better error handling and more robust cleanup.

Consider these improvements:

   async function handleDragEnd() {
     if (draggedRowIndex.value !== null && draggedRowIndex.value + 1 !== targetRowIndex.value) {
-      await updateRecordOrder(draggedRowIndex.value, targetRowIndex.value === totalRows.value ? null : targetRowIndex.value)
+      try {
+        await updateRecordOrder(
+          draggedRowIndex.value,
+          targetRowIndex.value === totalRows.value ? null : targetRowIndex.value
+        )
+      } catch (error) {
+        console.error('Failed to update record order:', error)
+        // Consider showing user feedback
+      }
     }
     cleanup()
   }
 
   function cleanup() {
+    // Reset all dragging related state
     isDragging.value = false
     draggedRowIndex.value = null
     targetRowIndex.value = null
-    window.removeEventListener('mousemove', handleDrag)
-    window.removeEventListener('mouseup', handleDragEnd)
+    
+    // Clean up throttled/debounced functions
+    handleDrag.cancel?.()
+    debouncedRefreshCanvas.cancel?.()
+    
+    // Remove event listeners with same options as add
+    window.removeEventListener('mousemove', handleDrag, { passive: true })
+    window.removeEventListener('mouseup', handleDragEnd, { passive: true })
+    
     triggerRefreshCanvas()
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  async function handleDragEnd() {
    if (draggedRowIndex.value !== null && draggedRowIndex.value + 1 !== targetRowIndex.value) {
      try {
        await updateRecordOrder(
          draggedRowIndex.value,
          targetRowIndex.value === totalRows.value ? null : targetRowIndex.value
        )
      } catch (error) {
        console.error('Failed to update record order:', error)
        // Consider showing user feedback
      }
    }
    cleanup()
  }

  function cleanup() {
    // Reset all dragging related state
    isDragging.value = false
    draggedRowIndex.value = null
    targetRowIndex.value = null

    // Clean up throttled/debounced functions
    handleDrag.cancel?.()
    debouncedRefreshCanvas.cancel?.()

    // Remove event listeners with same options as add
    window.removeEventListener('mousemove', handleDrag, { passive: true })
    window.removeEventListener('mouseup', handleDragEnd, { passive: true })

    triggerRefreshCanvas()
  }

  onBeforeUnmount(() => {
    cleanup()
  })

  return {
    handleDragStart,
  }
}
packages/nc-gui/components/smartsheet/grid/canvas/cells/Duration.ts (2)

1-3: ⚠️ Potential issue

Add missing imports for types and utility functions.

Several types and utility functions are used but not imported:

  • CellRenderer type/interface
  • isValidValue, parseProp, and convertMS2Duration utility functions

Add the following imports:

+import type { CellRenderer } from '../types'
 import { renderSingleLineText, renderTagLabel } from '../utils/canvas'
+import { isValidValue, parseProp, convertMS2Duration } from '../utils/cell'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import type { CellRenderer } from '../types'
import { renderSingleLineText, renderTagLabel } from '../utils/canvas'
import { isValidValue, parseProp, convertMS2Duration } from '../utils/cell'

export const DurationCellRenderer: CellRenderer = {

41-45: 🛠️ Refactor suggestion

Avoid direct row mutation and add error handling.

The current implementation directly mutates the row data and lacks error handling for makeCellEditable.

-    if (/^[0-9]$/.test(e.key) && columnObj.title) {
-      row.row[columnObj.title] = ''
-      makeCellEditable(row, column)
-      return true
-    }
+    if (/^[0-9]$/.test(e.key) && columnObj.title) {
+      try {
+        // Use a proper update method instead of direct mutation
+        await updateRowValue(row, columnObj.title, '')
+        await makeCellEditable(row, column)
+        return true
+      } catch (error) {
+        console.error('Failed to make cell editable:', error)
+        return false
+      }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (/^[0-9]$/.test(e.key) && columnObj.title) {
      try {
        // Use a proper update method instead of direct mutation
        await updateRowValue(row, columnObj.title, '')
        await makeCellEditable(row, column)
        return true
      } catch (error) {
        console.error('Failed to make cell editable:', error)
        return false
      }
    }
packages/nc-gui/components/smartsheet/grid/canvas/cells/Checkbox.ts (6)

1-2: ⚠️ Potential issue

Add missing imports.

The following types/constants are used but not imported:

  • CellRenderer type
  • rowHeightInPx constant

Add the missing imports at the top of the file:

import { isBoxHovered, renderTag } from '../utils/canvas'
+import type { CellRenderer } from '../types'
+import { rowHeightInPx } from '../constants'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import { isBoxHovered, renderTag } from '../utils/canvas'
import type { CellRenderer } from '../types'
import { rowHeightInPx } from '../constants'
export const CheckboxCellRenderer: CellRenderer = {
  // rest of the implementation...
}

70-82: 🛠️ Refactor suggestion

Improve error handling and type safety in keyboard handler.

The keyboard handler has several areas for improvement:

  1. Inconsistent variable naming (columnObj vs column)
  2. Direct property access without type checking
  3. Missing error handling for async operation
 async handleKeyDown(ctx) {
   const { e, row, column, updateOrSaveRow, readonly } = ctx
-  const columnObj = column.columnObj
+  const { columnObj } = column
   if (column.readonly || readonly) return

   if (e.key === 'Enter') {
-    row.row[columnObj.title!] = !row.row[columnObj.title!]
-    await updateOrSaveRow(row, columnObj.title)
+    const title = columnObj.title
+    if (!title) return false
+    try {
+      row.row[title] = !row.row[title]
+      await updateOrSaveRow(row, title)
+    } catch (error) {
+      console.error('Failed to update checkbox value:', error)
+    }
     return true
   }

   return false
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  async handleKeyDown(ctx) {
    const { e, row, column, updateOrSaveRow, readonly } = ctx
    const { columnObj } = column
    if (column.readonly || readonly) return

    if (e.key === 'Enter') {
      const title = columnObj.title
      if (!title) return false
      try {
        row.row[title] = !row.row[title]
        await updateOrSaveRow(row, title)
      } catch (error) {
        console.error('Failed to update checkbox value:', error)
      }
      return true
    }

    return false
  },

24-24: 🛠️ Refactor suggestion

Return coordinates on early exit.

Early return without coordinates could affect layout calculations in the grid. Consider returning default coordinates.

-if (readonly && !checked && !renderAsTag) return
+if (readonly && !checked && !renderAsTag) return { x, y: y + height }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (readonly && !checked && !renderAsTag) return { x, y: y + height }

18-22: ⚠️ Potential issue

Add missing imports for utility functions.

The following utility functions are used but not imported:

  • parseProp
  • extractCheckboxIcon

Add the missing imports at the top of the file:

import { isBoxHovered, renderTag } from '../utils/canvas'
+import { parseProp, extractCheckboxIcon } from '../utils/helpers'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import { isBoxHovered, renderTag } from '../utils/canvas'
import { parseProp, extractCheckboxIcon } from '../utils/helpers'

// ... other imports and file content

    const columnMeta = {
      color: 'primary',
      ...parseProp(column.meta),
      icon: extractCheckboxIcon(column?.meta ?? {}),
    }

4-4: ⚠️ Potential issue

Use safe array access.

Direct array access with the non-null assertion operator (!) is unsafe. Consider using a default value or runtime check.

-height = rowHeightInPx[1]!
+height = rowHeightInPx[1] ?? rowHeightInPx[0] ?? 32
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    height = rowHeightInPx[1] ?? rowHeightInPx[0] ?? 32

83-109: 🛠️ Refactor suggestion

Improve error handling and maintainability in click handler.

The click handler has several areas for improvement:

  1. Missing type checking for column title
  2. Missing error handling for async operation
  3. Magic numbers in checkbox bounds calculation
 async handleClick(ctx) {
   const { row, column, updateOrSaveRow, getCellPosition, mousePosition, selected, readonly } = ctx
   if (column.readonly || readonly) return false

   if (selected) {
-    row.row[column.title!] = !row.row[column.title!]
-    await updateOrSaveRow(row, column.title)
+    const title = column.title
+    if (!title) return false
+    try {
+      row.row[title] = !row.row[title]
+      await updateOrSaveRow(row, title)
+    } catch (error) {
+      console.error('Failed to update checkbox value:', error)
+    }
     return true
   }

   const bounds = getCellPosition(column, row.rowMeta.rowIndex!)

+  const CHECKBOX_SIZE = 14
+  const VERTICAL_OFFSET = 8
   const checkboxBounds = {
-    x: bounds.x + bounds.width / 2 - 7,
-    y: bounds.y + bounds.height / 2 - 8,
-    width: 14,
-    height: 14,
+    x: bounds.x + bounds.width / 2 - CHECKBOX_SIZE / 2,
+    y: bounds.y + bounds.height / 2 - VERTICAL_OFFSET,
+    width: CHECKBOX_SIZE,
+    height: CHECKBOX_SIZE,
   }

   if (isBoxHovered(checkboxBounds, mousePosition)) {
-    row.row[column.title!] = !row.row[column.title!]
-    await updateOrSaveRow(row, column.title)
+    const title = column.title
+    if (!title) return false
+    try {
+      row.row[title] = !row.row[title]
+      await updateOrSaveRow(row, title)
+    } catch (error) {
+      console.error('Failed to update checkbox value:', error)
+    }
     return true
   }

   return false
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  async handleClick(ctx) {
    const { row, column, updateOrSaveRow, getCellPosit
F438
ion, mousePosition, selected, readonly } = ctx
    if (column.readonly || readonly) return false

    if (selected) {
      const title = column.title
      if (!title) return false
      try {
        row.row[title] = !row.row[title]
        await updateOrSaveRow(row, title)
      } catch (error) {
        console.error('Failed to update checkbox value:', error)
      }
      return true
    }

    const bounds = getCellPosition(column, row.rowMeta.rowIndex!)

    const CHECKBOX_SIZE = 14
    const VERTICAL_OFFSET = 8
    const checkboxBounds = {
      x: bounds.x + bounds.width / 2 - CHECKBOX_SIZE / 2,
      y: bounds.y + bounds.height / 2 - VERTICAL_OFFSET,
      width: CHECKBOX_SIZE,
      height: CHECKBOX_SIZE,
    }

    if (isBoxHovered(checkboxBounds, mousePosition)) {
      const title = column.title
      if (!title) return false
      try {
        row.row[title] = !row.row[title]
        await updateOrSaveRow(row, title)
      } catch (error) {
        console.error('Failed to update checkbox value:', error)
      }
      return true
    }

    return false
  },
packages/nc-gui/components/smartsheet/grid/canvas/composables/useDataFetch.ts (1)

24-37: 🛠️ Refactor suggestion

Handle out-of-range chunkId + 1 for safety

When isInitialLoad is true, the code sets the state of chunkId + 1 to 'loading' or 'loaded'. If chunkId happens to be the last possible chunk, this can go out of range. Consider guarding against that scenario to avoid potential index errors or undefined behavior.

A minimal fix could be:

 if (isInitialLoad && chunkId + 1 < chunkStates.value.length) {
   chunkStates.value[chunkId + 1] = 'loading'
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const fetchChunk = async (chunkId: number, isInitialLoad = false) => {
    if (chunkStates.value[chunkId]) return

    const offset = chunkId * CHUNK_SIZE
    const limit = isInitialLoad ? INITIAL_LOAD_SIZE : CHUNK_SIZE

    if (offset >= totalRows.value) {
      return
    }

    chunkStates.value[chunkId] = 'loading'
    if (isInitialLoad && chunkId + 1 < chunkStates.value.length) {
      chunkStates.value[chunkId + 1] = 'loading'
    }
packages/nc-gui/components/smartsheet/grid/canvas/composables/useColumnResize.ts (1)

75-77: 🛠️ Refactor suggestion

Unify minimum-width enforcement with normalizeWidth.
Currently, a hardcoded 50 minimum width is used in multiple places (lines 76, 126) while normalizeWidth() applies different fallback minimum widths (80) for unknown column types. Consider consistently using normalizeWidth() to ensure that all columns respect the same constraints.

--- a/packages/nc-gui/components/smartsheet/grid/canvas/composables/useColumnResize.ts
+++ b/packages/nc-gui/components/smartsheet/grid/canvas/composables/useColumnResize.ts
@@ -74,7 +74,7 @@ const handleMouseMove = (e: MouseEvent) => {
       if (isResizing.value && activeColumn.value) {
         const delta = mousePosition.value.x - activeColumn.value.startX
-        const newWidth = Math.max(50, activeColumn.value.initialWidth + delta)
+        const newWidth = normalizeWidth({ uidt: undefined }, activeColumn.value.initialWidth + delta)
         onResize?.(activeColumn.value.id, newWidth)
       }
@@ -125,7 +125,7 @@ if (shouldTriggerResize && activeColumn.value && mousePosition.value) {
       const delta = mousePosition.value.x - activeColumn.value.startX
-      const newWidth = Math.max(50, activeColumn.value.initialWidth + delta)
+      const newWidth = normalizeWidth({ uidt: undefined }, activeColumn.value.initialWidth + delta)
       onResizeEnd?.(activeColumn.value.id, newWidth)
     }

Also applies to: 126-127, 165-170

packages/nc-gui/components/smartsheet/grid/canvas/cells/LTAR/ManyToMany.ts (1)

77-79: ⚠️ Potential issue

Bug: Unused count variable and missing iteration logic.
The variable count is never incremented, yet it’s used in conditions like (count < cells.length). This likely prevents correct handling of multiple cells and the rendering of the ellipsis when items overflow.

- let flag = false
- const count = 1
- for (const cell of cells) {
+ let flag = false
+ let index = 0
+ for (const cell of cells) {
    ...
    if (point?.x >= x + initialWidth - padding * 2 - (index < cells.length - 1 ? 50 - ellipsisWidth : 0)) {
      ...
    }
    index++

Also applies to: 88-94, 117-117

packages/nc-gui/components/smartsheet/grid/canvas/cells/LTAR/HasMany.ts (1)

77-79: ⚠️ Potential issue

Bug: Unused count variable and missing iteration logic.
Just like ManyToMany.ts, count is never incremented, but it’s used to decide if ellipses should be displayed. This will break overflow handling when multiple items exist.

- let flag = false
- const count = 1
- for (const cell of cells) {
+ let flag = false
+ let index = 0
+ for (const cell of cells) {
    ...
    if (point?.x >= x + initialWidth - padding * 2 - (index < cells.length - 1 ? 50 - ellipsisWidth : 0)) {
      ...
    }
    index++

Also applies to: 88-94, 121-121

packages/nc-gui/components/smartsheet/grid/canvas/cells/LongText.ts (1)

97-124: 🛠️ Refactor suggestion

Open new windows securely
When calling window.open(link.url, '_blank'), consider adding noopener or noreferrer to protect the app from reverse tabnabbing or external manipulations. For example:

-  window.open(link.url, '_blank')
+  window.open(link.url, '_blank', 'noopener,noreferrer')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  handleClick: async (props) => {
    const { column, getCellPosition, row, mousePosition, makeCellEditable, cellRenderStore } = props

    const isRichMode = column.columnObj?.meta?.richMode

    if (isRichMode) {
      const links: { x: number; y: number; width: number; height: number; url: string }[] = cellRenderStore?.links || []

      for (const link of links) {
        if (isBoxHovered(link, mousePosition)) {
-          window.open(link.url, '_blank')
+          window.open(link.url, '_blank', 'noopener,noreferrer')
          return true
        }
      }
    }

    if (isAIPromptCol(column?.columnObj)) {
      return AILongTextCellRenderer.handleClick!(props)
    } else {
      const { x, y, width } = getCellPosition(column, row.rowMeta.rowIndex!)

      if (isBoxHovered({ x: x + width - 28, y: y + 7, width: 18, height: 18 }, mousePosition)) {
        makeCellEditable(row.rowMeta.rowIndex!, column)
        return true
      }
      return false
    }
  },
packages/nc-gui/components/smartsheet/grid/canvas/loaders/ActionManager.ts (1)

65-101: 🛠️ Refactor suggestion

Optimize or evaluate performance overhead in animation loop
startAnimationLoop() calls requestAnimationFrame repeatedly while actions are loading, plus a cooldown. This approach may become expensive if many actions are triggered in quick succession. It might be worth implementing a more throttled or debounced rendering approach to avoid excessive UI updates.

packages/nc-gui/components/smartsheet/grid/canvas/loaders/ImageLoader.ts (2)

84-110: 🛠️ Refactor suggestion

Harmonize calling this.onSettled?.() with the loadImage flow.
generateQR calls onSettled each time a QR is done, whereas loadImage only calls onSettled when all images finish (pendingSprites === 0). Consider aligning these behaviors—if you only want a callback once all loads are complete, do so in both places consistently.


18-63: 🛠️ Refactor suggestion

Return a consistent placeholder or in-progress reference for multiple calls.
If a subsequent call to loadOrGetImage() arrives while the first is still loading, the function currently returns undefined. To improve usability, you could return the same promise or a placeholder so that multiple calls can share a single loading flow instead of proceeding blindly.

- if (isAnyLoading) {
-   return undefined
- }
+ if (isAnyLoading) {
+   // Return the same promise or a custom handle to represent an in-flight image
+   return undefined // or a reference to the existing loading promise
+ }

Committable suggestion skipped: line range outside the PR's diff.

packages/nc-gui/components/smartsheet/grid/canvas/cells/User.ts (1)

127-151: 🛠️ Refactor suggestion

Handle missing images gracefully.
When userIcon.iconType === IconType.IMAGE, if loading the image fails or img is not retrieved, a fallback is still needed. Consider an error callback or a placeholder icon if an image can’t be rendered.

packages/nc-gui/components/smartsheet/grid/canvas/composables/useKeyboardNavigation.ts (1)

57-72: 🛠️ Refactor suggestion

Evaluate breaking down handleKeyDown for maintainability.

This block begins a large conditional chain. While it’s logically correct, the length and nested checks suggest that splitting out distinct logic paths (e.g., handling open dropdowns, expansions, or modals) into helper functions would improve clarity and testability.

packages/nc-gui/components/smartsheet/grid/canvas/cells/index.ts (1)

39-53: 🛠️ Refactor suggestion

Extract typed definitions for the handler’s parameter object.

useGridCellHandler has a complex parameter object with multiple function references and typed data. Define an interface or type alias to clarify each field’s usage (e.g., SetCursorType) and to standardize the usage across the codebase.

packages/nc-gui/components/smartsheet/grid/canvas/context/Cell.vue (1)

101-117: ⚠️ Potential issue

Await async function calls for proper error handling.

deleteRow?.(row) is asynchronous, but it’s invoked in a synchronous try/catch. The error may never be caught if the promise rejects later. Use await inside an async function or handle the promise explicitly.

- deleteRow?.(row)
+ await deleteRow?.(row)

Committable suggestion skipped: line range outside the PR's diff.

packages/nc-gui/components/smartsheet/grid/canvas/composables/useCanvasTable.ts (1)

890-936: 🛠️ Refactor suggestion

Extract or streamline the makeCellEditable logic.

The logic has many conditional checks for read/write states, system columns, PK columns, etc., making it lengthy and harder to maintain. Consider extracting subroutines (e.g., PK checks, AI checks, system column checks) or using a helper composable to handle different column constraints more cleanly.

packages/nc-gui/components/smartsheet/grid/canvas/composables/useCanvasRender.ts (1)

797-1225: 🛠️ Refactor suggestion

Extract row drawing logic into smaller functions.

renderRows handles hovering, selection, row ordering, and partial row rendering. This monolithic approach can become difficult to maintain. Splitting out specialized helpers—e.g., one for drawing row backgrounds, one for rendering cells, and one for row metadata—would reduce complexity.

packages/nc-gui/components/smartsheet/grid/canvas/index.vue (1)

828-849: 🛠️ Refactor suggestion

Use structured approaches for new column creation logic.

Within handleMouseUp, there's a conditional block to open the “Add New Column” dropdown (ADD_NEW_COLUMN_WIDTH, etc.). Much of this logic is repeated in addEmptyColumn and openColumnCreate. It might be beneficial to unify them in a single composable or helper function to avoid potential maintenance overhead.

packages/nc-gui/components/smartsheet/grid/Table.vue (1)

5-5: ⚠️ Potential issue

Possible undefined reference to removed isReadonly logic

Line 443 in this file still references isReadonly in the colMeta computed property, but the removed isReadonly function is no longer imported. This can cause a runtime error and break the read-only logic for columns. Reintroduce or replace isReadonly logic to avoid undefined references and maintain the expected read-only behavior.

- import { UITypes, ViewTypes, isAIPromptCol, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
+ import { UITypes, ViewTypes, isAIPromptCol, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
// restore or re-implement isReadonly here, for example:
+ function isReadonly(col: ColumnType) {
+   // reintroduce or replicate the original read-only checks
+   return false // placeholder
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import { UITypes, ViewTypes, isAIPromptCol, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
// restore or re-implement isReadonly here, for example:
function isReadonly(col: ColumnType) {
  // reintroduce or replicate the original read-only checks
  return false // placeholder
}
packages/nc-gui/components/smartsheet/grid/canvas/cells/LTAR/index.ts (2)

21-26: ⚠️ Potential issue

Fix type inconsistency in handleClick method.

The handleClick method uses props.column.columnObj while the render method uses props.column directly. This inconsistency could lead to type errors.

Ensure consistent type usage:

   handleClick: async (props) => {
-    const cellRenderer = getLtarCellRenderer(props.column.columnObj)
+    const cellRenderer = getLtarCellRenderer(props.column)
     if (cellRenderer) {
-      return cellRenderer?.handleClick?.(props)
+      return cellRenderer.handleClick?.(props)
     }
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  handleClick: async (props) => {
    const cellRenderer = getLtarCellRenderer(props.column)
    if (cellRenderer) {
      return cellRenderer.handleClick?.(props)
    }
  },

1-6: ⚠️ Potential issue

Add missing type imports.

The file is missing imports for several types and helper functions:

  • CellRenderer type
  • Helper functions: isHm, isMm, isBt, isOo

Add the missing imports:

 import type { ColumnType } from 'nocodb-sdk'
+import type { CellRenderer } from '../types'
+import { isHm, isMm, isBt, isOo } from '../utils/helpers'
 import { BelongsToCellRenderer } from './BelongsTo'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import type { ColumnType } from 'nocodb-sdk'
import type { CellRenderer } from '../types'
import { isHm, isMm, isBt, isOo } from '../utils/helpers'
import { BelongsToCellRenderer } from './BelongsTo'
import { HasManyCellRenderer } from './HasMany'
import { ManyToManyCellRenderer } from './ManyToMany'
import { OneToOneCellRenderer } from './OneToOne'
packages/nc-gui/components/smartsheet/grid/canvas/loaders/TableMetaLoader.ts (1)

16-26: ⚠️ Potential issue

Fix incorrect return behavior and add error handling in getTableMeta.

The current implementation has several issues:

  1. Returns undefined when loading is in progress instead of waiting for the result
  2. Doesn't handle errors from getMeta
  3. Doesn't return the loaded metadata

Implement proper async handling:

   async getTableMeta(tableIdOrTitle: string): Promise<TableType | undefined> {
     if (this.loadingCache.get(tableIdOrTitle)) return
 
     this.loadingCache.set(tableIdOrTitle, tableIdOrTitle)
     try {
-      await this.getMeta(tableIdOrTitle)
+      const meta = await this.getMeta(tableIdOrTitle)
       this.onSettled?.()
+      return meta || undefined
+    } catch (error) {
+      console.error('Failed to load table metadata:', error)
+      return undefined
     } finally {
       this.loadingCache.delete(tableIdOrTitle)
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  async getTableMeta(tableIdOrTitle: string): Promise<TableType | undefined> {
    if (this.loadingCache.get(tableIdOrTitle)) return

    this.loadingCache.set(tableIdOrTitle, tableIdOrTitle)
    try {
      const meta = await this.getMeta(tableIdOrTitle)
      this.onSettled?.()
      return meta || undefined
    } catch (error) {
      console.error('Failed to load table metadata:', error)
      return undefined
    } finally {
      this.loadingCache.delete(tableIdOrTitle)
    }
  }
packages/nc-gui/components/smartsheet/grid/canvas/context/Aggregation.vue (2)

16-17: ⚠️ Potential issue

Fix potential null reference in computed properties.

The computed properties don't handle the case when column.value is null.

Add null checks:

-const gridCol = computed(() => gridViewCols.value[column.value.id])
-const aggregations = computed(() => getAggregations(column.value.columnObj))
+const gridCol = computed(() => column.value ? gridViewCols.value[column.value.id] : undefined)
+const aggregations = computed(() => column.value ? getAggregations(column.value.columnObj) : [])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const gridCol = computed(() => column.value ? gridViewCols.value[column.value.id] : undefined)
const aggregations = computed(() => column.value ? getAggregations(column.value.columnObj) : [])

1-4: ⚠️ Potential issue

Add missing type imports and definitions.

The component is missing several type imports and has potential type safety issues.

Add the required types:

 <script setup lang="ts">
+import type { CanvasGridColumn } from '../types'
+import type { AggregationType } from 'nocodb-sdk'
 const props = defineProps<{
   column: CanvasGridColumn | null
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

<script setup lang="ts">
import type { CanvasGridColumn } from '../types'
import type { AggregationType } from 'nocodb-sdk'
const props = defineProps<{
  column: CanvasGridColumn | null
}>()
packages/nc-gui/components/smartsheet/grid/canvas/cells/SingleLineText.ts (2)

1-6: ⚠️ Potential issue

Add missing type imports and interfaces.

The file is missing necessary type definitions.

Add required types:

+import type { CellRenderer, RenderProps, KeyboardEventContext } from '../types'
 import { renderMultiLineText, renderTagLabel } from '../utils/canvas'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

import type { CellRenderer, RenderProps, KeyboardEventContext } from '../types'
import { renderMultiLineText, renderTagLabel } from '../utils/canvas'

export const SingleLineTextCellRenderer: CellRenderer = {
  render: (ctx, props) => {
    const { value, x, y, width, height, pv, padding, textColor = '#4a5268' } = props
    // ... rest of the implementation
  }
}

35-47: 🛠️ Refactor suggestion

Add input validation in handleKeyDown.

The keyboard event handler should validate the input more thoroughly.

Add input validation:

-  async handleKeyDown(ctx) {
+  async handleKeyDown(ctx: KeyboardEventContext) {
     const { e, row, column, makeCellEditable } = ctx
     if (column.readonly) return
     const columnObj = column.columnObj
 
-    if (e.key.length === 1 && columnObj.title) {
+    // Validate that the key is printable and not a control character
+    if (e.key.length === 1 && /^[\x20-\x7E]$/.test(e.key) && columnObj.title) {
       row.row[columnObj.title] = ''
       makeCellEditable(row, column)
       return true
     }
 
     return false
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  async handleKeyDown(ctx: KeyboardEventContext) {
    const { e, row, column, makeCellEditable } = ctx
    if (column.readonly) return
    const columnObj = column.columnObj
 
    // Validate that the key is printable and not a control character
    if (e.key.length === 1 && /^[\x20-\x7E]$/.test(e.key) && columnObj.title) {
      row.row[columnObj.title] = ''
      makeCellEditable(row, column)
      return true
    }
 
    return false
  },
packages/nc-gui/components/smartsheet/grid/canvas/cells/Currency.ts (1)

7-35: 🛠️ Refactor suggestion

Optimize value validation and remove redundant checks.

The code has multiple redundant value checks.

Simplify the validation:

-    if (!isValidValue(value) || isNaN(value)) {
+    if (!isValidValue(value) || isNaN(value) || !value) {
       return {
         x,
         y,
       }
     }
-
     const currencyMeta = {
       currency_locale: 'en-US',
       currency_code: 'USD',
       ...(parseProp(column?.meta) || {}),
     }
-
     let formattedValue = ''
     try {
       formattedValue = new Intl.NumberFormat(currencyMeta.currency_locale || 'en-US', {
         style: 'currency',
         currency: currencyMeta.currency_code || 'USD',
       }).format(value)
     } catch (e) {
       formattedValue = value.toString()
     }
-
-    if (!value) {
-      return {
-        x,
-        y,
-      }
-    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (!isValidValue(value) || isNaN(value) || !value) {
      return {
        x,
        y,
      }
    }

    const currencyMeta = {
      currency_locale: 'en-US',
      currency_code: 'USD',
      ...(parseProp(column?.meta) || {}),
    }

    let formattedValue = ''
    try {
      formattedValue = new Intl.NumberFormat(currencyMeta.currency_locale || 'en-US', {
        style: 'currency',
        currency: currencyMeta.currency_code || 'USD',
      }).format(value)
    } catch (e) {
      formattedValue = value.toString()
    }
packages/nc-gui/components/smartsheet/grid/canvas/cells/Decimal.ts (1)

11-20: 💡 Verification agent

🧩 Analysis chain

Consider adding validation for extreme decimal values.

The number validation and formatting logic should handle edge cases like Infinity, -Infinity, and very large/small decimals that might affect precision.


🌐 Web query:

What is the maximum safe decimal precision in JavaScript?

💡 Result:

JavaScript's maximum safe decimal precision is approximately 15 to 17 significant digits due to its use of double-precision floating-point numbers (IEEE 754 standard). Here’s a breakdown:

Key Points

  1. Mantissa Precision:

    • JavaScript stores numbers in a 64-bit binary format with:
      • 1 sign bit
      • 11 exponent bits
      • 52 mantissa bits (for fractional precision)[8][9].
    • The mantissa provides ~15–17 significant decimal digits of precision[8][9]. For example:
      console.log(0.123456789012345); // Accurate (15 digits)
      console.log(0.12345678901234567); // Rounded (17+ digits)
  2. Safe Integer Range:

    • Integers between -(2^53 - 1) (-9007199254740991) and 2^53 - 1 (9007199254740991) are "safe" (Number.MAX_SAFE_INTEGER)[1][4][6]. Beyond this range, integers lose precision:
      console.log(9007199254740992 === 9007199254740993); // true[1][4]
  3. Decimal Limitations:

    • Decimals with more than 16–17 significant digits (integer + fractional parts) lose accuracy[2][5][8]:
      console.log(0.1 + 0.2); // 0.30000000000000004[5]
      console.log(99999999999.0123 + 0.00231432423); // Rounded[5]
  4. Workarounds:

    • Use BigInt for integers beyond MAX_SAFE_INTEGER[7].
    • For high-precision decimals, libraries like decimal.js or big.js are recommended[5].

Summary

JavaScript’s number type reliably handles up to 15–17 significant digits for both integers and decimals due to its binary storage limitations. Beyond this range, rounding errors occur, necessitating specialized libraries for precise calculations[5][8][9].

Citations:


Action Required: Add Finite-Value Validation for Extreme Decimals

The current logic only checks for non-null and non-NaN values, meaning edge cases like Infinity, -Infinity, or numbers with precision beyond JavaScript’s ~15–17 significant digits aren’t explicitly handled. Consider updating the validation to use Number.isFinite(Number(value)) (or an equivalent check) to ensure that only finite numbers are processed, and potentially add guard clauses for values exceeding the safe precision range.

  • File: packages/nc-gui/components/smartsheet/grid/canvas/cells/Decimal.ts
  • Location: Lines 11–20
  • Action: Modify the validation logic to catch extreme values (e.g., by replacing or supplementing the current !isNaN(Number(value)) check with Number.isFinite(Number(value))).
packages/nc-gui/components/cell/PhoneNumber/Editor.vue (1)

43-50: ⚠️ Potential issue

Prevent potential data loss during unmount.

The validation in onBeforeUnmount could lead to data loss if the phone number is invalid. Consider validating before unmount but preserving the invalid value.

onBeforeUnmount(() => {
-  if (parseProp(column.value.meta)?.validate && localState.value && !isMobilePhone(localState.value)) {
+  if (parseProp(column.value.meta)?.validate && localState.value) {
+    if (!isMobilePhone(localState.value)) {
      message.error(t('msg.invalidPhoneNumber'))
-     localState.value = undefined
      return
+    }
   }
   localState.value = props.modelValue
})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

onBeforeUnmount(() => {
  if (parseProp(column.value.meta)?.validate && localState.value) {
    if (!isMobilePhone(localState.value)) {
      message.error(t('msg.invalidPhoneNumber'))
      return
    }
  }
  localState.value = props.modelValue
})
packages/nc-gui/components/smartsheet/grid/canvas/cells/Email.ts (1)

75-78: ⚠️ Potential issue

Add security measures for opening email links.

Using window.open directly with user input could pose security risks. Consider sanitizing the email address and adding noopener for security.

-      window.open(`mailto:${text}`, '_blank')
+      const sanitizedEmail = encodeURIComponent(text.trim())

10000
+      window.open(`mailto:${sanitizedEmail}`, '_blank', 'noopener')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (isBoxHovered({ x, y, width: xOffset - x, height: yOffset - y }, mousePosition)) {
      const sanitizedEmail = encodeURIComponent(text.trim())
      window.open(`mailto:${sanitizedEmail}`, '_blank', 'noopener')
      return true
    }
packages/nc-gui/components/smartsheet/grid/canvas/cells/PhoneNumber.ts (1)

75-78: ⚠️ Potential issue

Add security measures for opening phone links.

Similar to email links, phone numbers should be sanitized before opening.

-      window.open(`tel:${text}`, '_blank')
+      const sanitizedPhone = encodeURIComponent(text.replace(/\s+/g, ''))
+      window.open(`tel:${sanitizedPhone}`, '_blank', 'noopener')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (isBoxHovered({ x, y, width: xOffset - x, height: yOffset - y }, mousePosition)) {
      const sanitizedPhone = encodeURIComponent(text.replace(/\s+/g, ''))
      window.open(`tel:${sanitizedPhone}`, '_blank', 'noopener')
      return true
    }
packages/nc-gui/components/smartsheet/grid/canvas/cells/Links.ts (1)

21-42: 🛠️ Refactor suggestion

Cache text measurements for better performance.

Text measurements are performed twice. Consider caching the measurements to avoid redundant calculations.

+      const textMeasurements = renderSingleLineText(ctx, {
+        x: x + padding,
+        y,
+        text,
+        maxWidth: width - padding * 2 - 20,
+        fontFamily: '500 13px Manrope',
+        fillStyle: 'rgb(67, 81, 232)',
+        height,
+      })

-      const { y: textYOffset, width: textWidth } = renderSingleLineText(ctx, {...})
+      const { y: textYOffset, width: textWidth } = textMeasurements

       const isHoverOverText = isBoxHovered({...}, mousePosition)

-      const { x: xOffset, y: yOffset } = renderSingleLineText(ctx, {...})
+      const { x: xOffset, y: yOffset } = renderSingleLineText(ctx, {
+        ...textMeasurements,
+        underline: isHoverOverText,
+      })

Committable suggestion skipped: line range outside the PR's diff.

packages/nc-gui/components/smartsheet/grid/canvas/cells/GeoData.ts (1)

10-12: 🛠️ Refactor suggestion

Add input validation for geographical coordinates.

The latitude/longitude parsing lacks validation. Invalid coordinate values could pass through silently.

-    const [latitude, longitude] = (value || '').split(';')
-    const isLocationSet = !!(latitude && longitude)
+    const [latitude, longitude] = (value || '').split(';')
+    const isValidCoordinate = (coord: string) => {
+      const num = parseFloat(coord)
+      return !isNaN(num) && isFinite(num)
+    }
+    const isLocationSet = !!(latitude && longitude && isValidCoordinate(latitude) && isValidCoordinate(longitude))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const [latitude, longitude] = (value || '').split(';')
    const isValidCoordinate = (coord: string) => {
      const num = parseFloat(coord)
      return !isNaN(num) && isFinite(num)
    }
    const isLocationSet = !!(latitude && longitude && isValidCoordinate(latitude) && isValidCoordinate(longitude))
    const displayText = isLocationSet ? `${latitude}; ${longitude}` : 'Set location'
packages/nc-gui/components/smartsheet/grid/canvas/cells/SingleSelect.ts (1)

53-74: 🛠️ Refactor suggestion

Optimize canvas context creation in handleHover.

Creating a new canvas context on every hover is inefficient. Consider reusing a shared context.

+const sharedContext = document.createElement('canvas').getContext('2d')
+
 async handleHover({ getCellPosition, column, row, value, mousePosition }) {
     const { x, y, width } = getCellPosition(column, row.rowMeta.rowIndex!)
     const { tryShowTooltip, hideTooltip } = useTooltipStore()
     hideTooltip()
     const padding = 10
     const maxWidth = width - padding * 2 - tagPadding * 2
-    const ctx = document.createElement('canvas').getContext('2d')
-    if (!ctx) return
-    ctx.font = `${column.pv ? 600 : 500} 13px Manrope`
+    if (!sharedContext) return
+    sharedContext.font = `${column.pv ? 600 : 500} 13px Manrope`

Committable suggestion skipped: line range outside the PR's diff.

packages/nc-gui/components/smartsheet/grid/canvas/cells/Time.ts (1)

59-70: 🛠️ Refactor suggestion

Optimize time parsing logic to reduce unnecessary operations.

The current implementation attempts multiple time parsing formats sequentially. This can be optimized by using a more targeted approach.

-      let time = dayjs(value)
-      if (!time.isValid()) {
-        time = dayjs(value, 'HH:mm:ss')
-      }
-      if (!time.isValid()) {
-        time = dayjs(`1999-01-01 ${value}`)
-      }
+      const formats = ['HH:mm:ss', 'HH:mm']
+      let time
+      for (const format of formats) {
+        time = dayjs(value, format)
+        if (time.isValid()) break
+      }
+      if (!time?.isValid()) {
+        time = dayjs(`1999-01-01 ${value}`)
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (value) {
      canvasContext.font = '500 13px Manrope'
-      let time = dayjs(value)
-      if (!time.isValid()) {
-        time = dayjs(value, 'HH:mm:ss')
-      }
-      if (!time.isValid()) {
-        time = dayjs(`1999-01-01 ${value}`)
-      }
+      const formats = ['HH:mm:ss', 'HH:mm']
+      let time
+      for (const format of formats) {
+        time = dayjs(value, format)
+        if (time.isValid()) break
+      }
+      if (!time?.isValid()) {
+        time = dayjs(`1999-01-01 ${value}`)
+      }
      if (time.isValid()) {
        text = time.format(timeFormat)
      }
    }
packages/nc-gui/components/smartsheet/grid/canvas/cells/Date.ts (1)

23-27: ⚠️ Potential issue

Add validation for date string format.

The date parsing logic assumes the input string is in a valid format. Consider adding explicit format validation to prevent invalid date displays.

- const date = dayjs(/^\d+$/.test(value) ? +value : value, defaultDateFormat)
+ const date = dayjs(
+   /^\d+$/.test(value) 
+     ? +value 
+     : (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}$/) 
+       ? value 
+       : null), 
+   defaultDateFormat
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      const date = dayjs(
        /^\d+$/.test(value) 
          ? +value 
          : (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}$/) 
            ? value 
            : null), 
        defaultDateFormat
      )
      if (date.isValid()) {
        formattedDate = date.format(dateFormat)
      }
    }
packages/nc-gui/components/smartsheet/grid/canvas/cells/Json.ts (1)

34-35: 🛠️ Refactor suggestion

Add JSON validation and formatting.

The JSON stringification is done without validation or formatting. Consider adding proper validation and formatting for better readability.

- const text = typeof value === 'string' ? value : JSON.stringify(value)
+ const text = typeof value === 'string' 
+   ? (isValidJson(value) ? JSON.stringify(JSON.parse(value), null, 2) : value)
+   : JSON.stringify(value, null, 2)

+ function isValidJson(str: string): boolean {
+   try {
+     JSON.parse(str)
+     return true
+   } catch (e) {
+     return false
+   }
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const text = typeof value === 'string' 
      ? (isValidJson(value) ? JSON.stringify(JSON.parse(value), null, 2) : value)
      : JSON.stringify(value, null, 2)

    function isValidJson(str: string): boolean {
      try {
        JSON.parse(str)
        return true
      } catch (e) {
        return false
      }
    }
packages/nc-gui/components/cell/Decimal/Editor.vue (1)

72-76: 🛠️ Refactor suggestion

Add error handling for focus operations.

The focus operation might fail in certain browser conditions. Consider adding error handling.

  onMounted(() => {
    if (isCanvasInjected && !isExpandedFormOpen.value && !isEditColumn.value && !isForm.value) {
-     inputRef.value?.focus()
+     try {
+       inputRef.value?.focus()
+     } catch (e) {
+       console.warn('Failed to focus input:', e)
+     }
    }
  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

onMounted(() => {
  if (isCanvasInjected && !isExpandedFormOpen.value && !isEditColumn.value && !isForm.value) {
    try {
      inputRef.value?.focus()
    } catch (e) {
      console.warn('Failed to focus input:', e)
    }
  }
})
packages/nc-gui/components/smartsheet/grid/canvas/cells/LTAR/BelongsTo.ts (1)

112-128: 🛠️ Refactor suggestion

Add error handling to click handler.

The click handler should include error handling for edge cases where cellRenderStore or rowIndex might be undefined.

 async handleClick({ row, column, getCellPosition, mousePosition, makeCellEditable, cellRenderStore, selected }) {
+  if (!row?.rowMeta?.rowIndex) {
+    console.warn('Invalid row data in BelongsToCellRenderer click handler')
+    return false
+  }
+
   const rowIndex = row.rowMeta.rowIndex!
   const { x, y, width } = getCellPosition(column, rowIndex)
   const size = 14
   
   try {
     if (isBoxHovered({ x: x + width - 26, y: y + 8, height: size, width: size }, mousePosition)) {
       makeCellEditable(rowIndex, column)
       return true
     }
 
     if (!cellRenderStore?.x || !selected) return false
 
     if (isBoxHovered({ x: cellRenderStore.x + 2, y: y + 8, height: size, width: size }, mousePosition)) {
       makeCellEditable(rowIndex, column)
       return true
     }
     return false
+  } catch (error) {
+    console.error('Error in BelongsToCellRenderer click handler:', error)
+    return false
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  async handleClick({ row, column, getCellPosition, mousePosition, makeCellEditable, cellRenderStore, selected }) {
    if (!row?.rowMeta?.rowIndex) {
      console.warn('Invalid row data in BelongsToCellRenderer click handler')
      return false
    }
    const rowIndex = row.rowMeta.rowIndex!
    const { x, y, width } = getCellPosition(column, rowIndex)
    const size = 14
    try {
      if (isBoxHovered({ x: x + width - 26, y: y + 8, height: size, width: size }, mousePosition)) {
        makeCellEditable(rowIndex, column)
        return true
      }
  
      if (!cellRenderStore?.x || !selected) return false
  
      if (isBoxHovered({ x: cellRenderStore.x + 2, y: y + 8, height: size, width: size }, mousePosition)) {
        makeCellEditable(rowIndex, column)
        return true
      }
      return false
    } catch (error) {
      console.error('Error in BelongsToCellRenderer click handler:', error)
      return false
    }
  },
packages/nc-gui/components/smartsheet/grid/canvas/cells/Url.ts (1)

17-17: ⚠️ Potential issue

Enhance URL validation and sanitization.

The current URL handling has potential security issues:

  1. URL validation could be more robust
  2. URLs are opened without proper sanitization
+const sanitizeUrl = (url: string): string => {
+  try {
+    const parsed = new URL(url)
+    return ['http:', 'https:'].includes(parsed.protocol) ? url : ''
+  } catch {
+    return ''
+  }
+}
+
-const isValid = text && isValidURL(text)
+const isValid = text && sanitizeUrl(text)
 
 // In handleClick:
-window.open(/^https?:\/\//.test(text) ? text : `https://${text}`, '_blank')
+const sanitized = sanitizeUrl(/^https?:\/\//.test(text) ? text : `https://${text}`)
+if (sanitized) {
+  window.open(sanitized, '_blank', 'noopener,noreferrer')
+}

Also applies to: 133-133

packages/nc-gui/components/smartsheet/grid/canvas/cells/LTAR/OneToOne.ts (2)

54-77: 🛠️ Refactor suggestion

Improve error handling for cell rendering.

The code silently returns if returnData?.x is undefined, which might hide rendering issues.

Add error handling and logging:

 returnData = cellRenderer({
   ...props,
   value: cellValue,
   column: ooColumn,
   width: cellWidth,
   relatedColObj: undefined,
   relatedTableMeta: undefined,
   readonly: true,
   height: rowHeightInPx['1']!,
   padding: 10,
   textColor: themeV3Colors.brand['500'],
   tag: {
     renderAsTag: true,
     tagBgColor: themeV3Colors.brand['50'],
     tagHeight: 24,
   },
   meta: relatedTableMeta,
   x: x + 4,
   y: y + (rowHeightInPx['1'] === height ? 0 : 2),
 })

 Object.assign(cellRenderStore, returnData)

-if (!returnData?.x) return
+if (!returnData?.x) {
+  console.warn('Cell rendering failed: returnData.x is undefined', { cellValue, ooColumn })
+  return
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      returnData = cellRenderer({
        ...props,
        value: cellValue,
        column: ooColumn,
        width: cellWidth,
        relatedColObj: undefined,
        relatedTableMeta: undefined,
        readonly: true,
        height: rowHeightInPx['1']!,
        padding: 10,
        textColor: themeV3Colors.brand['500'],
        tag: {
          renderAsTag: true,
          tagBgColor: themeV3Colors.brand['50'],
          tagHeight: 24,
        },
        meta: relatedTableMeta,
        x: x + 4,
        y: y + (rowHeightInPx['1'] === height ? 0 : 2),
      })

      Object.assign(cellRenderStore, returnData)

      if (!returnData?.x) {
        console.warn('Cell rendering failed: returnData.x is undefined', { cellValue, ooColumn })
        return
      }

40-47: ⚠️ Potential issue

Add type checking for value object.

The code assumes value is an object with specific properties but lacks type checking, which could lead to runtime errors.

Add type checking before accessing object properties:

 if (isValidValue(value)) {
   const cellWidth = width - (isBoxHovered({ x, y, width, height }, mousePosition) ? (hasValue ? 16 : 14) : 0)

   const cellValue =
-    value && !Array.isArray(value) && typeof value === 'object'
+    value && !Array.isArray(value) && typeof value === 'object' && (relatedTableDisplayValueProp in value || relatedTableDisplayValuePropId in value)
       ? value[relatedTableDisplayValueProp] ?? value[relatedTableDisplayValuePropId]
       : value
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (isValidValue(value)) {
      const cellWidth = width - (isBoxHovered({ x, y, width, height }, mousePosition) ? (hasValue ? 16 : 14) : 0)

      const cellValue =
        value && !Array.isArray(value) && typeof value === 'object' && (relatedTableDisplayValueProp in value || relatedTableDisplayValuePropId in value)
          ? value[relatedTableDisplayValueProp] ?? value[relatedTableDisplayValuePropId]
          : value
packages/nc-gui/components/cell/Percent/index.vue (1)

57-62: 🛠️ Refactor suggestion

Improve type safety for column meta parsing.

The code assumes parseProp returns an object with specific properties but lacks type checking.

Add type checking and interface:

+interface ColumnMeta {
+  is_progress?: boolean
+  [key: string]: any
+}

 const percentMeta = computed(() => {
+  const meta = parseProp(column.value?.meta) as ColumnMeta
   return {
-    ...parseProp(column.value?.meta),
-    is_progress: isUnderLookup ? false : parseProp(column.value?.meta).is_progress ?? false,
+    ...meta,
+    is_progress: isUnderLookup.value ? false : meta.is_progress ?? false,
   }
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

interface ColumnMeta {
  is_progress?: boolean
  [key: string]: any
}

const percentMeta = computed(() => {
  const meta = parseProp(column.value?.meta) as ColumnMeta
  return {
    ...meta,
    is_progress: isUnderLookup.value ? false : meta.is_progress ?? false,
  }
})
packages/nc-gui/components/cell/GeoData.vue (1)

52-72: 🛠️ Refactor suggestion

Consider adding error messages for geolocation failures.

The geolocation error handling only logs to console, which doesn't provide feedback to users when location access fails.

Add user-friendly error notifications:

 const onError: PositionErrorCallback = (err: GeolocationPositionError) => {
-  console.error(`ERROR(${err.code}): ${err.message}`)
+  const errorMessages = {
+    1: 'Location access denied. Please enable location services.',
+    2: 'Location unavailable. Please try again.',
+    3: 'Location request timed out. Please try again.'
+  }
+  message.error(errorMessages[err.code] || err.message)
   isLoading.value = false
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const  => {
  isLoading.value = true
  const onSuccess: PositionCallback = (position: GeolocationPosition) => {
    const crd = position.coords
    formState.latitude = `${crd.latitude}`
    formState.longitude = `${crd.longitude}`
    isLoading.value = false
  }

  const onError: PositionErrorCallback = (err: GeolocationPositionError) => {
    const errorMessages = {
      1: 'Location access denied. Please enable location services.',
      2: 'Location unavailable. Please try again.',
      3: 'Location request timed out. Please try again.'
    }
    message.error(errorMessages[err.code] || err.message)
    isLoading.value = false
  }

  const options = {
    enableHighAccuracy: true,
    timeout: 20000,
    maximumAge: 2000,
  }
  navigator.geolocation.getCurrentPosition(onSuccess, onError, options)
}
packages/nc-gui/components/smartsheet/Cell.vue (1)

91-98: 🛠️ Refactor suggestion

Consider adding cleanup for save timer.

The save timer should be cleaned up when the component is unmounted.

 onBeforeUnmount(() => {
   if (!isCanvasInjected) return
   if (currentRow.value.oldRow?.[column.value.title] === currentRow.value.row?.[column.value.title]) return
   currentRow.value.rowMeta.changed = false
   emitSave()
+  if (saveTimer) {
+    clearTimeout(saveTimer)
+  }
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const isCanvasInjected = inject(IsCanvasInjectionInj, false)

onBeforeUnmount(() => {
  if (!isCanvasInjected) return
  if (currentRow.value.oldRow?.[column.value.title] === currentRow.value.row?.[column.value.title]) return
  currentRow.value.rowMeta.changed = false
  emitSave()
  if (saveTimer) {
    clearTimeout(saveTimer)
  }
})

Copy link
Contributor
github-actions bot commented Feb 17, 2025

Uffizzi Preview deployment-61005 was deleted.

@dstala dstala merged commit 9bff98c into develop Feb 17, 2025
1 check passed
@dstala dstala deleted the nc-canvas-table branch February 17, 2025 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
0