@operato/data-grist 10.10.3 → 10.10.4

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 (31) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/src/data-card/data-card.js +33 -8
  3. package/dist/src/data-card/data-card.js.map +1 -1
  4. package/dist/src/data-card/record-card.js +20 -46
  5. package/dist/src/data-card/record-card.js.map +1 -1
  6. package/dist/src/data-grist.d.ts +0 -3
  7. package/dist/src/data-grist.js +32 -26
  8. package/dist/src/data-grist.js.map +1 -1
  9. package/dist/src/data-list/data-list.js +21 -8
  10. package/dist/src/data-list/data-list.js.map +1 -1
  11. package/dist/src/types.d.ts +9 -6
  12. package/dist/src/types.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +2 -2
  15. package/yarn-error.log +16971 -0
  16. package/coverage/lcov-report/base.css +0 -224
  17. package/coverage/lcov-report/block-navigation.js +0 -87
  18. package/coverage/lcov-report/favicon.png +0 -0
  19. package/coverage/lcov-report/index.html +0 -866
  20. package/coverage/lcov-report/prettify.css +0 -1
  21. package/coverage/lcov-report/prettify.js +0 -2
  22. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  23. package/coverage/lcov-report/sorter.js +0 -210
  24. package/coverage/lcov-report/webpack/bootstrap.html +0 -118
  25. package/coverage/lcov-report/webpack/index.html +0 -116
  26. package/coverage/lcov-report/webpack/runtime/define property getters.html +0 -118
  27. package/coverage/lcov-report/webpack/runtime/global.html +0 -118
  28. package/coverage/lcov-report/webpack/runtime/hasOwnProperty shorthand.html +0 -118
  29. package/coverage/lcov-report/webpack/runtime/index.html +0 -161
  30. package/coverage/lcov-report/webpack/runtime/make namespace object.html +0 -118
  31. package/coverage/lcov.info +0 -53451
package/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ### [10.10.4](https://github.com/hatiolab/operato/compare/v10.10.3...v10.10.4) (2026-09-10)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * **data-grist:** stop reserving a band the list cannot use, and fix the scroll-to-top button ([9beae1d](https://github.com/hatiolab/operato/commit/9beae1db4fa17ff7d6a2b015b534c44b2a6735ad))
12
+
13
+
14
+
6
15
  ### [10.10.3](https://github.com/hatiolab/operato/compare/v10.10.2...v10.10.3) (2026-09-09)
7
16
 
8
17
 
@@ -107,16 +107,41 @@ DataCard.styles = [
107
107
  background-color: var(--grid-record-selected-background-color);
108
108
  }
109
109
 
110
+ /*
111
+ ── 맨 위로 가는 단추 ──────────────────────────────────────────────────
112
+ 자리를 먹지 않는다(absolute). 기준 상자는 ox-grist 의 host 인데(그쪽이 position: relative),
113
+ 그래서 **목록과 함께 스크롤되지 않고** 상자에 붙어 있다.
114
+
115
+ 고친 것 둘.
116
+
117
+ 1. 위가 아니라 **아래**다. 오른쪽 위는 머리말·검색·필터가 있는 자리여서 단추가 그것들과
118
+ 겹쳤다. 맨 위로 보내는 단추는 사람이 아래에 있을 때만 나오므로 손이 있는 쪽에 둔다.
119
+ 2. rgba(255,255,255,0.7) 이 박혀 있었다 — 어두운 테마에서 흰 알약이 떠 있고, 아이콘은
120
+ --icon-size-large 라 알약보다 커 보였다. 테마 토큰으로 그리고 크기를 정한다.
121
+ */
110
122
  #upward {
111
- --md-icon-size: var(--icon-size-large);
123
+ --md-icon-size: 20px;
112
124
  position: absolute;
113
- top: var(--data-card-fab-position-vertical);
114
- right: var(--data-card-fab-position-horizontal);
115
- background-color: rgba(255, 255, 255, 0.7);
116
- border-radius: var(--md-sys-shape-corner-large);
117
- color: var(--data-card-fab-color);
118
- box-shadow: var(--data-card-fab-shadow);
119
- padding: var(--spacing-small);
125
+ bottom: var(--data-card-fab-position-vertical, var(--spacing-large));
126
+ right: var(--data-card-fab-position-horizontal, var(--spacing-large));
127
+ display: grid;
128
+ place-items: center;
129
+ width: 36px;
130
+ height: 36px;
131
+ box-sizing: border-box;
132
+ color: var(--data-card-fab-color, var(--md-sys-color-on-surface-variant));
133
+ background-color: var(--data-card-fab-background-color, var(--md-sys-color-surface-container-high));
134
+ /* 떠 있는 단추의 모서리는 알약이다 — 담는 면(6px)이 아니라 그 자신의 격을 따른다 */
135
+ border-radius: var(--md-sys-shape-corner-full, 50%);
136
+ border: 1px solid var(--md-sys-color-outline-variant);
137
+ box-shadow: var(--data-card-fab-shadow, 0 1px 3px rgba(0, 0, 0, 0.18));
138
+ cursor: pointer;
139
+ z-index: 9;
140
+ }
141
+
142
+ #upward:hover {
143
+ color: var(--md-sys-color-on-secondary-container);
144
+ background-color: var(--md-sys-color-secondary-container);
120
145
  }
121
146
 
122
147
  slot {
@@ -1 +1 @@
1
- {"version":3,"file":"data-card.js","sourceRoot":"","sources":["../../../src/data-card/data-card.ts"],"names":[],"mappings":"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","sourcesContent":["import i18next from 'i18next'\nimport '@material/web/icon/icon.js'\nimport '../empty-note.js'\nimport './record-card.js'\n\nimport { PropertyValues, css, html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { DataManipulator } from '../data-manipulator.js'\nimport { GristRecord } from '../types.js'\n\n@customElement('ox-card')\nexport class DataCard extends DataManipulator {\n static styles = [\n css`\n :host {\n overflow-y: auto;\n padding: var(--spacing-medium);\n\n display: grid;\n /*\n 카드 한 장의 최소 폭. 화면마다 실리는 것이 다르므로 값을 밖에서 정할 수 있어야 한다 —\n 그림이 주인공인 목록(자산 카탈로그)은 좁게 여러 장을, 글이 많은 목록은 넓게 몇 장을 놓는다.\n 기본값은 지금까지의 값 그대로다.\n */\n grid-template-columns: repeat(auto-fill, minmax(var(--data-card-min-width, 300px), 1fr));\n grid-template-rows: min-content;\n grid-gap: var(--spacing-large);\n }\n\n [selected-row] {\n background-color: var(--grid-record-selected-background-color);\n }\n\n #upward {\n --md-icon-size: var(--icon-size-large);\n position: absolute;\n top: var(--data-card-fab-position-vertical);\n right: var(--data-card-fab-position-horizontal);\n background-color: rgba(255, 255, 255, 0.7);\n border-radius: var(--md-sys-shape-corner-large);\n color: var(--data-card-fab-color);\n box-shadow: var(--data-card-fab-shadow);\n padding: var(--spacing-small);\n }\n\n slot {\n width: 100%;\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 [card][emphasized-row],\n [card][emphasized-row][focused] {\n background-color: var(--grid-record-emphasized-background-color);\n color: var(--grid-record-emphasized-color);\n }\n\n @media screen and (max-width: 460px) {\n #setting {\n grid-template-columns: 1fr;\n grid-template-rows: 1fr 1fr;\n }\n }\n `\n ]\n\n @property({ type: Boolean }) empty?: boolean\n\n /** 비었을 때 무슨 말을 하나 — 판단은 data-grist 가 하고 여기서는 그리기만 한다. */\n @property({ type: Object }) emptyNote?: { icon?: string; title: string; description?: string }\n\n @state() private _records: GristRecord[] = []\n @state() private isTop: boolean = false\n\n private _total: number = 0\n private _limit: number = 20\n private _page: number = 1\n\n firstUpdated(changes: PropertyValues<this>) {\n this.isTop = true\n\n /* infinite scrolling */\n this.addEventListener('scroll', e => {\n const totalScrollHeight = this.scrollHeight\n const screenHeight = this.offsetHeight\n const currentScrollTop = this.scrollTop\n\n if (totalScrollHeight <= screenHeight + currentScrollTop + 1) {\n /* 마지막 페이지까지 계속 페이지를 증가시킨다. */\n var lastPage = Math.ceil(this._total / this._limit)\n\n if (this._page < lastPage) {\n this.dispatchEvent(new CustomEvent('attach-page', { bubbles: true, composed: true }))\n }\n }\n\n this.isTop = this.scrollTop == 0\n })\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('config')) {\n this._records = []\n this._page = 1\n }\n\n if (changes.has('data')) {\n this._records = this.data.records\n this._total = this.data.total || 0\n this._limit = this.data.limit || 20\n this._page = this.data.page || 0\n }\n }\n\n render() {\n var { classifier } = this.config.rows\n var records = this._records || []\n\n if (this.config && this.config.rows.appendable) {\n records = [...records, { __dirty__: '+' }]\n }\n\n return html`\n ${records.map((record, rowIndex) => {\n var { emphasized } = classifier.call(null, record, rowIndex) || {}\n\n return html`\n <ox-record-card\n .config=${this.config}\n .data=${this.data}\n .record=${record}\n .rowIndex=${rowIndex}\n .emphasized=${emphasized}\n ?selected-row=${record['__selected__']}\n ?dirty=${record['__dirty__']}\n card\n ></ox-record-card>\n `\n })}\n ${this.empty\n ? html`<ox-empty-note\n icon=${this.emptyNote?.icon ?? nothing}\n title=${this.emptyNote?.title ?? ''}\n description=${this.emptyNote?.description ?? nothing}\n ></ox-empty-note>`\n : html``}\n ${this.isTop\n ? html``\n : html` <md-icon id=\"upward\" @click=${(e: Event) => this.gotoTop(e)}>arrow_upward</md-icon> `}\n `\n }\n\n gotoTop(e: Event) {\n this.scrollTop = 0\n\n e.stopPropagation()\n }\n\n get pullToRefreshTarget() {\n return this\n }\n}\n"]}
1
+ {"version":3,"file":"data-card.js","sourceRoot":"","sources":["../../../src/data-card/data-card.ts"],"names":[],"mappings":"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","sourcesContent":["import i18next from 'i18next'\nimport '@material/web/icon/icon.js'\nimport '../empty-note.js'\nimport './record-card.js'\n\nimport { PropertyValues, css, html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { DataManipulator } from '../data-manipulator.js'\nimport { GristRecord } from '../types.js'\n\n@customElement('ox-card')\nexport class DataCard extends DataManipulator {\n static styles = [\n css`\n :host {\n overflow-y: auto;\n padding: var(--spacing-medium);\n\n display: grid;\n /*\n 카드 한 장의 최소 폭. 화면마다 실리는 것이 다르므로 값을 밖에서 정할 수 있어야 한다 —\n 그림이 주인공인 목록(자산 카탈로그)은 좁게 여러 장을, 글이 많은 목록은 넓게 몇 장을 놓는다.\n 기본값은 지금까지의 값 그대로다.\n */\n grid-template-columns: repeat(auto-fill, minmax(var(--data-card-min-width, 300px), 1fr));\n grid-template-rows: min-content;\n grid-gap: var(--spacing-large);\n }\n\n [selected-row] {\n background-color: var(--grid-record-selected-background-color);\n }\n\n /*\n ── 맨 위로 가는 단추 ──────────────────────────────────────────────────\n 자리를 먹지 않는다(absolute). 기준 상자는 ox-grist 의 host 인데(그쪽이 position: relative),\n 그래서 **목록과 함께 스크롤되지 않고** 상자에 붙어 있다.\n\n 고친 것 둘.\n\n 1. 위가 아니라 **아래**다. 오른쪽 위는 머리말·검색·필터가 있는 자리여서 단추가 그것들과\n 겹쳤다. 맨 위로 보내는 단추는 사람이 아래에 있을 때만 나오므로 손이 있는 쪽에 둔다.\n 2. rgba(255,255,255,0.7) 이 박혀 있었다 — 어두운 테마에서 흰 알약이 떠 있고, 아이콘은\n --icon-size-large 라 알약보다 커 보였다. 테마 토큰으로 그리고 크기를 정한다.\n */\n #upward {\n --md-icon-size: 20px;\n position: absolute;\n bottom: var(--data-card-fab-position-vertical, var(--spacing-large));\n right: var(--data-card-fab-position-horizontal, var(--spacing-large));\n display: grid;\n place-items: center;\n width: 36px;\n height: 36px;\n box-sizing: border-box;\n color: var(--data-card-fab-color, var(--md-sys-color-on-surface-variant));\n background-color: var(--data-card-fab-background-color, var(--md-sys-color-surface-container-high));\n /* 떠 있는 단추의 모서리는 알약이다 — 담는 면(6px)이 아니라 그 자신의 격을 따른다 */\n border-radius: var(--md-sys-shape-corner-full, 50%);\n border: 1px solid var(--md-sys-color-outline-variant);\n box-shadow: var(--data-card-fab-shadow, 0 1px 3px rgba(0, 0, 0, 0.18));\n cursor: pointer;\n z-index: 9;\n }\n\n #upward:hover {\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n }\n\n slot {\n width: 100%;\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 [card][emphasized-row],\n [card][emphasized-row][focused] {\n background-color: var(--grid-record-emphasized-background-color);\n color: var(--grid-record-emphasized-color);\n }\n\n @media screen and (max-width: 460px) {\n #setting {\n grid-template-columns: 1fr;\n grid-template-rows: 1fr 1fr;\n }\n }\n `\n ]\n\n @property({ type: Boolean }) empty?: boolean\n\n /** 비었을 때 무슨 말을 하나 — 판단은 data-grist 가 하고 여기서는 그리기만 한다. */\n @property({ type: Object }) emptyNote?: { icon?: string; title: string; description?: string }\n\n @state() private _records: GristRecord[] = []\n @state() private isTop: boolean = false\n\n private _total: number = 0\n private _limit: number = 20\n private _page: number = 1\n\n firstUpdated(changes: PropertyValues<this>) {\n this.isTop = true\n\n /* infinite scrolling */\n this.addEventListener('scroll', e => {\n const totalScrollHeight = this.scrollHeight\n const screenHeight = this.offsetHeight\n const currentScrollTop = this.scrollTop\n\n if (totalScrollHeight <= screenHeight + currentScrollTop + 1) {\n /* 마지막 페이지까지 계속 페이지를 증가시킨다. */\n var lastPage = Math.ceil(this._total / this._limit)\n\n if (this._page < lastPage) {\n this.dispatchEvent(new CustomEvent('attach-page', { bubbles: true, composed: true }))\n }\n }\n\n this.isTop = this.scrollTop == 0\n })\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('config')) {\n this._records = []\n this._page = 1\n }\n\n if (changes.has('data')) {\n this._records = this.data.records\n this._total = this.data.total || 0\n this._limit = this.data.limit || 20\n this._page = this.data.page || 0\n }\n }\n\n render() {\n var { classifier } = this.config.rows\n var records = this._records || []\n\n if (this.config && this.config.rows.appendable) {\n records = [...records, { __dirty__: '+' }]\n }\n\n return html`\n ${records.map((record, rowIndex) => {\n var { emphasized } = classifier.call(null, record, rowIndex) || {}\n\n return html`\n <ox-record-card\n .config=${this.config}\n .data=${this.data}\n .record=${record}\n .rowIndex=${rowIndex}\n .emphasized=${emphasized}\n ?selected-row=${record['__selected__']}\n ?dirty=${record['__dirty__']}\n card\n ></ox-record-card>\n `\n })}\n ${this.empty\n ? html`<ox-empty-note\n icon=${this.emptyNote?.icon ?? nothing}\n title=${this.emptyNote?.title ?? ''}\n description=${this.emptyNote?.description ?? nothing}\n ></ox-empty-note>`\n : html``}\n ${this.isTop\n ? html``\n : html` <md-icon id=\"upward\" @click=${(e: Event) => this.gotoTop(e)}>arrow_upward</md-icon> `}\n `\n }\n\n gotoTop(e: Event) {\n this.scrollTop = 0\n\n e.stopPropagation()\n }\n\n get pullToRefreshTarget() {\n return this\n }\n}\n"]}
@@ -59,13 +59,9 @@ let RecordCard = class RecordCard extends LitElement {
59
59
  this.addEventListener('show-record-view', () => this.popupRecordView());
60
60
  }
61
61
  updated(changes) {
62
- var _a, _b;
63
62
  if (changes.has('record') && this._recordView) {
64
63
  this._recordView.record = this.record;
65
64
  }
66
- if (changes.has('config')) {
67
- this.toggleAttribute('gutters-hover', ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.list) === null || _b === void 0 ? void 0 : _b.gutters) == 'hover');
68
- }
69
65
  const emphasized = this.emphasized;
70
66
  if (!!emphasized) {
71
67
  this.setAttribute('emphasized-row', '');
@@ -80,6 +76,7 @@ let RecordCard = class RecordCard extends LitElement {
80
76
  }
81
77
  }
82
78
  render() {
79
+ var _a;
83
80
  const record = this.record;
84
81
  const rowIndex = this.rowIndex;
85
82
  const { columns, list } = this.config;
@@ -95,9 +92,15 @@ let RecordCard = class RecordCard extends LitElement {
95
92
  * 제목은 잘렸다. 없는 것의 자리를 비워 두는 것이다.
96
93
  */
97
94
  const thumbnailValue = thumbnailColumn ? record === null || record === void 0 ? void 0 : record[thumbnailColumn.name] : undefined;
98
- const hasThumbnail = Array.isArray(thumbnailValue)
99
- ? thumbnailValue.length > 0
100
- : thumbnailValue !== null && thumbnailValue !== undefined && thumbnailValue !== '';
95
+ const hasThumbnail =
96
+ /*
97
+ * 자기가 그리겠다고 한 renderer 에게는 값이 비어도 자리를 준다. 그림이 없다는 말도, 그림
98
+ * 위에 얹는 것들(상태 표시 · 단추)도 renderer 안에 있고, 그 사정을 아는 것은 renderer 뿐이다.
99
+ */
100
+ ((_a = thumbnailColumn === null || thumbnailColumn === void 0 ? void 0 : thumbnailColumn.record) === null || _a === void 0 ? void 0 : _a.renderEmpty) ||
101
+ (Array.isArray(thumbnailValue)
102
+ ? thumbnailValue.length > 0
103
+ : thumbnailValue !== null && thumbnailValue !== undefined && thumbnailValue !== '');
101
104
  const gutters = (columns || []).filter(column => column.type == 'gutter' && column.forList);
102
105
  if (this.hasAttribute('dirty')) {
103
106
  var dirtyIcon;
@@ -200,7 +203,11 @@ RecordCard.styles = [
200
203
  border-radius: var(--data-card-record-card-border-radius, var(--md-sys-shape-corner-medium));
201
204
  border: var(--data-card-record-card-border);
202
205
  height: min-content;
203
- padding: var(--spacing-small) var(--spacing-none);
206
+ /*
207
+ 그림이 카드 끝까지 닿아야 하는 목록은 이 여백을 0 으로 두고 그림 칸의 radius 로 위쪽
208
+ 모서리를 맞춘다. 글이 실리는 카드는 지금까지의 값 그대로다.
209
+ */
210
+ padding: var(--data-card-record-card-padding, var(--spacing-small) var(--spacing-none));
204
211
  }
205
212
 
206
213
  :host(:hover) {
@@ -305,8 +312,9 @@ RecordCard.styles = [
305
312
  글자 크기에 따라 줄이 붙어 버리고 제목 묶음(brief)과도 구분되지 않았다. 세로로 세우고
306
313
  간격을 준다 — 카드에서 「무엇인가」와 「어떤가」는 다른 묶음이다.
307
314
 
308
- 선으로 가르지는 않는다. 값이 없는 줄이 섞이는 목록(있을 때만 말하는 칸)에서는 선 아래에
309
- 아무것도 없는 카드가 생긴다.
315
+ 제목과는 옅은 선으로 가른다 — 카드에서 「무엇인가」와 「어떤가」는 다른 묶음이다. 값이
316
+ **전부** 빌 수 있는 목록이라면 선 아래에 아무것도 없는 카드가 생기므로, 그런 화면은
317
+ --data-card-detail-border-top: none 으로 끈다.
310
318
 
311
319
  「이름 : 값」이 아니라 짧은 표식 몇 개(상태 · 점수)를 다는 목록에서는 세로로 세우는 것이
312
320
  자리만 먹는다. 그때는 화면이 한 줄로 뉘일 수 있다(--data-card-detail-direction: row).
@@ -318,6 +326,8 @@ RecordCard.styles = [
318
326
  align-items: baseline;
319
327
  gap: var(--spacing-tiny) var(--spacing-medium);
320
328
  margin-top: var(--spacing-small);
329
+ padding-top: var(--spacing-small);
330
+ border-top: var(--data-card-detail-border-top, 1px solid var(--md-sys-color-outline-variant));
321
331
  overflow: hidden;
322
332
  }
323
333
 
@@ -330,42 +340,6 @@ RecordCard.styles = [
330
340
  align-items: stretch;
331
341
  }
332
342
 
333
- /*
334
- ── 손이 갈 때만 나오는 단추 ────────────────────────────────────────
335
- 단추가 그 카드의 주된 할 일인 목록(결재함의 「상세정보」)에서는 늘 보여야 한다. 반대로
336
- 고르는 것이 주된 일인 목록(자산 카탈로그)에서 삭제 단추가 카드마다 떠 있으면, 사람이
337
- 고르려고 보는 자리에 되돌릴 수 없는 조작이 먼저 눈에 든다.
338
-
339
- 어느 쪽인지는 화면이 정한다(list.gutters: 'hover'). 눌리지 않게 함께 막아야 한다 —
340
- 보이지 않는 단추가 눌리는 것이 더 나쁘다.
341
- */
342
- /*
343
- 감춘 단추는 **자리도 차지하지 않는다.** opacity 만 0 으로 두면 카드마다 빈 줄이 남아,
344
- 늘 보이는 것보다 되레 카드가 길어진다. 그림 위 오른쪽 끝으로 띄운다 — 손이 갈 때만
345
- 나오는 단추가 있을 자리다. 그림이 어두울 수 있으니 옅은 바탕을 깐다.
346
- */
347
- :host([gutters-hover]) [gutters] {
348
- position: absolute;
349
- top: var(--spacing-small);
350
- right: var(--spacing-small);
351
- margin: var(--spacing-none);
352
- padding: 1px;
353
- border-radius: var(--md-sys-shape-corner-small);
354
- background-color: var(
355
- --data-card-gutters-floating-background,
356
- color-mix(in srgb, var(--md-sys-color-surface, #fff) 78%, transparent)
357
- );
358
- opacity: 0;
359
- pointer-events: none;
360
- transition: opacity 0.12s ease-out;
361
- }
362
-
363
- :host([gutters-hover]:hover) [gutters],
364
- :host([gutters-hover]:focus-within) [gutters] {
365
- opacity: 1;
366
- pointer-events: auto;
367
- }
368
-
369
343
  ox-card-field {
370
344
  font: var(--data-card-item-etc-font);
371
345
  color: var(--data-card-item-etc-color, var(--md-sys-color-on-surface));
@@ -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 padding: var(--spacing-small) var(--spacing-none);\n }\n\n :host(:hover) {\n background-color: 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\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 overflow: hidden;\n }\n\n [gutters] {\n display: flex;\n flex-direction: row;\n gap: var(--spacing-small);\n margin: var(--spacing-small) var(--spacing-medium);\n justify-content: end;\n align-items: stretch;\n }\n\n /*\n ── 손이 갈 때만 나오는 단추 ────────────────────────────────────────\n 단추가 그 카드의 주된 할 일인 목록(결재함의 「상세정보」)에서는 늘 보여야 한다. 반대로\n 고르는 것이 주된 일인 목록(자산 카탈로그)에서 삭제 단추가 카드마다 떠 있으면, 사람이\n 고르려고 보는 자리에 되돌릴 수 없는 조작이 먼저 눈에 든다.\n\n 어느 쪽인지는 화면이 정한다(list.gutters: 'hover'). 눌리지 않게 함께 막아야 한다 —\n 보이지 않는 단추가 눌리는 것이 더 나쁘다.\n */\n /*\n 감춘 단추는 **자리도 차지하지 않는다.** opacity 만 0 으로 두면 카드마다 빈 줄이 남아,\n 늘 보이는 것보다 되레 카드가 길어진다. 그림 위 오른쪽 끝으로 띄운다 — 손이 갈 때만\n 나오는 단추가 있을 자리다. 그림이 어두울 수 있으니 옅은 바탕을 깐다.\n */\n :host([gutters-hover]) [gutters] {\n position: absolute;\n top: var(--spacing-small);\n right: var(--spacing-small);\n margin: var(--spacing-none);\n padding: 1px;\n border-radius: var(--md-sys-shape-corner-small);\n background-color: var(\n --data-card-gutters-floating-background,\n color-mix(in srgb, var(--md-sys-color-surface, #fff) 78%, transparent)\n );\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.12s ease-out;\n }\n\n :host([gutters-hover]:hover) [gutters],\n :host([gutters-hover]:focus-within) [gutters] {\n opacity: 1;\n pointer-events: auto;\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 if (changes.has('config')) {\n this.toggleAttribute('gutters-hover', this.config?.list?.gutters == 'hover')\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 = 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 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(--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 [gutters] {\n display: flex;\n flex-direction: row;\n gap: var(--spacing-small);\n margin: var(--spacing-small) var(--spacing-medium);\n justify-content: end;\n align-items: stretch;\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"]}
@@ -114,10 +114,7 @@ export declare class DataGrist extends LitElement implements DataConsumer {
114
114
  private dataProvider?;
115
115
  private pulltorefreshHandle?;
116
116
  private headroom?;
117
- private orginPaddingTop?;
118
- private originMarginTop?;
119
117
  private headResizeObserver?;
120
- private headroomAtTop;
121
118
  private lastLocation;
122
119
  private pendingFetch?;
123
120
  private popstateEventHandler;
@@ -61,7 +61,6 @@ let DataGrist = class DataGrist extends LitElement {
61
61
  * 둘을 같게 두면 확인하지 않은 사실을 화면이 단정하게 된다.
62
62
  */
63
63
  this._fetched = false;
64
- this.headroomAtTop = true;
65
64
  this.lastLocation = {};
66
65
  this.popstateEventHandler = ((e) => {
67
66
  const { origin, pathname, search } = window.location;
@@ -281,36 +280,35 @@ let DataGrist = class DataGrist extends LitElement {
281
280
  /* 머리가 덮고 있는 위쪽 padding. 머리의 기준 상자가 host 의 padding 박스이므로 이 값이다. */
282
281
  const overlaid = parseFloat(getComputedStyle(this).paddingTop || '0') || 0;
283
282
  const offset = Math.max(0, this.head.clientHeight - overlaid) + 'px';
284
- if (this.headroomAtTop) {
285
- this.grist.style.marginTop = offset;
286
- }
287
- else {
288
- this.grist.style.paddingTop = offset;
289
- }
283
+ /*
284
+ ── 자리는 목록 **안쪽**에 만든다(padding), 목록을 밀어내지 않는다(margin) ──────────
285
+ 전에는 맨 위에서 margin-top 으로 목록 상자 자체를 내려놓았다. 그러면 목록이 차지하는 상자가
286
+ 머리 높이만큼 작아지고, **머리가 스크롤에 말려 올라가면 그 띠가 빈 채로 남는다.** 머리는
287
+ `headroom--unpinned` 에서 translateY(-100%) 로 사라지는데(HeadroomStyles), 목록은 여전히
288
+ 62px 아래에서 시작해 흰 띠가 그대로 보였다.
289
+
290
+ padding 으로 두면 목록 상자는 늘 꽉 차 있고, 위쪽 62px 은 스크롤된 내용이 지나가는 자리가
291
+ 된다. 맨 위에서는 그 자리를 머리가 덮으므로(불투명) 보이는 것은 전과 같다. 값이 늘 같아서
292
+ 머리가 접히고 펴질 때 내용이 튀지 않는다 — 상태를 나눠 서로 다른 값을 쓰던 것이 원인이었다.
293
+ */
294
+ this.grist.style.paddingTop = offset;
295
+ this.grist.style.removeProperty('margin-top');
290
296
  }
291
297
  setHeadroom() {
292
298
  var _a, _b;
293
299
  (_a = this.headroom) === null || _a === void 0 ? void 0 : _a.destroy();
294
300
  if (this.grist && this.head && this.head.style.display !== 'none') {
295
- const style = getComputedStyle(this.grist);
296
- this.orginPaddingTop = style.paddingTop || '0';
297
- this.originMarginTop = style.marginTop || '0';
298
- this.headroom = new Headroom(this.head, {
299
- scroller: this.grist,
300
- onTop: () => {
301
- this.grist.style.paddingTop = this.orginPaddingTop;
302
- this.originMarginTop = this.grist.style.marginTop;
303
- this.headroomAtTop = true;
304
- this.applyHeadOffset();
305
- },
306
- onNotTop: () => {
307
- this.grist.style.marginTop = this.originMarginTop;
308
- this.orginPaddingTop = this.grist.style.paddingTop;
309
- this.headroomAtTop = false;
310
- this.applyHeadOffset();
311
- }
312
- });
301
+ /*
302
+ **맨 위인지 아닌지로 값을 갈라 두지 않는다.**
303
+
304
+ 전에는 onTop·onNotTop 이 서로의 값을 저장했다 되돌리며 margin 과 padding 을 번갈아 썼다.
305
+ 그 저장 변수들은 서로가 밀어 놓은 값을 다시 담아 믿을 수 없었고(2026-09-02 에 남은 주석이
306
+ 그 사연이다), 상태에 따라 값이 달라지니 머리가 접힐 때 내용이 튀었다. 자리는 한 가지
307
+ 방법으로만 만든다 — `applyHeadOffset` 하나가 소유한다.
308
+ */
309
+ this.headroom = new Headroom(this.head, { scroller: this.grist });
313
310
  this.headroom.init();
311
+ this.applyHeadOffset();
314
312
  /* 머리가 커지거나 줄면 그때마다 다시 민다 — 한 번 잰 값으로 두면 내용이 겹친다. */
315
313
  (_b = this.headResizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
316
314
  this.headResizeObserver = new ResizeObserver(() => this.applyHeadOffset());
@@ -1319,7 +1317,15 @@ DataGrist.styles = [
1319
1317
 
1320
1318
  width: 100%;
1321
1319
  box-sizing: border-box;
1322
- background-color: var(--grist-background-color);
1320
+ /*
1321
+ **머리 뒤는 막혀 있어야 한다.**
1322
+
1323
+ 머리는 목록 위에 떠 있고 목록은 그 아래로 지나간다. --grist-background-color 는
1324
+ 테마에서 **transparent 로 선언되어 있어**(그리스트가 페이지 바탕을 물려받게 하려고) 그것을
1325
+ 그대로 쓰면 머리 글자와 목록 내용이 겹쳐 보인다. 값이 선언돼 있으니 var() 의 기본값도
1326
+ 소용없다 — 다른 토큰으로 막고, 화면의 바탕이 surface 가 아니면 그 토큰으로 맞춘다.
1327
+ */
1328
+ background-color: var(--grist-headroom-background-color, var(--md-sys-color-surface));
1323
1329
 
1324
1330
  z-index: 8;
1325
1331
  }