name: DataGrid
purpose: Manage editable, virtualized data-API tables while preserving server field definitions.

attributes:
  editable:
    type: boolean
    behavior: Enable row, field, cell, and save controls.
  api:
    type: string
    behavior: POST endpoint for the data-table action protocol. Requires state.table.

state:
  table: Table name sent as request.name when api is configured.
  fields: Array of data-API field definitions; assign before list.
  list: Array of row objects; each key matches a field name.
  _originalList: Internal unfiltered row snapshot.
  sortConfig: "{ fieldId, direction }"
  filterConfig: Per-field filter configuration.
  selectedRowCount: Selected row count.
  isDirty: Whether edits are pending save.

slots:
  toolbar-start: Content rendered at the far left of the top toolbar, before the selection count and built-in actions.
  toolbar-end: Content rendered near the right edge of the top toolbar, before save status.
  footer-start: Compatibility alias rendered after the built-in actions in the top toolbar.
  footer-end: Compatibility alias rendered near the right edge of the top toolbar.

editing:
  formTypes: [text, number, select, checkbox, radio, switch, textarea, code, date, datetime, TagsInput, DatePicker, ColorPicker, IconPicker]
  rule: Double-click an editable cell, then click outside the editor or press Enter to save its bound value.
  field_settings: Field Name, Field Type, and type-specific options are shown first. Advanced mode exposes a friendly Database Type selector covering i/ui/bi/ubi/ti/v[N]/c[N]/t/o/dt/d/tm/f/ff/b/bb; Object fields use a larger JSON code editor for cell editing, and its outer editing surface can be resized from the bottom-right corner. VARCHAR and CHAR show an inline length input and still submit one encoded type string. It also exposes Index, Memo, AutoForm Control Attributes JSON, and Display Condition.
  row_form: Double-click the trailing tinted background of a row to open an AutoForm bound to that local row. Object fields are presented as pretty JSON code and parsed back to objects after valid edits. Previous/Next and Ctrl/Cmd+ArrowLeft/ArrowRight switch rows. Changes remain local until the grid Save action.

filtering:
  rule: Click a column header, enter a value in its menu, and the grid filters immediately. Use the menu controls for sort and reset.

field:
  required: [id, tableID, name, type, settings]
  fields:
    id: _Fields record ID; used for field metadata operations.
    tableID: Owning table ID.
    name: Database column name and row value key.
    type: Database storage type, for example v100, v30, ff, b, or dt. It is preserved when editing an unchanged UI type; changing settings.type assigns that UI type's default storage type.
    memo: Field description.
    settings: "Extensible field metadata. Built-ins are { type, label, options, attrs, if, width, pinned, decimals, prefix, suffix, thousandSep, labelOn, labelOff }. Applications may add custom keys. DataGrid interprets only documented built-ins; only attrs maps to AutoForm control attributes, so the complete settings object must never be expanded onto a native control. Omit absent attrs such as pattern instead of storing undefined."
    formatter: Function receiving value and field.

methods:
  addRow: Add an empty row.
  deleteSelectedRow: Delete selected rows.
  getSelectedRows: Return the unique data rows touched by the current cell selections, in row order.
  selectAllRows: Select every currently loaded row and visible field.
  clearSelection: Clear all selected cells and rows.
  getQuery: Return the current structured filter and sort payload used by an API-backed grid.
  saveChanges: Dispatch save.
  addField: Add a field.
  editField: Edit the active field.
  deleteField: Delete the active field.
  editCell: Open a cell editor.
  applySortFilter: Apply current or supplied sorting and filters.
  onScroll: Refresh the virtual row window after scrolling.
  openRowForm: Open the local row AutoForm by absolute row index.

events:
  save:
    detail: "{ list, fields }"
  savefields:
    detail: fields
  remove:
    detail: "{ items }"

api:
  request: "POST { action, name: state.table, ...payload }"
  actions:
    fields: "response { ok, fields }; loaded when table changes."
    query: "request { filter, sort, offset: 0, limit: 1000 }; response { ok, data, count } or { ok, list, count }. Filter items are { field, operator, value }; sort is ['field DESC']."
    setField: "request { fields: [field] }; called after adding or editing a field."
    removeField: "request { fields: [field] }; called after deleting a field."
    save: "request { data: list }; called by Save."
    remove: "request { data: ids }; called by Delete; ids contains stable record ID strings, not row objects."
  behavior: Sorting and filters build validated filter plus sort structures and reload from the server. Pending edits require confirmation before a reload.
  field_layout: Header drag reorder writes Field.priority; completed column resize writes settings.width. API-backed grids immediately call setField with complete field records, so these metadata-only operations do not make the row Save button dirty.
  row_order: A real data field named priority with database type ff becomes a 42px drag handle. It follows the same optional Pin Column setting as ordinary fields and is not pinned implicitly. Reordering is available only with no active user sort or filter. Normally only the moved row is saved with a midpoint priority; exhausted floating-point gaps re-space at most 21 nearby rows rather than the whole table.
  empty_table: When fields returns an empty array, DataGrid does not query the not-yet-created physical table and shows Add Field in the toolbar.

advanced_mode:
  behavior: The toolbar toggle appends read-only internal columns id, creator, createTime, updater, and updateTime. They are not added to fields or included in field save requests.

global:
  DataGrid:
    methods:
      registerFieldType: Register a field type configuration.
      getFieldTypes: Return registered field types.

related:
  - ../base/AutoForm.yaml
  - ../base/Modal.yaml
  - ../base/Dialog.yaml
  - ../../utilities/VirtualScroll.yaml
  - ../base/Resizer.yaml
  - ../form/DatePicker.yaml
  - ../form/ColorPicker.yaml
  - ../form/IconPicker.yaml
  - ../form/TagsInput.yaml

rules:
  - Bind fields and list with $.state.fields and $.state.list.
  - DataGrid uses field.name (not field.id) to read and update each row value.
  - DataGrid only supports data-API field definitions; use datatable for generic tabular display.
  - Async fields and list bindings may be undefined before an API response; DataGrid waits for arrays.
  - Give the DataGrid or its parent an explicit height so virtual scrolling has a viewport.
  - DataGrid contains its intrinsic inline size so wide columns scroll inside .dt-main instead of widening the host or its toolbar.
  - The top toolbar has a stable 48px height so host modules can align their primary toolbars consistently.
  - Treat underscore-prefixed state fields as internal read-only diagnostics.
  - The toolbar total selects every currently loaded row; when a selection exists, the adjacent clear button clears it.
  - Custom field editors must be registered in AutoForm before use.
  - The trailing row editor is the tinted row div background after the last real field, not a synthetic field or pseudo-element. Real cells paint their own background, so cell and row-background double-clicks remain distinct without an event-capturing overlay.

examples:
  local_and_api: |
    <script>
      const orderFields = [
        { id: 'f-name', tableID: 'orders', name: 'name', type: 'v100', isIndex: true, memo: '', settings: { type: 'text' } },
        { id: 'f-total', tableID: 'orders', name: 'total', type: 'ff', isIndex: false, memo: '', settings: { type: 'number', prefix: '$', decimals: 2 } },
        { id: 'f-status', tableID: 'orders', name: 'status', type: 'v30', isIndex: false, memo: '', settings: { type: 'select', options: ['draft', 'paid'] } }
      ]
      const orders = [{ name: 'Ada', total: 42, status: 'paid', paid: true, tags: ['priority'], due: '2026-07-12' }]
      const saveRows = rows => console.log(rows)
    </script>
    <div style="height: 480px">
      <DataGrid id="orders" editable $.state.fields="orderFields" $.state.list="orders" $onsave="saveRows(event.detail.list)"></DataGrid>
    </div>
    <DataGrid editable api="/admin/table" $.state.table="Hash.table"></DataGrid>

tests:
  - DataGrid.test.html
