@operato/data-grist 10.29.27 → 10.29.28

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/coverage/lcov-report/base.css +224 -0
  3. package/coverage/lcov-report/block-navigation.js +87 -0
  4. package/coverage/lcov-report/favicon.png +0 -0
  5. package/coverage/lcov-report/index.html +896 -0
  6. package/coverage/lcov-report/prettify.css +1 -0
  7. package/coverage/lcov-report/prettify.js +2 -0
  8. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  9. package/coverage/lcov-report/sorter.js +210 -0
  10. package/coverage/lcov-report/webpack/bootstrap.html +118 -0
  11. package/coverage/lcov-report/webpack/index.html +116 -0
  12. package/coverage/lcov-report/webpack/runtime/define property getters.html +118 -0
  13. package/coverage/lcov-report/webpack/runtime/global.html +118 -0
  14. package/coverage/lcov-report/webpack/runtime/hasOwnProperty shorthand.html +118 -0
  15. package/coverage/lcov-report/webpack/runtime/index.html +161 -0
  16. package/coverage/lcov-report/webpack/runtime/make namespace object.html +118 -0
  17. package/coverage/lcov.info +59434 -0
  18. package/dist/src/data-card/record-card.js +5 -1
  19. package/dist/src/data-card/record-card.js.map +1 -1
  20. package/dist/src/data-grid/data-grid.js +7 -1
  21. package/dist/src/data-grid/data-grid.js.map +1 -1
  22. package/dist/test/fetch-failure-state.test.js +24 -6
  23. package/dist/test/fetch-failure-state.test.js.map +1 -1
  24. package/dist/test/record-card-height.test.d.ts +1 -0
  25. package/dist/test/record-card-height.test.js +15 -0
  26. package/dist/test/record-card-height.test.js.map +1 -0
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +3 -3
  29. package/test/fetch-failure-state.test.ts +26 -5
  30. package/test/record-card-height.test.ts +19 -0
@@ -202,7 +202,11 @@ RecordCard.styles = [
202
202
  /* Theme 정의가 누락된 application 에서도 합리적 radius 보장. */
203
203
  border-radius: var(--data-card-record-card-border-radius, var(--md-sys-shape-corner-medium));
204
204
  border: var(--data-card-record-card-border);
205
- height: min-content;
205
+ /*
206
+ A card is as tall as its content. A list whose cards carry different numbers of lines can stretch the
207
+ cards of a row to one height with --data-card-record-card-height: auto (the grid stretches its items).
208
+ */
209
+ height: var(--data-card-record-card-height, min-content);
206
210
  /*
207
211
  그림이 카드 끝까지 닿아야 하는 목록은 이 여백을 0 으로 두고 그림 칸의 radius 로 위쪽
208
212
  모서리를 맞춘다. 글이 실리는 카드는 지금까지의 값 그대로다.
@@ -1 +1 @@
1
- {"version":3,"file":"record-card.js","sourceRoot":"","sources":["../../../src/data-card/record-card.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '../record-view/record-view.js'\nimport './data-card-field.js'\nimport './data-card-gutter.js'\nimport './data-card-gutter-menu.js'\n\nimport { LitElement, PropertyValues, css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { GristConfig, GristData, GristRecord } from '../types.js'\nimport { ZERO_CONFIG, ZERO_DATA, ZERO_RECORD } from '../configure/zero-config.js'\nimport { RecordViewHandler } from '../record-view/record-view-handler.js'\nimport { recordCardClickHandler } from './event-handlers/record-card-click-handler.js'\nimport { recordCardDblClickHandler } from './event-handlers/record-card-dblclick-handler.js'\n\n// TODO 로케일 설정에 따라서 포맷이 바뀌도록 해야한다.\nconst OPTIONS: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n hour12: false\n // timeZone: 'America/Los_Angeles'\n}\n\nfunction getSafeFormatter(locale: string, options: Intl.DateTimeFormatOptions) {\n try {\n const safeLocale = locale || 'en-US'\n return new Intl.DateTimeFormat(safeLocale, options)\n } catch (e) {\n return new Intl.DateTimeFormat('en-US', options)\n }\n}\n\nconst formatter = getSafeFormatter(navigator.language, OPTIONS)\n\n@customElement('ox-record-card')\nexport class RecordCard extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n position: relative;\n background-color: var(--data-card-record-card-background-color);\n /* Theme 정의가 누락된 application 에서도 합리적 radius 보장. */\n border-radius: var(--data-card-record-card-border-radius, var(--md-sys-shape-corner-medium));\n border: var(--data-card-record-card-border);\n height: min-content;\n /*\n 그림이 카드 끝까지 닿아야 하는 목록은 이 여백을 0 으로 두고 그림 칸의 radius 로 위쪽\n 모서리를 맞춘다. 글이 실리는 카드는 지금까지의 값 그대로다.\n */\n padding: var(--data-card-record-card-padding, var(--spacing-small) var(--spacing-none));\n }\n\n :host(:hover) {\n background-color: var(--data-card-record-card-background-color-hover, var(--md-sys-color-surface));\n border: var(--data-card-record-card-border-hover);\n box-shadow: var(--data-card-record-card-boxshadow-hover);\n }\n\n :host([dirty])::before {\n content: '';\n position: absolute;\n left: var(--spacing-none);\n top: var(--spacing-none);\n\n width: var(--spacing-none);\n height: var(--spacing-none);\n border-top: var(--grid-record-dirty-border-top);\n border-right: var(--grid-record-dirty-border-left);\n }\n\n :host[emphasized-row] {\n background-color: var(--grid-record-emphasized-background-color);\n color: var(--grid-record-emphasized-color);\n }\n\n [dirty] {\n position: absolute;\n margin: var(--spacing-none);\n height: 20px;\n\n font: var(--grid-record-dirty-icon-font);\n text-indent: 1px;\n left: var(--spacing-none);\n top: var(--spacing-none);\n color: var(--grid-record-dirty-color, var(--md-sys-color-error));\n }\n\n [thumbnail] {\n /*\n 높이 대신 **비율**로 잡을 수 있다.\n\n 그림이 자산인 목록에서는 카드 폭이 달라져도 그림 칸의 비율이 같아야 나란히 읽힌다. 높이만\n 있으면 좁은 카드에서는 납작하고 넓은 카드에서는 홀쭉해진다. 둘 다 안 주면 지금까지처럼\n 내용 높이를 따른다.\n */\n height: var(--data-card-thumbnail-height, auto);\n aspect-ratio: var(--data-card-thumbnail-aspect, auto);\n /*\n 그림 안쪽 여백과 바탕도 밖에서 정한다.\n\n 목록마다 그림이 하는 일이 다르다. 사람 얼굴(직원 목록)은 여백이 있어야 카드 안에 놓인\n 것처럼 보이고, 자산 카탈로그의 그림은 **그림이 주인공**이라 여백이 그림을 작게 만든다.\n 바탕도 마찬가지다 — 배경이 없는 그림(투명 png · 3D 미리보기)은 놓일 면이 있어야 한다.\n */\n padding: var(--data-card-thumbnail-padding, var(--spacing-large));\n background-color: var(--data-card-thumbnail-background, transparent);\n box-sizing: border-box;\n /* 동심 안쪽 radius — Material 3 shape token (외곽 = corner-medium, 안쪽 =\n * corner-small). board-ui 카드와 동일 톤. */\n border-radius: var(--data-card-thumbnail-radius, var(--md-sys-shape-corner-small));\n overflow: hidden;\n }\n\n /*\n 그림이 없는 것도 **자리는 지킨다.**\n\n 전에는 값이 없으면 썸네일 요소를 아예 그리지 않았다. 그러면 한 목록 안에서 카드 높이가\n 들쭉날쭉해져 눈이 줄을 못 잡고, 「아직 그림이 없다」는 사실을 말할 자리도 사라진다.\n 빈 자리는 조용한 바탕으로 두고, 무엇이라고 부를지는 쓰는 쪽이 정한다.\n */\n [thumbnail][empty] {\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: var(--data-card-thumbnail-empty-background, var(--md-sys-color-surface-container));\n color: var(--data-card-thumbnail-empty-color, var(--md-sys-color-outline));\n font: var(--data-card-item-etc-font);\n }\n\n [thumbnail][empty] md-icon {\n --md-icon-size: 28px;\n }\n\n [content] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-tiny);\n margin: var(--data-card-item-margin);\n }\n\n [brief] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-tiny);\n overflow: hidden;\n }\n\n /*\n 곁들이는 칸들 — 이 자리에 규칙이 아예 없었다.\n\n details 는 「이름 : 값」이 여러 줄 서는 자리인데 그 줄들을 세우는 규칙이 없어서, 카드가\n 글자 크기에 따라 줄이 붙어 버리고 제목 묶음(brief)과도 구분되지 않았다. 세로로 세우고\n 간격을 준다 — 카드에서 「무엇인가」와 「어떤가」는 다른 묶음이다.\n\n 제목과는 옅은 선으로 가른다 — 카드에서 「무엇인가」와 「어떤가」는 다른 묶음이다. 값이\n **전부** 빌 수 있는 목록이라면 선 아래에 아무것도 없는 카드가 생기므로, 그런 화면은\n --data-card-detail-border-top: none 으로 끈다.\n\n 「이름 : 값」이 아니라 짧은 표식 몇 개(상태 · 점수)를 다는 목록에서는 세로로 세우는 것이\n 자리만 먹는다. 그때는 화면이 한 줄로 뉘일 수 있다(--data-card-detail-direction: row).\n */\n [detail] {\n display: flex;\n flex-direction: var(--data-card-detail-direction, column);\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--spacing-tiny) var(--spacing-medium);\n margin-top: var(--spacing-small);\n padding-top: var(--spacing-small);\n border-top: var(--data-card-detail-border-top, 1px solid var(--md-sys-color-outline-variant));\n overflow: hidden;\n }\n\n /*\n The action row sits under the text by default. A catalog whose actions are one or two\n small icons (board-ui's card lists) can instead park them in the card's bottom-right corner,\n on the same line as the last text row, so the card does not spend a whole row on them:\n --data-card-gutters-position: absolute; --data-card-gutters-inset: auto 8px 6px auto.\n The host is already position: relative. The page then owes the text a right margin\n (--data-card-item-margin) so a long name does not run under the icons.\n */\n [gutters] {\n display: flex;\n flex-direction: row;\n gap: var(--spacing-small);\n margin: var(--data-card-gutters-margin, var(--spacing-small) var(--spacing-medium));\n justify-content: end;\n align-items: stretch;\n position: var(--data-card-gutters-position, static);\n inset: var(--data-card-gutters-inset, auto);\n }\n\n /*\n A quiet gutter (ColumnConfig.quiet) waits for the pointer. The row keeps its height either\n way, so cards do not jump; only the buttons fade. On a device with no hover there is\n nothing to wait for, so they are simply drawn.\n */\n [gutters] ox-card-gutter[quiet] {\n opacity: 0;\n transition: opacity 0.12s ease-out;\n }\n\n :host(:hover) [gutters] ox-card-gutter[quiet],\n [gutters] ox-card-gutter[quiet]:focus-within {\n opacity: 1;\n }\n\n @media (hover: none) {\n [gutters] ox-card-gutter[quiet] {\n opacity: 1;\n }\n }\n\n ox-card-field {\n font: var(--data-card-item-etc-font);\n color: var(--data-card-item-etc-color, var(--md-sys-color-on-surface));\n }\n\n ox-card-field[name] {\n font: var(--data-card-item-name-font);\n color: var(--data-card-item-name-color, var(--md-sys-color-secondary));\n }\n\n ox-card-field[desc] {\n font: var(--data-card-item-disc-font);\n color: var(--data-card-item-disc-color, var(--md-sys-color-tertiary));\n }\n `\n ]\n\n @property({ type: Object }) config: GristConfig = ZERO_CONFIG\n @property({ type: Object }) data: GristData = ZERO_DATA\n @property({ type: Object }) record: GristRecord = ZERO_RECORD\n @property({ type: Number }) rowIndex: number = -1\n /*\n * row-selector를 사용자가 변경할 때, record-card의 update를 유도하기 위해 selected-row attribute를 property에 추가함.\n * (이를 해주지 않으면, 리스트 refresh 경우에 selected-row checkbox가 클리어되지 않는 현상이 발생함.)\n */\n @property({ type: Boolean, attribute: 'selected-row' }) selectedRow: boolean = false\n @property({ attribute: false }) emphasized: any = false\n\n private _recordView: any\n private clickHandler = recordCardClickHandler.bind(this) as EventListener\n private dblclickHandler = recordCardDblClickHandler.bind(this) as EventListener\n\n firstUpdated() {\n /*\n long-press\n TODO. performance를 확인한 후에 활성화하자.\n */\n // longpressable(this.renderRoot.querySelector('[content]'))\n // this.renderRoot.addEventListener('long-press', recordPartialLongPressHandler.bind(this))\n this.renderRoot.addEventListener('click', this.clickHandler)\n this.renderRoot.addEventListener('dblclick', this.dblclickHandler)\n\n this.addEventListener('show-record-view', () => this.popupRecordView())\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('record') && this._recordView) {\n this._recordView.record = this.record\n }\n\n const emphasized = this.emphasized\n\n if (!!emphasized) {\n this.setAttribute('emphasized-row', '')\n\n if (Symbol.iterator in Object(emphasized)) {\n const [backgroundColor, foregroundColor] = emphasized as string[]\n backgroundColor && this.style.setProperty('--grid-record-emphasized-background-color', backgroundColor)\n foregroundColor && this.style.setProperty('--grid-record-emphasized-color', foregroundColor)\n }\n } else {\n this.removeAttribute('emphasized-row')\n }\n }\n\n render() {\n const record = this.record\n const rowIndex = this.rowIndex\n const { columns, list } = this.config\n const { thumbnail, fields, details, emptyThumbnail } = list || {}\n\n const briefFields = fields.map(field => columns.find(column => column.name == field)).filter(column => column) || []\n const detailFields =\n details.map(field => columns.find(column => column.name == field)).filter(column => column) || []\n\n const thumbnailColumn = thumbnail ? columns.find(column => column.name == thumbnail) : undefined\n /*\n * 값이 없으면 썸네일 자리를 만들지 않는다.\n *\n * 전에는 설정에 썸네일 컬럼 이름이 있으면 **레코드에 값이 있는지 보지 않고** 그렸다. 그래서 이미지를\n * 갖지 않는 목록(결재함 · 업무함)에서 카드마다 깨진 이미지 표시가 세로의 절반을 차지하고, 정작\n * 제목은 잘렸다. 없는 것의 자리를 비워 두는 것이다.\n */\n const thumbnailValue = thumbnailColumn ? record?.[thumbnailColumn.name] : undefined\n const hasThumbnail =\n /*\n * 자기가 그리겠다고 한 renderer 에게는 값이 비어도 자리를 준다. 그림이 없다는 말도, 그림\n * 위에 얹는 것들(상태 표시 · 단추)도 renderer 안에 있고, 그 사정을 아는 것은 renderer 뿐이다.\n */\n thumbnailColumn?.record?.renderEmpty ||\n (Array.isArray(thumbnailValue)\n ? thumbnailValue.length > 0\n : thumbnailValue !== null && thumbnailValue !== undefined && thumbnailValue !== '')\n\n const gutters = (columns || []).filter(column => column.type == 'gutter' && column.forList)\n\n if (this.hasAttribute('dirty')) {\n var dirtyIcon\n\n switch (this.record['__dirty__']) {\n case 'M':\n dirtyIcon = 'done'\n break\n case '+':\n dirtyIcon = 'add'\n break\n case '-':\n dirtyIcon = 'remove'\n break\n }\n }\n\n return html`\n ${dirtyIcon ? html` <md-icon dirty>${dirtyIcon}</md-icon> ` : html``}\n ${thumbnailColumn\n ? hasThumbnail\n ? html` <ox-card-field\n thumbnail\n .rowIndex=${rowIndex}\n .column=${thumbnailColumn}\n .record=${record}\n .value=${record[thumbnailColumn?.name || '']}\n ></ox-card-field>`\n : html`<div thumbnail empty>\n ${emptyThumbnail?.icon ? html`<md-icon>${emptyThumbnail.icon}</md-icon>` : ''}\n ${emptyThumbnail?.label || ''}\n </div>`\n : html``}\n\n <div content>\n <div brief>\n ${briefFields.map(\n (column, idx) => html`\n <ox-card-field\n .rowIndex=${rowIndex}\n .column=${column}\n .record=${record}\n .value=${record[column?.name || '']}\n ?name=${idx == 0}\n ?desc=${idx == 1}\n ></ox-card-field>\n `\n )}\n </div>\n ${detailFields.length > 0\n ? html`\n <div detail>\n ${detailFields.map(\n (column, idx) => html`\n <ox-card-field\n .rowIndex=${rowIndex}\n .column=${column}\n .record=${record}\n .value=${record[column?.name || '']}\n ></ox-card-field>\n `\n )}\n </div>\n `\n : html``}\n </div>\n\n <div gutters>\n ${gutters.map(gutter => {\n return html`\n <ox-card-gutter\n .rowIndex=${rowIndex}\n .column=${gutter}\n .record=${record}\n .value=${record[gutter.name]}\n ></ox-card-gutter>\n `\n })}\n </div>\n `\n }\n\n popupRecordView() {\n var titleField = this.config.list.fields[0] || 'name'\n var title = this.record[titleField]\n\n /* field가 오브젝트형인 경우에는 렌더러를 타이틀로 사용한다. */\n if (typeof title == 'object') {\n var column = this.config.columns.find(column => column.name == titleField)\n title = column?.record.renderer(title, column, this.record, this.rowIndex, this /* cautious */)\n }\n\n this._recordView = RecordViewHandler(\n this.config.columns,\n this.record,\n this.rowIndex,\n this,\n {\n title\n },\n () => {\n delete this._recordView\n }\n )\n }\n}\n"]}
1
+ {"version":3,"file":"record-card.js","sourceRoot":"","sources":["../../../src/data-card/record-card.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '../record-view/record-view.js'\nimport './data-card-field.js'\nimport './data-card-gutter.js'\nimport './data-card-gutter-menu.js'\n\nimport { LitElement, PropertyValues, css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { GristConfig, GristData, GristRecord } from '../types.js'\nimport { ZERO_CONFIG, ZERO_DATA, ZERO_RECORD } from '../configure/zero-config.js'\nimport { RecordViewHandler } from '../record-view/record-view-handler.js'\nimport { recordCardClickHandler } from './event-handlers/record-card-click-handler.js'\nimport { recordCardDblClickHandler } from './event-handlers/record-card-dblclick-handler.js'\n\n// TODO 로케일 설정에 따라서 포맷이 바뀌도록 해야한다.\nconst OPTIONS: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n hour12: false\n // timeZone: 'America/Los_Angeles'\n}\n\nfunction getSafeFormatter(locale: string, options: Intl.DateTimeFormatOptions) {\n try {\n const safeLocale = locale || 'en-US'\n return new Intl.DateTimeFormat(safeLocale, options)\n } catch (e) {\n return new Intl.DateTimeFormat('en-US', options)\n }\n}\n\nconst formatter = getSafeFormatter(navigator.language, OPTIONS)\n\n@customElement('ox-record-card')\nexport class RecordCard extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n position: relative;\n background-color: var(--data-card-record-card-background-color);\n /* Theme 정의가 누락된 application 에서도 합리적 radius 보장. */\n border-radius: var(--data-card-record-card-border-radius, var(--md-sys-shape-corner-medium));\n border: var(--data-card-record-card-border);\n /*\n A card is as tall as its content. A list whose cards carry different numbers of lines can stretch the\n cards of a row to one height with --data-card-record-card-height: auto (the grid stretches its items).\n */\n height: var(--data-card-record-card-height, min-content);\n /*\n 그림이 카드 끝까지 닿아야 하는 목록은 이 여백을 0 으로 두고 그림 칸의 radius 로 위쪽\n 모서리를 맞춘다. 글이 실리는 카드는 지금까지의 값 그대로다.\n */\n padding: var(--data-card-record-card-padding, var(--spacing-small) var(--spacing-none));\n }\n\n :host(:hover) {\n background-color: var(--data-card-record-card-background-color-hover, var(--md-sys-color-surface));\n border: var(--data-card-record-card-border-hover);\n box-shadow: var(--data-card-record-card-boxshadow-hover);\n }\n\n :host([dirty])::before {\n content: '';\n position: absolute;\n left: var(--spacing-none);\n top: var(--spacing-none);\n\n width: var(--spacing-none);\n height: var(--spacing-none);\n border-top: var(--grid-record-dirty-border-top);\n border-right: var(--grid-record-dirty-border-left);\n }\n\n :host[emphasized-row] {\n background-color: var(--grid-record-emphasized-background-color);\n color: var(--grid-record-emphasized-color);\n }\n\n [dirty] {\n position: absolute;\n margin: var(--spacing-none);\n height: 20px;\n\n font: var(--grid-record-dirty-icon-font);\n text-indent: 1px;\n left: var(--spacing-none);\n top: var(--spacing-none);\n color: var(--grid-record-dirty-color, var(--md-sys-color-error));\n }\n\n [thumbnail] {\n /*\n 높이 대신 **비율**로 잡을 수 있다.\n\n 그림이 자산인 목록에서는 카드 폭이 달라져도 그림 칸의 비율이 같아야 나란히 읽힌다. 높이만\n 있으면 좁은 카드에서는 납작하고 넓은 카드에서는 홀쭉해진다. 둘 다 안 주면 지금까지처럼\n 내용 높이를 따른다.\n */\n height: var(--data-card-thumbnail-height, auto);\n aspect-ratio: var(--data-card-thumbnail-aspect, auto);\n /*\n 그림 안쪽 여백과 바탕도 밖에서 정한다.\n\n 목록마다 그림이 하는 일이 다르다. 사람 얼굴(직원 목록)은 여백이 있어야 카드 안에 놓인\n 것처럼 보이고, 자산 카탈로그의 그림은 **그림이 주인공**이라 여백이 그림을 작게 만든다.\n 바탕도 마찬가지다 — 배경이 없는 그림(투명 png · 3D 미리보기)은 놓일 면이 있어야 한다.\n */\n padding: var(--data-card-thumbnail-padding, var(--spacing-large));\n background-color: var(--data-card-thumbnail-background, transparent);\n box-sizing: border-box;\n /* 동심 안쪽 radius — Material 3 shape token (외곽 = corner-medium, 안쪽 =\n * corner-small). board-ui 카드와 동일 톤. */\n border-radius: var(--data-card-thumbnail-radius, var(--md-sys-shape-corner-small));\n overflow: hidden;\n }\n\n /*\n 그림이 없는 것도 **자리는 지킨다.**\n\n 전에는 값이 없으면 썸네일 요소를 아예 그리지 않았다. 그러면 한 목록 안에서 카드 높이가\n 들쭉날쭉해져 눈이 줄을 못 잡고, 「아직 그림이 없다」는 사실을 말할 자리도 사라진다.\n 빈 자리는 조용한 바탕으로 두고, 무엇이라고 부를지는 쓰는 쪽이 정한다.\n */\n [thumbnail][empty] {\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: var(--data-card-thumbnail-empty-background, var(--md-sys-color-surface-container));\n color: var(--data-card-thumbnail-empty-color, var(--md-sys-color-outline));\n font: var(--data-card-item-etc-font);\n }\n\n [thumbnail][empty] md-icon {\n --md-icon-size: 28px;\n }\n\n [content] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-tiny);\n margin: var(--data-card-item-margin);\n }\n\n [brief] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-tiny);\n overflow: hidden;\n }\n\n /*\n 곁들이는 칸들 — 이 자리에 규칙이 아예 없었다.\n\n details 는 「이름 : 값」이 여러 줄 서는 자리인데 그 줄들을 세우는 규칙이 없어서, 카드가\n 글자 크기에 따라 줄이 붙어 버리고 제목 묶음(brief)과도 구분되지 않았다. 세로로 세우고\n 간격을 준다 — 카드에서 「무엇인가」와 「어떤가」는 다른 묶음이다.\n\n 제목과는 옅은 선으로 가른다 — 카드에서 「무엇인가」와 「어떤가」는 다른 묶음이다. 값이\n **전부** 빌 수 있는 목록이라면 선 아래에 아무것도 없는 카드가 생기므로, 그런 화면은\n --data-card-detail-border-top: none 으로 끈다.\n\n 「이름 : 값」이 아니라 짧은 표식 몇 개(상태 · 점수)를 다는 목록에서는 세로로 세우는 것이\n 자리만 먹는다. 그때는 화면이 한 줄로 뉘일 수 있다(--data-card-detail-direction: row).\n */\n [detail] {\n display: flex;\n flex-direction: var(--data-card-detail-direction, column);\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--spacing-tiny) var(--spacing-medium);\n margin-top: var(--spacing-small);\n padding-top: var(--spacing-small);\n border-top: var(--data-card-detail-border-top, 1px solid var(--md-sys-color-outline-variant));\n overflow: hidden;\n }\n\n /*\n The action row sits under the text by default. A catalog whose actions are one or two\n small icons (board-ui's card lists) can instead park them in the card's bottom-right corner,\n on the same line as the last text row, so the card does not spend a whole row on them:\n --data-card-gutters-position: absolute; --data-card-gutters-inset: auto 8px 6px auto.\n The host is already position: relative. The page then owes the text a right margin\n (--data-card-item-margin) so a long name does not run under the icons.\n */\n [gutters] {\n display: flex;\n flex-direction: row;\n gap: var(--spacing-small);\n margin: var(--data-card-gutters-margin, var(--spacing-small) var(--spacing-medium));\n justify-content: end;\n align-items: stretch;\n position: var(--data-card-gutters-position, static);\n inset: var(--data-card-gutters-inset, auto);\n }\n\n /*\n A quiet gutter (ColumnConfig.quiet) waits for the pointer. The row keeps its height either\n way, so cards do not jump; only the buttons fade. On a device with no hover there is\n nothing to wait for, so they are simply drawn.\n */\n [gutters] ox-card-gutter[quiet] {\n opacity: 0;\n transition: opacity 0.12s ease-out;\n }\n\n :host(:hover) [gutters] ox-card-gutter[quiet],\n [gutters] ox-card-gutter[quiet]:focus-within {\n opacity: 1;\n }\n\n @media (hover: none) {\n [gutters] ox-card-gutter[quiet] {\n opacity: 1;\n }\n }\n\n ox-card-field {\n font: var(--data-card-item-etc-font);\n color: var(--data-card-item-etc-color, var(--md-sys-color-on-surface));\n }\n\n ox-card-field[name] {\n font: var(--data-card-item-name-font);\n color: var(--data-card-item-name-color, var(--md-sys-color-secondary));\n }\n\n ox-card-field[desc] {\n font: var(--data-card-item-disc-font);\n color: var(--data-card-item-disc-color, var(--md-sys-color-tertiary));\n }\n `\n ]\n\n @property({ type: Object }) config: GristConfig = ZERO_CONFIG\n @property({ type: Object }) data: GristData = ZERO_DATA\n @property({ type: Object }) record: GristRecord = ZERO_RECORD\n @property({ type: Number }) rowIndex: number = -1\n /*\n * row-selector를 사용자가 변경할 때, record-card의 update를 유도하기 위해 selected-row attribute를 property에 추가함.\n * (이를 해주지 않으면, 리스트 refresh 경우에 selected-row checkbox가 클리어되지 않는 현상이 발생함.)\n */\n @property({ type: Boolean, attribute: 'selected-row' }) selectedRow: boolean = false\n @property({ attribute: false }) emphasized: any = false\n\n private _recordView: any\n private clickHandler = recordCardClickHandler.bind(this) as EventListener\n private dblclickHandler = recordCardDblClickHandler.bind(this) as EventListener\n\n firstUpdated() {\n /*\n long-press\n TODO. performance를 확인한 후에 활성화하자.\n */\n // longpressable(this.renderRoot.querySelector('[content]'))\n // this.renderRoot.addEventListener('long-press', recordPartialLongPressHandler.bind(this))\n this.renderRoot.addEventListener('click', this.clickHandler)\n this.renderRoot.addEventListener('dblclick', this.dblclickHandler)\n\n this.addEventListener('show-record-view', () => this.popupRecordView())\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('record') && this._recordView) {\n this._recordView.record = this.record\n }\n\n const emphasized = this.emphasized\n\n if (!!emphasized) {\n this.setAttribute('emphasized-row', '')\n\n if (Symbol.iterator in Object(emphasized)) {\n const [backgroundColor, foregroundColor] = emphasized as string[]\n backgroundColor && this.style.setProperty('--grid-record-emphasized-background-color', backgroundColor)\n foregroundColor && this.style.setProperty('--grid-record-emphasized-color', foregroundColor)\n }\n } else {\n this.removeAttribute('emphasized-row')\n }\n }\n\n render() {\n const record = this.record\n const rowIndex = this.rowIndex\n const { columns, list } = this.config\n const { thumbnail, fields, details, emptyThumbnail } = list || {}\n\n const briefFields = fields.map(field => columns.find(column => column.name == field)).filter(column => column) || []\n const detailFields =\n details.map(field => columns.find(column => column.name == field)).filter(column => column) || []\n\n const thumbnailColumn = thumbnail ? columns.find(column => column.name == thumbnail) : undefined\n /*\n * 값이 없으면 썸네일 자리를 만들지 않는다.\n *\n * 전에는 설정에 썸네일 컬럼 이름이 있으면 **레코드에 값이 있는지 보지 않고** 그렸다. 그래서 이미지를\n * 갖지 않는 목록(결재함 · 업무함)에서 카드마다 깨진 이미지 표시가 세로의 절반을 차지하고, 정작\n * 제목은 잘렸다. 없는 것의 자리를 비워 두는 것이다.\n */\n const thumbnailValue = thumbnailColumn ? record?.[thumbnailColumn.name] : undefined\n const hasThumbnail =\n /*\n * 자기가 그리겠다고 한 renderer 에게는 값이 비어도 자리를 준다. 그림이 없다는 말도, 그림\n * 위에 얹는 것들(상태 표시 · 단추)도 renderer 안에 있고, 그 사정을 아는 것은 renderer 뿐이다.\n */\n thumbnailColumn?.record?.renderEmpty ||\n (Array.isArray(thumbnailValue)\n ? thumbnailValue.length > 0\n : thumbnailValue !== null && thumbnailValue !== undefined && thumbnailValue !== '')\n\n const gutters = (columns || []).filter(column => column.type == 'gutter' && column.forList)\n\n if (this.hasAttribute('dirty')) {\n var dirtyIcon\n\n switch (this.record['__dirty__']) {\n case 'M':\n dirtyIcon = 'done'\n break\n case '+':\n dirtyIcon = 'add'\n break\n case '-':\n dirtyIcon = 'remove'\n break\n }\n }\n\n return html`\n ${dirtyIcon ? html` <md-icon dirty>${dirtyIcon}</md-icon> ` : html``}\n ${thumbnailColumn\n ? hasThumbnail\n ? html` <ox-card-field\n thumbnail\n .rowIndex=${rowIndex}\n .column=${thumbnailColumn}\n .record=${record}\n .value=${record[thumbnailColumn?.name || '']}\n ></ox-card-field>`\n : html`<div thumbnail empty>\n ${emptyThumbnail?.icon ? html`<md-icon>${emptyThumbnail.icon}</md-icon>` : ''}\n ${emptyThumbnail?.label || ''}\n </div>`\n : html``}\n\n <div content>\n <div brief>\n ${briefFields.map(\n (column, idx) => html`\n <ox-card-field\n .rowIndex=${rowIndex}\n .column=${column}\n .record=${record}\n .value=${record[column?.name || '']}\n ?name=${idx == 0}\n ?desc=${idx == 1}\n ></ox-card-field>\n `\n )}\n </div>\n ${detailFields.length > 0\n ? html`\n <div detail>\n ${detailFields.map(\n (column, idx) => html`\n <ox-card-field\n .rowIndex=${rowIndex}\n .column=${column}\n .record=${record}\n .value=${record[column?.name || '']}\n ></ox-card-field>\n `\n )}\n </div>\n `\n : html``}\n </div>\n\n <div gutters>\n ${gutters.map(gutter => {\n return html`\n <ox-card-gutter\n .rowIndex=${rowIndex}\n .column=${gutter}\n .record=${record}\n .value=${record[gutter.name]}\n ></ox-card-gutter>\n `\n })}\n </div>\n `\n }\n\n popupRecordView() {\n var titleField = this.config.list.fields[0] || 'name'\n var title = this.record[titleField]\n\n /* field가 오브젝트형인 경우에는 렌더러를 타이틀로 사용한다. */\n if (typeof title == 'object') {\n var column = this.config.columns.find(column => column.name == titleField)\n title = column?.record.renderer(title, column, this.record, this.rowIndex, this /* cautious */)\n }\n\n this._recordView = RecordViewHandler(\n this.config.columns,\n this.record,\n this.rowIndex,\n this,\n {\n title\n },\n () => {\n delete this._recordView\n }\n )\n }\n}\n"]}
@@ -232,7 +232,6 @@ let DataGrid = class DataGrid extends DataManipulator {
232
232
  .fixedLefts=${this.fixedLefts}
233
233
  .ariaRowIndexBase=${headerRows + (page - 1) * limit + 1}
234
234
  >
235
- ${this.empty ? renderEmptyNote(this.emptyNote) : html ``}
236
235
  </ox-grid-body>
237
236
  </div>
238
237
 
@@ -255,6 +254,13 @@ let DataGrid = class DataGrid extends DataManipulator {
255
254
  <slot name="setting"></slot>
256
255
  </div>
257
256
 
257
+ <!--
258
+ The empty note sits on the host, not in the body. The body is as wide as all its columns, so a note
259
+ centred in it went off to the right whenever the columns were wider than the grid (lot list,
260
+ 2026-10-12: body 1250px, grid 755px, note at 625px). The host is the visible grid.
261
+ -->
262
+ ${this.empty ? renderEmptyNote(this.emptyNote) : html ``}
263
+
258
264
  ${paginatable
259
265
  ? html `
260
266
  <!--
@@ -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 /* 초점 칸을 보이게 하는 스크롤은 몸이 직접 한다 — 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"]}
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 </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 <!--\n The empty note sits on the host, not in the body. The body is as wide as all its columns, so a note\n centred in it went off to the right whenever the columns were wider than the grid (lot list,\n 2026-10-12: body 1250px, grid 755px, note at 625px). The host is the visible grid.\n -->\n ${this.empty ? renderEmptyNote(this.emptyNote) : html``}\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"]}
@@ -22,12 +22,8 @@ async function grist(mode, handler) {
22
22
  function emptyNote(el) {
23
23
  var _a;
24
24
  const body = el.renderRoot.querySelector('#grist');
25
- const inBody = body === null || body === void 0 ? void 0 : body.renderRoot.querySelector('ox-empty-note');
26
- if (inBody)
27
- return inBody;
28
- /* GRID draws the note inside its body element. */
29
- const gridBody = body === null || body === void 0 ? void 0 : body.renderRoot.querySelector('ox-grid-body');
30
- return (_a = gridBody === null || gridBody === void 0 ? void 0 : gridBody.querySelector('ox-empty-note')) !== null && _a !== void 0 ? _a : null;
25
+ /* Every mode draws the note in its own shadow root - GRID on the grid host, not in its body. */
26
+ return (_a = body === null || body === void 0 ? void 0 : body.renderRoot.querySelector('ox-empty-note')) !== null && _a !== void 0 ? _a : null;
31
27
  }
32
28
  describe('ox-grist when the fetch fails', () => {
33
29
  /*
@@ -76,6 +72,28 @@ describe('ox-grist when the fetch fails', () => {
76
72
  expect(note.hasAttribute('role')).to.equal(false);
77
73
  expect(note.getAttribute('title')).to.equal('nothing here yet.');
78
74
  });
75
+ it('GRID: the note stays in the visible grid when the columns are wider than it', async () => {
76
+ const wide = {
77
+ columns: [
78
+ { type: 'string', name: 'id', hidden: true },
79
+ { type: 'string', name: 'a', header: 'a', width: 600 },
80
+ { type: 'string', name: 'b', header: 'b', width: 600 }
81
+ ],
82
+ pagination: { pages: [20] }
83
+ };
84
+ const el = (await fixture(html `
85
+ <ox-grist style="height: 300px; width: 500px" mode="GRID" .config=${wide} .fetchHandler=${async () => ({ total: 0, records: [] })}></ox-grist>
86
+ `));
87
+ await el.fetch();
88
+ await settle();
89
+ await el.updateComplete;
90
+ const note = emptyNote(el);
91
+ expect(note, 'a note is drawn').to.exist;
92
+ const box = el.getBoundingClientRect();
93
+ const at = note.getBoundingClientRect();
94
+ /* Inside the 500px the person sees, not centred in the 1200px of columns. */
95
+ expect(at.left >= box.left && at.right <= box.right, `note ${at.left}-${at.right} within grid ${box.left}-${box.right}`).to.equal(true);
96
+ });
79
97
  it('rows from before stay, with a band saying they are not a fresh result; a good fetch clears it', async () => {
80
98
  var _a;
81
99
  let fail = false;
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-failure-state.test.js","sourceRoot":"","sources":["../../test/fetch-failure-state.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 { html } from 'lit'\n\nimport '../src/data-grist.js'\n\n/**\n * A failed fetch is not an empty list.\n *\n * The provider wrapped the fetch in try/finally with no catch, so a thrown fetch hid the spinner and\n * nothing else: with no rows the grid said \"nothing to show\", with rows from before it kept them as if\n * they were the answer. Both are a confident wrong statement about data the grid never saw.\n */\n\ntype Rec = { id: string; name: string }\n\nconst config = {\n columns: [\n { type: 'string', name: 'id', hidden: true },\n { type: 'string', name: 'name', header: 'name' }\n ],\n pagination: { pages: [20] }\n}\n\nconst settle = (ms = 150) => new Promise(resolve => setTimeout(resolve, ms))\n\nasync function grist(mode: 'GRID' | 'LIST' | 'CARD', handler: () => Promise<unknown>) {\n const el = (await fixture(html`\n <ox-grist style=\"height: 300px\" mode=${mode} .config=${config} .fetchHandler=${handler}></ox-grist>\n `)) as HTMLElement & { fetch(): Promise<void>; updateComplete: Promise<unknown>; renderRoot: ShadowRoot }\n await el.fetch().catch(() => {})\n await settle()\n await el.updateComplete\n return el\n}\n\nfunction emptyNote(el: HTMLElement & { renderRoot: ShadowRoot }): HTMLElement | null {\n const body = el.renderRoot.querySelector('#grist') as (HTMLElement & { renderRoot: ShadowRoot }) | null\n const inBody = body?.renderRoot.querySelector('ox-empty-note') as HTMLElement | null\n if (inBody) return inBody\n /* GRID draws the note inside its body element. */\n const gridBody = body?.renderRoot.querySelector('ox-grid-body') as HTMLElement | null\n return (gridBody?.querySelector('ox-empty-note') as HTMLElement | null) ?? null\n}\n\ndescribe('ox-grist when the fetch fails', () => {\n /*\n * Resizing the grid body inside a fixture trips Chrome's benign \"ResizeObserver loop completed\"\n * error, which mocha reports as a failure. It says nothing about fetch failures; keep it out.\n */\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(() => {\n window.removeEventListener('error', ignoreResizeLoop, true)\n window.onerror = previousOnError\n })\n\n for (const mode of ['GRID', 'LIST', 'CARD'] as const) {\n it(`${mode}: says it could not load, not that there is nothing, and retries`, async () => {\n let calls = 0\n const el = await grist(mode, async () => {\n calls++\n throw new Error('server down')\n })\n\n const note = emptyNote(el)\n expect(note, 'a note is drawn').to.exist\n expect(note!.getAttribute('role')).to.equal('alert')\n expect(note!.getAttribute('title')).to.equal('could not load the list')\n\n const retry = note!.querySelector('md-text-button') as HTMLElement\n expect(retry, 'retry').to.exist\n const before = calls\n retry.click()\n await settle()\n expect(calls).to.equal(before + 1)\n })\n }\n\n it('an empty answer still says empty, without an alert', async () => {\n const el = await grist('GRID', async () => ({ total: 0, records: [] as Rec[] }))\n\n const note = emptyNote(el)\n expect(note, 'a note is drawn').to.exist\n expect(note!.hasAttribute('role')).to.equal(false)\n expect(note!.getAttribute('title')).to.equal('nothing here yet.')\n })\n\n it('rows from before stay, with a band saying they are not a fresh result; a good fetch clears it', async () => {\n let fail = false\n const el = await grist('GRID', async () => {\n if (fail) throw new Error('server down')\n return { total: 1, records: [{ id: '1', name: 'first' }] }\n })\n\n expect(el.renderRoot.querySelector('ox-list-state') === null, 'absent').to.equal(true)\n\n fail = true\n await el.fetch().catch(() => {})\n await settle()\n await el.updateComplete\n\n const state = el.renderRoot.querySelector('ox-list-state')!\n expect(state, 'failure band').to.exist\n await state.updateComplete\n expect(state.renderRoot.querySelector('[role=\"alert\"]')?.textContent).to.contain('could not refresh')\n\n fail = false\n await el.fetch()\n await settle()\n await el.updateComplete\n expect(el.renderRoot.querySelector('ox-list-state') === null, 'absent').to.equal(true)\n })\n\n it('the error still reaches whoever awaited the fetch', async () => {\n const el = (await fixture(html`\n <ox-grist mode=\"GRID\" .config=${config} .fetchHandler=${async () => { throw new Error('server down') }}></ox-grist>\n `)) as HTMLElement & { fetch(): Promise<void> }\n\n let caught: unknown\n await el.fetch().catch(e => (caught = e))\n expect((caught as Error)?.message).to.equal('server down')\n })\n})\n"]}
1
+ {"version":3,"file":"fetch-failure-state.test.js","sourceRoot":"","sources":["../../test/fetch-failure-state.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 { html } from 'lit'\n\nimport '../src/data-grist.js'\n\n/**\n * A failed fetch is not an empty list.\n *\n * The provider wrapped the fetch in try/finally with no catch, so a thrown fetch hid the spinner and\n * nothing else: with no rows the grid said \"nothing to show\", with rows from before it kept them as if\n * they were the answer. Both are a confident wrong statement about data the grid never saw.\n */\n\ntype Rec = { id: string; name: string }\n\nconst config = {\n columns: [\n { type: 'string', name: 'id', hidden: true },\n { type: 'string', name: 'name', header: 'name' }\n ],\n pagination: { pages: [20] }\n}\n\nconst settle = (ms = 150) => new Promise(resolve => setTimeout(resolve, ms))\n\nasync function grist(mode: 'GRID' | 'LIST' | 'CARD', handler: () => Promise<unknown>) {\n const el = (await fixture(html`\n <ox-grist style=\"height: 300px\" mode=${mode} .config=${config} .fetchHandler=${handler}></ox-grist>\n `)) as HTMLElement & { fetch(): Promise<void>; updateComplete: Promise<unknown>; renderRoot: ShadowRoot }\n await el.fetch().catch(() => {})\n await settle()\n await el.updateComplete\n return el\n}\n\nfunction emptyNote(el: HTMLElement & { renderRoot: ShadowRoot }): HTMLElement | null {\n const body = el.renderRoot.querySelector('#grist') as (HTMLElement & { renderRoot: ShadowRoot }) | null\n /* Every mode draws the note in its own shadow root - GRID on the grid host, not in its body. */\n return (body?.renderRoot.querySelector('ox-empty-note') as HTMLElement | null) ?? null\n}\n\ndescribe('ox-grist when the fetch fails', () => {\n /*\n * Resizing the grid body inside a fixture trips Chrome's benign \"ResizeObserver loop completed\"\n * error, which mocha reports as a failure. It says nothing about fetch failures; keep it out.\n */\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(() => {\n window.removeEventListener('error', ignoreResizeLoop, true)\n window.onerror = previousOnError\n })\n\n for (const mode of ['GRID', 'LIST', 'CARD'] as const) {\n it(`${mode}: says it could not load, not that there is nothing, and retries`, async () => {\n let calls = 0\n const el = await grist(mode, async () => {\n calls++\n throw new Error('server down')\n })\n\n const note = emptyNote(el)\n expect(note, 'a note is drawn').to.exist\n expect(note!.getAttribute('role')).to.equal('alert')\n expect(note!.getAttribute('title')).to.equal('could not load the list')\n\n const retry = note!.querySelector('md-text-button') as HTMLElement\n expect(retry, 'retry').to.exist\n const before = calls\n retry.click()\n await settle()\n expect(calls).to.equal(before + 1)\n })\n }\n\n it('an empty answer still says empty, without an alert', async () => {\n const el = await grist('GRID', async () => ({ total: 0, records: [] as Rec[] }))\n\n const note = emptyNote(el)\n expect(note, 'a note is drawn').to.exist\n expect(note!.hasAttribute('role')).to.equal(false)\n expect(note!.getAttribute('title')).to.equal('nothing here yet.')\n })\n\n it('GRID: the note stays in the visible grid when the columns are wider than it', async () => {\n const wide = {\n columns: [\n { type: 'string', name: 'id', hidden: true },\n { type: 'string', name: 'a', header: 'a', width: 600 },\n { type: 'string', name: 'b', header: 'b', width: 600 }\n ],\n pagination: { pages: [20] }\n }\n const el = (await fixture(html`\n <ox-grist style=\"height: 300px; width: 500px\" mode=\"GRID\" .config=${wide} .fetchHandler=${async () => ({ total: 0, records: [] as Rec[] })}></ox-grist>\n `)) as HTMLElement & { fetch(): Promise<void>; updateComplete: Promise<unknown>; renderRoot: ShadowRoot }\n await el.fetch()\n await settle()\n await el.updateComplete\n\n const note = emptyNote(el)\n expect(note, 'a note is drawn').to.exist\n const box = el.getBoundingClientRect()\n const at = note!.getBoundingClientRect()\n /* Inside the 500px the person sees, not centred in the 1200px of columns. */\n expect(at.left >= box.left && at.right <= box.right, `note ${at.left}-${at.right} within grid ${box.left}-${box.right}`).to.equal(true)\n })\n\n it('rows from before stay, with a band saying they are not a fresh result; a good fetch clears it', async () => {\n let fail = false\n const el = await grist('GRID', async () => {\n if (fail) throw new Error('server down')\n return { total: 1, records: [{ id: '1', name: 'first' }] }\n })\n\n expect(el.renderRoot.querySelector('ox-list-state') === null, 'absent').to.equal(true)\n\n fail = true\n await el.fetch().catch(() => {})\n await settle()\n await el.updateComplete\n\n const state = el.renderRoot.querySelector('ox-list-state')!\n expect(state, 'failure band').to.exist\n await state.updateComplete\n expect(state.renderRoot.querySelector('[role=\"alert\"]')?.textContent).to.contain('could not refresh')\n\n fail = false\n await el.fetch()\n await settle()\n await el.updateComplete\n expect(el.renderRoot.querySelector('ox-list-state') === null, 'absent').to.equal(true)\n })\n\n it('the error still reaches whoever awaited the fetch', async () => {\n const el = (await fixture(html`\n <ox-grist mode=\"GRID\" .config=${config} .fetchHandler=${async () => { throw new Error('server down') }}></ox-grist>\n `)) as HTMLElement & { fetch(): Promise<void> }\n\n let caught: unknown\n await el.fetch().catch(e => (caught = e))\n expect((caught as Error)?.message).to.equal('server down')\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { RecordCard } from '../src/data-card/record-card.js';
3
+ /**
4
+ * A card is as tall as its content by default. A list whose cards carry different numbers of lines (a fact row that
5
+ * only some records have) stretches the cards of a row to one height through --data-card-record-card-height.
6
+ */
7
+ const text = (styles) => (Array.isArray(styles) ? styles : [styles]).map(s => { var _a; return String((_a = s.cssText) !== null && _a !== void 0 ? _a : s); }).join('\n');
8
+ describe('record card height', () => {
9
+ it('reads --data-card-record-card-height, min-content when not set', () => {
10
+ const css = text(RecordCard.styles);
11
+ const host = css.slice(css.indexOf(':host {'), css.indexOf('}', css.indexOf(':host {')));
12
+ expect(host).to.contain('height: var(--data-card-record-card-height, min-content);');
13
+ });
14
+ });
15
+ //# sourceMappingURL=record-card-height.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-card-height.test.js","sourceRoot":"","sources":["../../test/record-card-height.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAE5D;;;GAGG;AAEH,MAAM,IAAI,GAAG,CAAC,MAAe,EAAE,EAAE,CAC/B,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,WAAC,OAAA,MAAM,CAAC,MAAC,CAA0B,CAAC,OAAO,mCAAI,CAAC,CAAC,CAAA,EAAA,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEnH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QACnC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;QACxF,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAA;IACtF,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { RecordCard } from '../src/data-card/record-card.js'\n\n/**\n * A card is as tall as its content by default. A list whose cards carry different numbers of lines (a fact row that\n * only some records have) stretches the cards of a row to one height through --data-card-record-card-height.\n */\n\nconst text = (styles: unknown) =>\n (Array.isArray(styles) ? styles : [styles]).map(s => String((s as { cssText?: string }).cssText ?? s)).join('\\n')\n\ndescribe('record card height', () => {\n it('reads --data-card-record-card-height, min-content when not set', () => {\n const css = text(RecordCard.styles)\n const host = css.slice(css.indexOf(':host {'), css.indexOf('}', css.indexOf(':host {')))\n expect(host).to.contain('height: var(--data-card-record-card-height, min-content);')\n })\n})\n"]}