@operato/data-grist 10.29.7 → 10.29.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"data-grid.js","sourceRoot":"","sources":["../../../src/data-grid/data-grid.ts"],"names":[],"mappings":"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","sourcesContent":["import i18next from 'i18next'\nimport '@material/web/button/text-button.js'\nimport '@operato/list-state/ox-empty-note.js'\nimport './data-grid-header.js'\nimport './data-grid-body.js'\nimport './data-grid-footer.js'\n\nimport { css, html, LitElement, PropertyValues, nothing} from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles.js'\n\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { DataManipulator } from '../data-manipulator.js'\nimport { ColumnConfig, GristRecord, PaginationConfig } from '../types.js'\nimport { GridEmptyNote, renderEmptyNote } from '../empty-note-render.js'\n// import { supportsPassive } from '../utils/index.js'\nimport { DataGridHeader } from './data-grid-header.js'\n\n/**\n * DataGrid\n */\n@customElement('ox-grid')\nexport class DataGrid extends DataManipulator {\n static styles = [\n MDTypeScaleStyles,\n ScrollbarStyles,\n css`\n /*\n * ── 고치는 표만 셀을 겨눈다 ─────────────────────────────────────────\n *\n * 셀 하나를 가리키는 점선과 열 폭 조절 손잡이는 **고칠 셀을 겨누는 장치**다. 기본 겉모습에서는\n * 둘을 두지 않는다(data-grist.ts 와 같은 값). 고칠 수 있는 열이 하나라도 있으면 여기서 되살린다.\n *\n * 화면이 고르는 모드가 아니다 — 표가 스스로 아는 사실이다(위 editableGrid 주석 참조).\n */\n :host([editable-grid]) {\n --grid-record-focused-cell-border: 1px dashed var(--ui-faint, #8a959c);\n /*\n * 정렬 표식의 자리는 이 값에서 **파생된다** — 손잡이가 보이면 표식이 칸 오른쪽 끝으로 가고,\n * 안 보이면 이름에 붙는다(data-grid-header.ts 의 스타일 질의). 여기서 따로 정하지 않는다.\n */\n --grid-header-splitter-display: flex;\n /*\n * ── 지나는 줄 표시를 **비우지 않는다** ──────────────────────────────\n *\n * 여기 hover 색을 transparent 로 두었었다. 「점선 셀 테두리가 이미 「지금 여기」를 말하므로\n * 줄 배경까지 움직일 필요가 없다」는 뜻이었는데, **그 값은 「칠하지 않는다」가 아니라\n * 「칸의 색을 지운다」였다.**\n *\n * 칸은 자기 바탕을 갖는다(흰색). 그것을 지우면 그 자리에 **구멍이 뚫려 표 뒤의 페이지가\n * 비친다.** 이 앱에서 그 색이 회색이라(rgb 240·242·244) 회색 띠처럼 보였고, 그래서\n * 「지나는 줄 표시」가 실제로는 있었다 — 아무도 정하지 않은 색으로.\n *\n * 그리고 **고정 열에서 그 띠가 끊긴다.** 고정 열은 스크롤되는 열 위에 떠 있어야 하니\n * 불투명해야 하고, 그래서 구멍이 안 뚫린다. 줄 하나가 두 색으로 갈렸다.\n *\n * 표 뒤에 무엇이 있느냐로 줄 색이 정해지는 것 자체가 결함이다. 어두운 페이지에 이 표를\n * 놓으면 마우스를 올린 줄이 검게 된다.\n *\n * 그래서 이 예외를 걷는다. 모든 표가 같은 hover 색을 쓰고, 그 색은 칸마다 실제로 칠하는\n * 색이므로 고정 열에도 똑같이 걸린다.\n */\n }\n\n :host {\n display: flex;\n flex-direction: column;\n\n /* div[setting] 의 절대 좌표 기준. */\n position: relative;\n overflow: hidden;\n\n border-width: var(--grid-container-border-width);\n }\n\n /*\n * ── 머리와 몸은 **한 스크롤러** 안에 있다 (2026-10-05) ──────────────────────\n *\n * 전에는 몸이 스크롤하고 머리는 따로 스크롤되는 상자였다. 몸의 scroll 이벤트를 받아 머리의\n * scrollLeft 를 옮겼고, 머리 쪽에서도 거꾸로 몸을 옮겼다. 두 가지가 어긋났다.\n *\n * 이격 몸은 컴포지터가 곧바로 옮기고, 머리는 메인 스레드가 이벤트를 받은 다음에야 따라간다.\n * 메인 스레드가 바쁜 화면일수록 머리가 몇 프레임씩 뒤처졌다\n * 흔들림 양방향이라 늦게 도착한 머리의 이벤트가 몸을 **한 프레임 전 자리로 되돌렸다**\n * 끝 두 상자의 스크롤 폭이 다르면(칸이 흘러 몸만 넓어지면) 오른쪽 끝에서 머리가 멈췄다\n *\n * 이제 role=grid 인 이 상자 하나가 두 방향을 다 스크롤한다. 머리는 위에 sticky 로 붙고, 몸은\n * 스크롤하지 않고 내용만큼 넓어진다. 같은 상자 안에 있으므로 맞추는 코드가 없고, 어긋날 수도 없다.\n */\n div[grid] {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n\n overflow: auto;\n overscroll-behavior: none;\n /* 세로 스크롤바가 생기는 순간 열이 움찔하지 않게 자리를 늘 비워 둔다. */\n scrollbar-gutter: stable;\n }\n\n /*\n * 머리와 몸은 **열 폭의 합만큼** 넓다(width: max-content). 고정 열의 sticky 는 자기를 담은\n * 상자 안에서만 움직이므로, 상자가 화면 폭에 머물면 그 너머로 스크롤했을 때 고정 열이 따라\n * 밀려난다. 합이 화면보다 좁을 때는 화면을 채운다(min-width: 100%) — 마지막 auto 열이 남는\n * 자리를 채운다.\n */\n ox-grid-body {\n flex: 1 0 auto;\n width: max-content;\n min-width: 100%;\n box-sizing: border-box;\n position: relative;\n /* 몸 안의 층(고정 열 2 · 선택 상자 5 · 입력 버퍼 10)을 몸 안에 가둔다. 머리는 그 위에 있다. */\n z-index: 0;\n\n font-variation-settings: 'FILL' 1;\n }\n\n ox-grid-header {\n flex: none;\n width: max-content;\n min-width: 100%;\n box-sizing: border-box;\n position: sticky;\n top: 0;\n z-index: 1;\n\n font-variation-settings: 'FILL' 1;\n }\n\n ox-grid-footer {\n font-variation-settings: 'FILL' 1;\n }\n\n ox-empty-note {\n display: block;\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n }\n\n /*\n * 컬럼 설정 버튼은 **헤더에 마우스를 올릴 때만** 보인다.\n *\n * 늘 보이면 목록 화면마다 오른쪽 위에 눈에 걸리는 것이 하나 생긴다. 자주 쓰는 것도 아니다.\n *\n * 감춰 있는 동안에는 pointer-events 도 끈다 — 안 보이는데 눌리는 자리가 있으면, 헤더의 마지막\n * 컬럼을 누르려던 사람이 엉뚱한 팝업을 만난다.\n *\n * 세 갈래로 켠다.\n * 헤더에 올렸을 때 :has(ox-grid-header:hover)\n * 버튼 자체에 올렸을 때 버튼이 헤더 위에 떠 있으므로, 커서가 버튼으로 옮겨오면 헤더는 더 이상\n * hover 가 아니다. 이 갈래가 없으면 커서 아래에서 사라진다\n * 키보드 초점 focus-within\n */\n div[setting] {\n /*\n * 크기 없이 호스트의 오른쪽 위에 붙는다. 안에 들어오는 버튼이 [slot='setting'] 규칙으로\n * top: -3px · right: 0 을 스스로 잡으므로, 이 div 는 기준점 노릇만 한다.\n */\n position: absolute;\n top: 0;\n right: 0;\n width: 0;\n height: 0;\n z-index: 5;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.12s ease-out;\n }\n\n /*\n * 버튼 자체에 올렸을 때도 켜 둔다. 버튼이 헤더 위에 떠 있어서, 커서가 버튼으로 옮겨오면 헤더는\n * 더 이상 hover 가 아니다. 이 갈래가 없으면 커서 아래에서 사라진다.\n */\n div[grid]:has(ox-grid-header:hover) + div[setting],\n div[setting]:hover,\n div[setting]:focus-within {\n opacity: 1;\n pointer-events: auto;\n }\n\n /*\n * 손가락으로 쓰는 장치 — hover 가 없으므로 감추면 닿을 방법이 없다. 늘 두고 평소에는 흐리게,\n * 누르는 동안 선명하게 한다.\n *\n * 화면 폭이 아니라 입력 장치로 구분한다. 창을 좁힌 데스크톱은 여기에 걸리지 않고, 큰 태블릿은\n * 폭이 넓어도 걸린다.\n */\n @media (pointer: coarse) {\n div[setting] {\n opacity: 0.35;\n pointer-events: auto;\n }\n\n div[setting]:active,\n div[setting]:focus-within {\n opacity: 1;\n }\n }\n\n @media print {\n :host {\n zoom: 80%;\n }\n }\n `\n ]\n\n @property({ type: Object }) focused?: { row: number; column: number }\n @property({ type: Boolean }) empty?: boolean\n\n /** 비었을 때 무슨 말을 하나 — 판단은 data-grist 가 하고 여기서는 그리기만 한다. */\n @property({ type: Object }) emptyNote?: GridEmptyNote\n\n\n\n /**\n * 이 표에 고칠 수 있는 열이 하나라도 있는지. **겉모습이 이것으로 달라진다.**\n *\n * 셀 하나를 가리키는 점선과 열 폭 조절 손잡이는 **고칠 셀을 겨누는 장치**다. 고치지 않는 목록에서는\n * 셀 하나가 뜻을 갖지 않아, 줄을 고르려고 누른 자리에 점선이 남고 세로 선이 줄 읽기를 방해한다.\n *\n * 그래서 모드를 따로 두지 않는다. 화면이 고를 일이 아니라 **표가 스스로 아는 사실**이다 —\n * 열에 `record.editable` 이 있으면 고치는 표다.\n */\n @property({ type: Boolean, reflect: true, attribute: 'editable-grid' }) editableGrid: boolean = false\n\n @query('div[grid]', true) scroller!: HTMLElement\n @query('ox-grid-body', true) body!: LitElement\n @query('ox-grid-header', true) header!: DataGridHeader\n @query('ox-grid-footer', true) footer!: LitElement\n\n @state() fixedLefts: number[] = []\n\n firstUpdated() {\n /*\n * 머리와 몸은 같은 스크롤러 안에 있으므로 맞출 것이 없다(위 div[grid] 주석). 남는 것은 고정 열의\n * 그림자 하나다 — 옆으로 밀렸을 때만 고정 열이 떠 보이게 한다. 값이 바뀔 때만 손댄다.\n */\n let raised = false\n this.scroller.addEventListener(\n 'scroll',\n () => {\n const next = this.scroller.scrollLeft > 0\n if (next !== raised) {\n raised = next\n this.header.toggleAttribute('raised', raised)\n this.body.toggleAttribute('raised', raised)\n }\n },\n { passive: true }\n )\n\n this.header.addEventListener('wheel', this.onWheelEvent.bind(this))\n this.footer && this.footer.addEventListener('wheel', this.onWheelEvent.bind(this))\n\n /* record selection processing */\n this.addEventListener('select-all-change', e => {\n if (this.data.records.length == 0) {\n return\n }\n\n var { selected } = (e as CustomEvent).detail\n var { records } = this.data\n\n records.forEach(record => (record['__selected__'] = selected))\n\n this.header.requestUpdate()\n this.body.requestUpdate()\n })\n\n this.addEventListener('focus-change', e => {\n const { row, column } = (e as CustomEvent).detail || {}\n if (!this.focused || this.focused.row != row || this.focused.column != column) {\n this.focused = { row, column }\n this.focus()\n this.requestUpdate()\n }\n })\n }\n\n /*\n * 머리 · 꼬리 위에서 굴린 **세로** 휠을 가로 스크롤로 바꾼다.\n *\n * 가로 휠(트랙패드의 옆 밀기)은 건드리지 않는다 — 머리도 스크롤러 안에 있으므로 브라우저가 컴포지터에서\n * 그대로 옮긴다. 손을 대면 그 부드러운 길을 메인 스레드로 끌어내리게 된다.\n */\n onWheelEvent(e: WheelEvent) {\n if (Math.abs(e.deltaX) >= Math.abs(e.deltaY)) {\n return\n }\n\n const scroller = this.scroller\n if (scroller.scrollWidth > scroller.clientWidth) {\n var delta = Math.max(-1, Math.min(1, e.deltaY || 0))\n scroller.scrollLeft = Math.max(0, scroller.scrollLeft - delta * 30)\n\n var maxScrollLeft = scroller.scrollWidth - scroller.clientWidth\n\n var atStart = scroller.scrollLeft === 0\n var atEnd = scroller.scrollLeft >= maxScrollLeft - 1\n\n // 스크롤이 맨 앞으로 와 있는 상태에서 휠을 올리는 경우 또는\n // 스크롤이 맨 끝으로 가 있는 상태에서 휠을 내리는 경우에만 디폴트 동작 허용\n if ((atStart && delta > 0) || (atEnd && delta < 0)) {\n return true\n }\n\n e.preventDefault()\n }\n }\n\n onSelectRecordChanged({\n selectedRecords,\n added,\n removed\n }: {\n selectedRecords: GristRecord[]\n added: GristRecord[]\n removed: GristRecord[]\n }): void {\n super.onSelectRecordChanged({ selectedRecords, added, removed })\n\n this.header.requestUpdate()\n this.body.requestUpdate()\n }\n\n onRecordChanged(\n recordData: GristRecord,\n row: number,\n column: ColumnConfig | null /* TODO column should be removed */\n ) {\n /* record-viewer 등 grid 외에서 변경되는 경우에도 header와 body를 refresh 하도록 한다. */\n super.onRecordChanged(recordData, row, column)\n\n this.header.requestUpdate()\n this.body.requestUpdate()\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('data')) {\n /*\n * 데이타 내용에 따라 동적으로 컬럼의 폭이 달라지는 경우(예를 들면, sequence field)가 있으므로,\n * data의 변동에 대해서도 다시 계산되어야 한다.\n */\n this.calculateWidths(this.config.columns)\n\n if (this.focused && 'row' in this.focused) {\n var { row = 0, column = 0 } = this.focused\n var { records: oldrecords = [] } = changes.get('data') || {}\n var { records: newrecords } = this.data\n\n var oldrecord = oldrecords[row]\n\n if (oldrecord && oldrecord !== newrecords[row]) {\n var newrecord = newrecords.find(\n record =>\n /* TODO keyfields를 정의하고, keyfields의 동일성으로 확정해야 한다. */\n oldrecord === record ||\n ('id' in oldrecord\n ? record.id == oldrecord.id\n : 'name' in oldrecord\n ? record.name == oldrecord.name\n : false)\n )\n\n this.focused = {\n row: newrecord ? newrecords.indexOf(newrecord) : row,\n column\n }\n }\n }\n } else if (changes.has('config')) {\n this.calculateWidths(this.config.columns)\n }\n\n /* grid fetch 후에 grid로 focus를 주어야 하는 지 검토가 필요하다. */\n // this.focus()\n }\n\n calculateWidths(columns: ColumnConfig[]) {\n /*\n * 컬럼 모델 마지막에 'auto' cell을 추가하여, 자투리 영역을 꽉 채워서 표시한다.\n *\n * 숫자로 준 폭은 **그 폭 그대로** 쓴다. 합이 보이는 폭보다 크면 표는 가로로 넘치고, 나머지는\n * 가로 스크롤로 본다 — 표의 정상 거동이다.\n *\n * 한때 minmax(0, Npx) 로 두어 넘칠 때 열들이 함께 줄게 했다가 되돌렸다. 그렇게 하면 어떤 표도\n * 가로로 넘치지 않으므로 **가로 스크롤이 아예 사라진다.** 열 열다섯 개를 700px 에 밀어 넣으면\n * 모든 열이 못 읽을 만큼 좁아지고, 화면은 그것을 알릴 방법이 없다.\n *\n * 열이 좁아 잘리는 것은 화면이 열 폭을 정해서 풀 일이다 — 부품이 대신 줄이면 화면의 뜻이 사라진다.\n */\n const widths = columns\n .filter(column => !column.hidden)\n .map(column => {\n switch (typeof column.width) {\n case 'number':\n return `${column.width}px`\n case 'string':\n return column.width\n case 'function':\n return column.width.call(this, column)\n default:\n return 'auto'\n }\n })\n .concat(['auto'])\n .join(' ')\n\n /* 겉모습이 이 사실로 달라진다(위 editableGrid 주석 참조). 열이 정해지는 이 시점이 그 자리다. */\n this.editableGrid = columns.some(column => !column.hidden && !column.gutterName && !!column.record?.editable)\n\n this.style.setProperty('--grid-template-columns', widths)\n /* 인쇄는 종이 폭에 맞춰 비율로 나눈다. `220px` → `220fr` 로 바뀐다. */\n this.style.setProperty('--grid-template-print-columns', widths.replace(/px/gi, 'fr'))\n }\n\n /**\n * The grid's name, set by the caller as `<ox-grid aria-label=\"Inbound queue\">`.\n *\n * role=grid sits on the inner wrapper, and an aria-label on the host does not reach it, so the\n * value is taken and forwarded. No name is invented here: with several grids on one screen, which\n * one this is belongs to the screen, not to the component.\n */\n @property({ type: String, attribute: 'aria-label' }) gridLabel?: string\n\n render() {\n var { pagination = {} as PaginationConfig, columns = [], filters } = this.config\n\n var paginatable = !pagination.infinite\n var data = this.data\n\n /*\n * Declare the grid a grid.\n *\n * This component carried no roles at all — one aria- attribute in the entire source, on the\n * page number. To a screen reader the screen was a pile of inputs. Naming each cell by hand\n * still would not say \"this is a table\", \"row 3, column 3\", or \"40 rows in all\", and scanning\n * and comparing is the whole point of a table; without roles that navigation does not exist.\n *\n * With role=grid the column header becomes the cell's name without anything being written on\n * the cell. That is how an HTML table works, and why the pattern is specified that way.\n *\n * The extra wrapper is needed because a grid may only contain rows (or row groups). This\n * element also holds the settings-button slot and the pager, so putting the role on the host\n * mixes non-rows into the table and assistive tech drops out of table reading. Wrapping just\n * the header and body keeps them out. display: contents generates no box, so the host's column\n * flex layout is unchanged.\n */\n const headerRows = columns.some(column => !column.hidden && column.header?.group) ? 2 : 1\n const visibleColumns = columns.filter(column => !column.hidden).length\n const total = typeof data.total == 'number' && data.total > 0 ? data.total : data.records?.length || 0\n const page = Number(data.page) > 0 ? Number(data.page) : 1\n const limit = Number(data.limit) > 0 ? Number(data.limit) : data.records?.length || 0\n\n return html`\n <div\n grid\n role=\"grid\"\n aria-label=${ifDefined(this.gridLabel)}\n aria-rowcount=${headerRows + total}\n aria-colcount=${visibleColumns}\n >\n <ox-grid-header\n role=\"rowgroup\"\n class=\"md-typescale-label-medium-prominent\"\n .columns=${columns}\n .sorters=${this.sorters}\n .filters=${this.filters}\n .data=${data}\n ?filtering-feature=${filters?.header}\n @column-width-change=${(e: CustomEvent) => {\n let { idx, width } = e.detail\n columns[idx].width = width\n this.calculateWidths(columns)\n }}\n @fixed-lefts-change=${(e: CustomEvent) => (this.fixedLefts = e.detail)}\n >\n </ox-grid-header>\n\n <ox-grid-body\n role=\"rowgroup\"\n class=\"md-typescale-body-medium\"\n .config=${this.config}\n .columns=${columns}\n .data=${data}\n .focused=${this.focused!}\n .fixedLefts=${this.fixedLefts}\n .ariaRowIndexBase=${headerRows + (page - 1) * limit + 1}\n >\n ${this.empty ? renderEmptyNote(this.emptyNote) : html``}\n </ox-grid-body>\n </div>\n\n <!--\n Placed after the grid. \"Only while hovering the header\" needs a sibling selector, and a\n sibling selector can only reach forward.\n\n :host(:has(ox-grid-header:hover)) does not work — the argument of :host() is evaluated in\n the outer tree, so it never sees ox-grid-header inside the shadow tree. That was tried and\n did not react to hover. The :has() on the grid wrapper is evaluated inside the shadow tree,\n so it does.\n\n The settings button operates on the grid rather than being part of it, so it stays outside\n role=grid: a grid may only contain rows, and anything else makes assistive tech drop out of\n table reading.\n\n Reordering does not move it on screen — the style below pins it to the host's top right.\n -->\n <div setting>\n <slot name=\"setting\"></slot>\n </div>\n\n ${paginatable\n ? html`\n <!--\n 푸터에는 본문 타이프스케일을 씌우지 않는다.\n\n 그 클래스는 font 축약형으로 0.875rem 를 넣는데, **바깥 트리의 규칙이라 푸터의\n :host { font-size } 를 이긴다**(그림자 캐스케이드에서 호스트 요소는 바깥 트리 규칙이\n 우선한다). 그래서 푸터가 자기 크기를 못 정하고 본문과 같은 14px 로 남아 있었다.\n [확인함] 리터럴 :host { font-size: 7px } 도 안 먹었다. 2026-09-02\n\n 푸터는 본문이 아니다. 크기는 --grid-footer-font-size 가 정한다.\n -->\n <ox-grid-footer\n .data=${data}\n .pagination=${this.pagination}\n ></ox-grid-footer>\n `\n : html``}\n `\n }\n\n focus() {\n super.focus()\n\n this.body.focus()\n }\n\n get pullToRefreshTarget() {\n return this.scroller\n }\n}\n"]}
1
+ {"version":3,"file":"data-grid.js","sourceRoot":"","sources":["../../../src/data-grid/data-grid.ts"],"names":[],"mappings":"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","sourcesContent":["import i18next from 'i18next'\nimport '@material/web/button/text-button.js'\nimport '@operato/list-state/ox-empty-note.js'\nimport './data-grid-header.js'\nimport './data-grid-body.js'\nimport './data-grid-footer.js'\n\nimport { css, html, LitElement, PropertyValues, nothing} from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles.js'\n\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { DataManipulator } from '../data-manipulator.js'\nimport { ColumnConfig, GristRecord, PaginationConfig } from '../types.js'\nimport { GridEmptyNote, renderEmptyNote } from '../empty-note-render.js'\n// import { supportsPassive } from '../utils/index.js'\nimport { DataGridHeader } from './data-grid-header.js'\n\n/**\n * DataGrid\n */\n@customElement('ox-grid')\nexport class DataGrid extends DataManipulator {\n static styles = [\n MDTypeScaleStyles,\n ScrollbarStyles,\n css`\n /*\n * ── 고치는 표만 셀을 겨눈다 ─────────────────────────────────────────\n *\n * 셀 하나를 가리키는 점선과 열 폭 조절 손잡이는 **고칠 셀을 겨누는 장치**다. 기본 겉모습에서는\n * 둘을 두지 않는다(data-grist.ts 와 같은 값). 고칠 수 있는 열이 하나라도 있으면 여기서 되살린다.\n *\n * 화면이 고르는 모드가 아니다 — 표가 스스로 아는 사실이다(위 editableGrid 주석 참조).\n */\n :host([editable-grid]) {\n --grid-record-focused-cell-border: 1px dashed var(--ui-faint, #8a959c);\n /*\n * 정렬 표식의 자리는 이 값에서 **파생된다** — 손잡이가 보이면 표식이 칸 오른쪽 끝으로 가고,\n * 안 보이면 이름에 붙는다(data-grid-header.ts 의 스타일 질의). 여기서 따로 정하지 않는다.\n */\n --grid-header-splitter-display: flex;\n /*\n * ── 지나는 줄 표시를 **비우지 않는다** ──────────────────────────────\n *\n * 여기 hover 색을 transparent 로 두었었다. 「점선 셀 테두리가 이미 「지금 여기」를 말하므로\n * 줄 배경까지 움직일 필요가 없다」는 뜻이었는데, **그 값은 「칠하지 않는다」가 아니라\n * 「칸의 색을 지운다」였다.**\n *\n * 칸은 자기 바탕을 갖는다(흰색). 그것을 지우면 그 자리에 **구멍이 뚫려 표 뒤의 페이지가\n * 비친다.** 이 앱에서 그 색이 회색이라(rgb 240·242·244) 회색 띠처럼 보였고, 그래서\n * 「지나는 줄 표시」가 실제로는 있었다 — 아무도 정하지 않은 색으로.\n *\n * 그리고 **고정 열에서 그 띠가 끊긴다.** 고정 열은 스크롤되는 열 위에 떠 있어야 하니\n * 불투명해야 하고, 그래서 구멍이 안 뚫린다. 줄 하나가 두 색으로 갈렸다.\n *\n * 표 뒤에 무엇이 있느냐로 줄 색이 정해지는 것 자체가 결함이다. 어두운 페이지에 이 표를\n * 놓으면 마우스를 올린 줄이 검게 된다.\n *\n * 그래서 이 예외를 걷는다. 모든 표가 같은 hover 색을 쓰고, 그 색은 칸마다 실제로 칠하는\n * 색이므로 고정 열에도 똑같이 걸린다.\n */\n }\n\n :host {\n display: flex;\n flex-direction: column;\n\n /* div[setting] 의 절대 좌표 기준. */\n position: relative;\n overflow: hidden;\n\n border-width: var(--grid-container-border-width);\n }\n\n /*\n * ── 머리와 몸은 **한 스크롤러** 안에 있다 (2026-10-05) ──────────────────────\n *\n * 전에는 몸이 스크롤하고 머리는 따로 스크롤되는 상자였다. 몸의 scroll 이벤트를 받아 머리의\n * scrollLeft 를 옮겼고, 머리 쪽에서도 거꾸로 몸을 옮겼다. 두 가지가 어긋났다.\n *\n * 이격 몸은 컴포지터가 곧바로 옮기고, 머리는 메인 스레드가 이벤트를 받은 다음에야 따라간다.\n * 메인 스레드가 바쁜 화면일수록 머리가 몇 프레임씩 뒤처졌다\n * 흔들림 양방향이라 늦게 도착한 머리의 이벤트가 몸을 **한 프레임 전 자리로 되돌렸다**\n * 끝 두 상자의 스크롤 폭이 다르면(칸이 흘러 몸만 넓어지면) 오른쪽 끝에서 머리가 멈췄다\n *\n * 이제 role=grid 인 이 상자 하나가 두 방향을 다 스크롤한다. 머리는 위에 sticky 로 붙고, 몸은\n * 스크롤하지 않고 내용만큼 넓어진다. 같은 상자 안에 있으므로 맞추는 코드가 없고, 어긋날 수도 없다.\n */\n div[grid] {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n\n overflow: auto;\n overscroll-behavior: none;\n /* 세로 스크롤바가 생기는 순간 열이 움찔하지 않게 자리를 늘 비워 둔다. */\n scrollbar-gutter: stable;\n }\n\n /*\n * 머리와 몸은 **열 폭의 합만큼** 넓다(width: max-content). 고정 열의 sticky 는 자기를 담은\n * 상자 안에서만 움직이므로, 상자가 화면 폭에 머물면 그 너머로 스크롤했을 때 고정 열이 따라\n * 밀려난다. 합이 화면보다 좁을 때는 화면을 채운다(min-width: 100%) — 마지막 auto 열이 남는\n * 자리를 채운다.\n */\n ox-grid-body {\n flex: 1 0 auto;\n width: max-content;\n min-width: 100%;\n box-sizing: border-box;\n position: relative;\n /* 몸 안의 층(고정 열 2 · 선택 상자 5 · 입력 버퍼 10)을 몸 안에 가둔다. 머리는 그 위에 있다. */\n z-index: 0;\n\n font-variation-settings: 'FILL' 1;\n }\n\n ox-grid-header {\n flex: none;\n width: max-content;\n min-width: 100%;\n box-sizing: border-box;\n position: sticky;\n top: 0;\n z-index: 1;\n\n font-variation-settings: 'FILL' 1;\n }\n\n ox-grid-footer {\n font-variation-settings: 'FILL' 1;\n }\n\n ox-empty-note {\n display: block;\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n }\n\n /*\n * 컬럼 설정 버튼은 **헤더에 마우스를 올릴 때만** 보인다.\n *\n * 늘 보이면 목록 화면마다 오른쪽 위에 눈에 걸리는 것이 하나 생긴다. 자주 쓰는 것도 아니다.\n *\n * 감춰 있는 동안에는 pointer-events 도 끈다 — 안 보이는데 눌리는 자리가 있으면, 헤더의 마지막\n * 컬럼을 누르려던 사람이 엉뚱한 팝업을 만난다.\n *\n * 세 갈래로 켠다.\n * 헤더에 올렸을 때 :has(ox-grid-header:hover)\n * 버튼 자체에 올렸을 때 버튼이 헤더 위에 떠 있으므로, 커서가 버튼으로 옮겨오면 헤더는 더 이상\n * hover 가 아니다. 이 갈래가 없으면 커서 아래에서 사라진다\n * 키보드 초점 focus-within\n */\n div[setting] {\n /*\n * 크기 없이 호스트의 오른쪽 위에 붙는다. 안에 들어오는 버튼이 [slot='setting'] 규칙으로\n * top: -3px · right: 0 을 스스로 잡으므로, 이 div 는 기준점 노릇만 한다.\n */\n position: absolute;\n top: 0;\n right: 0;\n width: 0;\n height: 0;\n z-index: 5;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.12s ease-out;\n }\n\n /*\n * 버튼 자체에 올렸을 때도 켜 둔다. 버튼이 헤더 위에 떠 있어서, 커서가 버튼으로 옮겨오면 헤더는\n * 더 이상 hover 가 아니다. 이 갈래가 없으면 커서 아래에서 사라진다.\n */\n div[grid]:has(ox-grid-header:hover) + div[setting],\n div[setting]:hover,\n div[setting]:focus-within {\n opacity: 1;\n pointer-events: auto;\n }\n\n /*\n * 손가락으로 쓰는 장치 — hover 가 없으므로 감추면 닿을 방법이 없다. 늘 두고 평소에는 흐리게,\n * 누르는 동안 선명하게 한다.\n *\n * 화면 폭이 아니라 입력 장치로 구분한다. 창을 좁힌 데스크톱은 여기에 걸리지 않고, 큰 태블릿은\n * 폭이 넓어도 걸린다.\n */\n @media (pointer: coarse) {\n div[setting] {\n opacity: 0.35;\n pointer-events: auto;\n }\n\n div[setting]:active,\n div[setting]:focus-within {\n opacity: 1;\n }\n }\n\n @media print {\n :host {\n zoom: 80%;\n }\n }\n `\n ]\n\n @property({ type: Object }) focused?: { row: number; column: number }\n @property({ type: Boolean }) empty?: boolean\n\n /** 비었을 때 무슨 말을 하나 — 판단은 data-grist 가 하고 여기서는 그리기만 한다. */\n @property({ type: Object }) emptyNote?: GridEmptyNote\n\n\n\n /**\n * 이 표에 고칠 수 있는 열이 하나라도 있는지. **겉모습이 이것으로 달라진다.**\n *\n * 셀 하나를 가리키는 점선과 열 폭 조절 손잡이는 **고칠 셀을 겨누는 장치**다. 고치지 않는 목록에서는\n * 셀 하나가 뜻을 갖지 않아, 줄을 고르려고 누른 자리에 점선이 남고 세로 선이 줄 읽기를 방해한다.\n *\n * 그래서 모드를 따로 두지 않는다. 화면이 고를 일이 아니라 **표가 스스로 아는 사실**이다 —\n * 열에 `record.editable` 이 있으면 고치는 표다.\n */\n @property({ type: Boolean, reflect: true, attribute: 'editable-grid' }) editableGrid: boolean = false\n\n @query('div[grid]', true) scroller!: HTMLElement\n @query('ox-grid-body', true) body!: LitElement\n @query('ox-grid-header', true) header!: DataGridHeader\n @query('ox-grid-footer', true) footer!: LitElement\n\n @state() fixedLefts: number[] = []\n\n firstUpdated() {\n /*\n * 머리와 몸은 같은 스크롤러 안에 있으므로 맞출 것이 없다(위 div[grid] 주석). 남는 것은 고정 열의\n * 그림자 하나다 — 옆으로 밀렸을 때만 고정 열이 떠 보이게 한다. 값이 바뀔 때만 손댄다.\n */\n let raised = false\n this.scroller.addEventListener(\n 'scroll',\n () => {\n const next = this.scroller.scrollLeft > 0\n if (next !== raised) {\n raised = next\n this.header.toggleAttribute('raised', raised)\n this.body.toggleAttribute('raised', raised)\n }\n },\n { passive: true }\n )\n\n this.header.addEventListener('wheel', this.onWheelEvent.bind(this))\n this.footer && this.footer.addEventListener('wheel', this.onWheelEvent.bind(this))\n\n /* record selection processing */\n this.addEventListener('select-all-change', e => {\n if (this.data.records.length == 0) {\n return\n }\n\n var { selected } = (e as CustomEvent).detail\n var { records } = this.data\n\n records.forEach(record => (record['__selected__'] = selected))\n\n this.header.requestUpdate()\n this.body.requestUpdate()\n })\n\n this.addEventListener('focus-change', e => {\n const { row, column } = (e as CustomEvent).detail || {}\n if (!this.focused || this.focused.row != row || this.focused.column != column) {\n this.focused = { row, column }\n this.focus()\n this.requestUpdate()\n }\n })\n }\n\n /*\n * 머리 · 꼬리 위에서 굴린 **세로** 휠을 가로 스크롤로 바꾼다.\n *\n * 가로 휠(트랙패드의 옆 밀기)은 건드리지 않는다 — 머리도 스크롤러 안에 있으므로 브라우저가 컴포지터에서\n * 그대로 옮긴다. 손을 대면 그 부드러운 길을 메인 스레드로 끌어내리게 된다.\n */\n onWheelEvent(e: WheelEvent) {\n if (Math.abs(e.deltaX) >= Math.abs(e.deltaY)) {\n return\n }\n\n const scroller = this.scroller\n if (scroller.scrollWidth > scroller.clientWidth) {\n var delta = Math.max(-1, Math.min(1, e.deltaY || 0))\n scroller.scrollLeft = Math.max(0, scroller.scrollLeft - delta * 30)\n\n var maxScrollLeft = scroller.scrollWidth - scroller.clientWidth\n\n var atStart = scroller.scrollLeft === 0\n var atEnd = scroller.scrollLeft >= maxScrollLeft - 1\n\n // 스크롤이 맨 앞으로 와 있는 상태에서 휠을 올리는 경우 또는\n // 스크롤이 맨 끝으로 가 있는 상태에서 휠을 내리는 경우에만 디폴트 동작 허용\n if ((atStart && delta > 0) || (atEnd && delta < 0)) {\n return true\n }\n\n e.preventDefault()\n }\n }\n\n onSelectRecordChanged({\n selectedRecords,\n added,\n removed\n }: {\n selectedRecords: GristRecord[]\n added: GristRecord[]\n removed: GristRecord[]\n }): void {\n super.onSelectRecordChanged({ selectedRecords, added, removed })\n\n this.header.requestUpdate()\n this.body.requestUpdate()\n }\n\n onRecordChanged(\n recordData: GristRecord,\n row: number,\n column: ColumnConfig | null /* TODO column should be removed */\n ) {\n /* record-viewer 등 grid 외에서 변경되는 경우에도 header와 body를 refresh 하도록 한다. */\n super.onRecordChanged(recordData, row, column)\n\n this.header.requestUpdate()\n this.body.requestUpdate()\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('data')) {\n /*\n * 데이타 내용에 따라 동적으로 컬럼의 폭이 달라지는 경우(예를 들면, sequence field)가 있으므로,\n * data의 변동에 대해서도 다시 계산되어야 한다.\n */\n this.calculateWidths(this.config.columns)\n\n if (this.focused && 'row' in this.focused) {\n var { row = 0, column = 0 } = this.focused\n var { records: oldrecords = [] } = changes.get('data') || {}\n var { records: newrecords } = this.data\n\n var oldrecord = oldrecords[row]\n\n if (oldrecord && oldrecord !== newrecords[row]) {\n var newrecord = newrecords.find(\n record =>\n /* TODO keyfields를 정의하고, keyfields의 동일성으로 확정해야 한다. */\n oldrecord === record ||\n ('id' in oldrecord\n ? record.id == oldrecord.id\n : 'name' in oldrecord\n ? record.name == oldrecord.name\n : false)\n )\n\n this.focused = {\n row: newrecord ? newrecords.indexOf(newrecord) : row,\n column\n }\n }\n }\n } else if (changes.has('config')) {\n this.calculateWidths(this.config.columns)\n }\n\n /* grid fetch 후에 grid로 focus를 주어야 하는 지 검토가 필요하다. */\n // this.focus()\n }\n\n calculateWidths(columns: ColumnConfig[]) {\n /*\n * 컬럼 모델 마지막에 'auto' cell을 추가하여, 자투리 영역을 꽉 채워서 표시한다.\n *\n * 숫자로 준 폭은 **그 폭 그대로** 쓴다. 합이 보이는 폭보다 크면 표는 가로로 넘치고, 나머지는\n * 가로 스크롤로 본다 — 표의 정상 거동이다.\n *\n * 한때 minmax(0, Npx) 로 두어 넘칠 때 열들이 함께 줄게 했다가 되돌렸다. 그렇게 하면 어떤 표도\n * 가로로 넘치지 않으므로 **가로 스크롤이 아예 사라진다.** 열 열다섯 개를 700px 에 밀어 넣으면\n * 모든 열이 못 읽을 만큼 좁아지고, 화면은 그것을 알릴 방법이 없다.\n *\n * 열이 좁아 잘리는 것은 화면이 열 폭을 정해서 풀 일이다 — 부품이 대신 줄이면 화면의 뜻이 사라진다.\n */\n const widths = columns\n .filter(column => !column.hidden)\n .map(column => {\n switch (typeof column.width) {\n case 'number':\n return `${column.width}px`\n case 'string':\n return column.width\n case 'function':\n return column.width.call(this, column)\n default:\n return 'auto'\n }\n })\n .concat(['auto'])\n .join(' ')\n\n /* 겉모습이 이 사실로 달라진다(위 editableGrid 주석 참조). 열이 정해지는 이 시점이 그 자리다. */\n this.editableGrid = columns.some(column => !column.hidden && !column.gutterName && !!column.record?.editable)\n\n this.style.setProperty('--grid-template-columns', widths)\n /* 인쇄는 종이 폭에 맞춰 비율로 나눈다. `220px` → `220fr` 로 바뀐다. */\n this.style.setProperty('--grid-template-print-columns', widths.replace(/px/gi, 'fr'))\n }\n\n /**\n * The grid's name, set by the caller as `<ox-grid aria-label=\"Inbound queue\">`.\n *\n * role=grid sits on the inner wrapper, and an aria-label on the host does not reach it, so the\n * value is taken and forwarded. No name is invented here: with several grids on one screen, which\n * one this is belongs to the screen, not to the component.\n */\n @property({ type: String, attribute: 'aria-label' }) gridLabel?: string\n\n render() {\n var { pagination = {} as PaginationConfig, columns = [], filters } = this.config\n\n var paginatable = !pagination.infinite\n var data = this.data\n\n /*\n * Declare the grid a grid.\n *\n * This component carried no roles at all — one aria- attribute in the entire source, on the\n * page number. To a screen reader the screen was a pile of inputs. Naming each cell by hand\n * still would not say \"this is a table\", \"row 3, column 3\", or \"40 rows in all\", and scanning\n * and comparing is the whole point of a table; without roles that navigation does not exist.\n *\n * With role=grid the column header becomes the cell's name without anything being written on\n * the cell. That is how an HTML table works, and why the pattern is specified that way.\n *\n * The extra wrapper is needed because a grid may only contain rows (or row groups). This\n * element also holds the settings-button slot and the pager, so putting the role on the host\n * mixes non-rows into the table and assistive tech drops out of table reading. Wrapping just\n * the header and body keeps them out. display: contents generates no box, so the host's column\n * flex layout is unchanged.\n */\n const headerRows = columns.some(column => !column.hidden && column.header?.group) ? 2 : 1\n const visibleColumns = columns.filter(column => !column.hidden).length\n const total = typeof data.total == 'number' && data.total > 0 ? data.total : data.records?.length || 0\n const page = Number(data.page) > 0 ? Number(data.page) : 1\n const limit = Number(data.limit) > 0 ? Number(data.limit) : data.records?.length || 0\n\n return html`\n <div\n grid\n role=\"grid\"\n aria-label=${ifDefined(this.gridLabel)}\n aria-rowcount=${headerRows + total}\n aria-colcount=${visibleColumns}\n >\n <ox-grid-header\n role=\"rowgroup\"\n class=\"md-typescale-label-medium-prominent\"\n .columns=${columns}\n .sorters=${this.sorters}\n .filters=${this.filters}\n .data=${data}\n ?filtering-feature=${filters?.header}\n @column-width-change=${(e: CustomEvent) => {\n let { idx, width } = e.detail\n columns[idx].width = width\n this.calculateWidths(columns)\n }}\n @fixed-lefts-change=${(e: CustomEvent) => (this.fixedLefts = e.detail)}\n >\n </ox-grid-header>\n\n <ox-grid-body\n role=\"rowgroup\"\n class=\"md-typescale-body-medium\"\n .config=${this.config}\n .columns=${columns}\n .data=${data}\n .focused=${this.focused!}\n .fixedLefts=${this.fixedLefts}\n .ariaRowIndexBase=${headerRows + (page - 1) * limit + 1}\n >\n ${this.empty ? renderEmptyNote(this.emptyNote) : html``}\n </ox-grid-body>\n </div>\n\n <!--\n Placed after the grid. \"Only while hovering the header\" needs a sibling selector, and a\n sibling selector can only reach forward.\n\n :host(:has(ox-grid-header:hover)) does not work — the argument of :host() is evaluated in\n the outer tree, so it never sees ox-grid-header inside the shadow tree. That was tried and\n did not react to hover. The :has() on the grid wrapper is evaluated inside the shadow tree,\n so it does.\n\n The settings button operates on the grid rather than being part of it, so it stays outside\n role=grid: a grid may only contain rows, and anything else makes assistive tech drop out of\n table reading.\n\n Reordering does not move it on screen — the style below pins it to the host's top right.\n -->\n <div setting>\n <slot name=\"setting\"></slot>\n </div>\n\n ${paginatable\n ? html`\n <!--\n 푸터에는 본문 타이프스케일을 씌우지 않는다.\n\n 그 클래스는 font 축약형으로 0.875rem 를 넣는데, **바깥 트리의 규칙이라 푸터의\n :host { font-size } 를 이긴다**(그림자 캐스케이드에서 호스트 요소는 바깥 트리 규칙이\n 우선한다). 그래서 푸터가 자기 크기를 못 정하고 본문과 같은 14px 로 남아 있었다.\n [확인함] 리터럴 :host { font-size: 7px } 도 안 먹었다. 2026-09-02\n\n 푸터는 본문이 아니다. 크기는 --grid-footer-font-size 가 정한다.\n -->\n <ox-grid-footer\n .data=${data}\n .pagination=${this.pagination}\n ></ox-grid-footer>\n `\n : html``}\n `\n }\n\n focus() {\n /* 초점 칸을 보이게 하는 스크롤은 몸이 직접 한다 — data-grid-body.ts focus() 주석. */\n super.focus({ preventScroll: true })\n\n this.body.focus()\n }\n\n get pullToRefreshTarget() {\n return this.scroller\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ import '../src/data-grist.js';
@@ -0,0 +1,77 @@
1
+ /* Elements are compared by presence, not by value: a failing element comparison makes chai render the DOM and hang the runner. */
2
+ import { expect, fixture } from '@open-wc/testing';
3
+ import { resetMouse, sendMouse } from '@web/test-runner-commands';
4
+ import { html } from 'lit';
5
+ import '../src/data-grist.js';
6
+ const settle = (ms = 150) => new Promise(resolve => setTimeout(resolve, ms));
7
+ describe('the first click on a scrolled grid', () => {
8
+ const ignoreResizeLoop = (e) => {
9
+ if (String(e.message).includes('ResizeObserver loop')) {
10
+ e.stopImmediatePropagation();
11
+ e.preventDefault();
12
+ }
13
+ };
14
+ let previousOnError = null;
15
+ before(() => {
16
+ window.addEventListener('error', ignoreResizeLoop, true);
17
+ previousOnError = window.onerror;
18
+ window.onerror = (message, ...rest) => String(message).includes('ResizeObserver loop') ? true : previousOnError === null || previousOnError === void 0 ? void 0 : previousOnError.call(window, message, ...rest);
19
+ });
20
+ after(async function () {
21
+ /* resetMouse is a round trip to the browser driver; under a full parallel run it takes seconds. */
22
+ this.timeout(10000);
23
+ /* Let the resize that the last layout queued arrive while the filter is still in place. */
24
+ await settle(300);
25
+ window.removeEventListener('error', ignoreResizeLoop, true);
26
+ window.onerror = previousOnError;
27
+ await resetMouse();
28
+ });
29
+ it('does not move the grid when the clicked cell is already in view', async function () {
30
+ this.timeout(10000);
31
+ const columns = [
32
+ { type: 'gutter', gutterName: 'sequence' },
33
+ { type: 'string', name: 'id', hidden: true },
34
+ { type: 'string', name: 'code', header: 'code', width: 140, record: { editable: true } }
35
+ ];
36
+ for (let i = 0; i < 30; i++) {
37
+ columns.push({ type: 'string', name: 'c' + i, header: 'col ' + i, width: 120, record: { editable: true } });
38
+ }
39
+ const records = Array.from({ length: 30 }, (_, r) => {
40
+ const record = { id: String(r), code: 'C-' + r };
41
+ for (let i = 0; i < 30; i++)
42
+ record['c' + i] = `v${r}-${i}`;
43
+ return record;
44
+ });
45
+ const el = (await fixture(html `
46
+ <ox-grist
47
+ style="display: flex; width: 700px; height: 360px"
48
+ mode="GRID"
49
+ .config=${{ columns, pagination: { pages: [100] } }}
50
+ .fetchHandler=${async () => ({ total: records.length, records })}
51
+ ></ox-grist>
52
+ `));
53
+ await el.fetch();
54
+ await settle();
55
+ const grist = el.renderRoot.querySelector('#grist');
56
+ const scroller = grist.renderRoot.querySelector('div[grid]');
57
+ const body = grist.renderRoot.querySelector('ox-grid-body');
58
+ await body.updateComplete;
59
+ scroller.scrollLeft = 1000;
60
+ await settle(50);
61
+ expect(scroller.scrollLeft, 'the grid must scroll, or this measures nothing').to.equal(1000);
62
+ /* A cell in the middle of the view, in the third row. */
63
+ const row = body.renderRoot.querySelectorAll('div[grid-row]')[2];
64
+ const view = scroller.getBoundingClientRect();
65
+ const cell = Array.from(row.querySelectorAll('ox-grid-field')).find(f => {
66
+ const r = f.getBoundingClientRect();
67
+ return r.left > view.left + 200 && r.right < view.right - 200;
68
+ });
69
+ const before = cell.getBoundingClientRect();
70
+ await sendMouse({ type: 'click', position: [Math.round(before.left + 20), Math.round(before.top + before.height / 2)] });
71
+ await settle();
72
+ expect(cell.hasAttribute('focused'), 'the clicked cell takes the focus').to.equal(true);
73
+ expect(scroller.scrollLeft, 'the grid moved under the pointer').to.equal(1000);
74
+ expect(Math.round(cell.getBoundingClientRect().left)).to.equal(Math.round(before.left));
75
+ });
76
+ });
77
+ //# sourceMappingURL=first-click-stays.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"first-click-stays.test.js","sourceRoot":"","sources":["../../test/first-click-stays.test.ts"],"names":[],"mappings":"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","sourcesContent":["/* Elements are compared by presence, not by value: a failing element comparison makes chai render the DOM and hang the runner. */\nimport { expect, fixture } from '@open-wc/testing'\nimport { resetMouse, sendMouse } from '@web/test-runner-commands'\nimport { html } from 'lit'\n\nimport '../src/data-grist.js'\n\n/**\n * The first cell you click stays where you clicked it.\n *\n * Clicking a cell sets the new focus and calls focus() at once, before the body has drawn that\n * focus. The input buffer that takes the focus was still where it had never been placed (top left),\n * and the browser scrolled the grid to show it. Then the grid scrolled the focused cell back into\n * view — at the far edge. A cell clicked in the middle of a grid pushed 1000px to the right ended up\n * at the right edge (0, then 581). From the second click on the buffer sat near the last cell, so it\n * only showed the first time.\n *\n * The grid scrolls its own focus into view, so focusing the buffer must not scroll.\n */\n\ntype Root = HTMLElement & { renderRoot: ShadowRoot; updateComplete: Promise<unknown> }\n\nconst settle = (ms = 150) => new Promise(resolve => setTimeout(resolve, ms))\n\ndescribe('the first click on a scrolled grid', () => {\n const ignoreResizeLoop = (e: ErrorEvent) => {\n if (String(e.message).includes('ResizeObserver loop')) {\n e.stopImmediatePropagation()\n e.preventDefault()\n }\n }\n let previousOnError: OnErrorEventHandler = null\n before(() => {\n window.addEventListener('error', ignoreResizeLoop, true)\n previousOnError = window.onerror\n window.onerror = (message, ...rest) =>\n String(message).includes('ResizeObserver loop') ? true : previousOnError?.call(window, message, ...rest)\n })\n after(async function () {\n /* resetMouse is a round trip to the browser driver; under a full parallel run it takes seconds. */\n this.timeout(10000)\n /* Let the resize that the last layout queued arrive while the filter is still in place. */\n await settle(300)\n window.removeEventListener('error', ignoreResizeLoop, true)\n window.onerror = previousOnError\n await resetMouse()\n })\n\n it('does not move the grid when the clicked cell is already in view', async function () {\n this.timeout(10000)\n const columns: any[] = [\n { type: 'gutter', gutterName: 'sequence' },\n { type: 'string', name: 'id', hidden: true },\n { type: 'string', name: 'code', header: 'code', width: 140, record: { editable: true } }\n ]\n for (let i = 0; i < 30; i++) {\n columns.push({ type: 'string', name: 'c' + i, header: 'col ' + i, width: 120, record: { editable: true } })\n }\n const records = Array.from({ length: 30 }, (_, r) => {\n const record: any = { id: String(r), code: 'C-' + r }\n for (let i = 0; i < 30; i++) record['c' + i] = `v${r}-${i}`\n return record\n })\n\n const el = (await fixture(html`\n <ox-grist\n style=\"display: flex; width: 700px; height: 360px\"\n mode=\"GRID\"\n .config=${{ columns, pagination: { pages: [100] } }}\n .fetchHandler=${async () => ({ total: records.length, records })}\n ></ox-grist>\n `)) as Root & { fetch(): Promise<void> }\n await el.fetch()\n await settle()\n\n const grist = el.renderRoot.querySelector('#grist') as Root\n const scroller = grist.renderRoot.querySelector('div[grid]') as HTMLElement\n const body = grist.renderRoot.querySelector('ox-grid-body') as Root\n await body.updateComplete\n\n scroller.scrollLeft = 1000\n await settle(50)\n expect(scroller.scrollLeft, 'the grid must scroll, or this measures nothing').to.equal(1000)\n\n /* A cell in the middle of the view, in the third row. */\n const row = body.renderRoot.querySelectorAll('div[grid-row]')[2] as HTMLElement\n const view = scroller.getBoundingClientRect()\n const cell = (Array.from(row.querySelectorAll('ox-grid-field')) as HTMLElement[]).find(f => {\n const r = f.getBoundingClientRect()\n return r.left > view.left + 200 && r.right < view.right - 200\n })!\n const before = cell.getBoundingClientRect()\n\n await sendMouse({ type: 'click', position: [Math.round(before.left + 20), Math.round(before.top + before.height / 2)] })\n await settle()\n\n expect(cell.hasAttribute('focused'), 'the clicked cell takes the focus').to.equal(true)\n expect(scroller.scrollLeft, 'the grid moved under the pointer').to.equal(1000)\n expect(Math.round(cell.getBoundingClientRect().left)).to.equal(Math.round(before.left))\n })\n})\n"]}