@operato/data-grist 10.22.0 → 10.23.0

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 +29 -0
  2. package/dist/src/configure/rows-option-builder.js +2 -1
  3. package/dist/src/configure/rows-option-builder.js.map +1 -1
  4. package/dist/src/data-card/data-card-field.d.ts +1 -0
  5. package/dist/src/data-card/data-card-field.js +9 -1
  6. package/dist/src/data-card/data-card-field.js.map +1 -1
  7. package/dist/src/data-card/data-card-gutter.js +30 -12
  8. package/dist/src/data-card/data-card-gutter.js.map +1 -1
  9. package/dist/src/data-card/data-card.d.ts +7 -0
  10. package/dist/src/data-card/data-card.js +237 -2
  11. package/dist/src/data-card/data-card.js.map +1 -1
  12. package/dist/src/data-card/record-card.js +33 -2
  13. package/dist/src/data-card/record-card.js.map +1 -1
  14. package/dist/src/data-grid/data-grid-field.js +1 -1
  15. package/dist/src/data-grid/data-grid-field.js.map +1 -1
  16. package/dist/src/filters/filter-styles.js +1 -1
  17. package/dist/src/filters/filter-styles.js.map +1 -1
  18. package/dist/src/gutters/gutter-sequence.d.ts +1 -0
  19. package/dist/src/personalizer/ox-grist-filter-personalizer.d.ts +1 -1
  20. package/dist/src/personalizer/ox-grist-filter-personalizer.js +11 -45
  21. package/dist/src/personalizer/ox-grist-filter-personalizer.js.map +1 -1
  22. package/dist/src/personalizer/ox-grist-personalizer.d.ts +1 -1
  23. package/dist/src/personalizer/ox-grist-personalizer.js +23 -86
  24. package/dist/src/personalizer/ox-grist-personalizer.js.map +1 -1
  25. package/dist/src/personalizer/personalizer-chrome.d.ts +5 -0
  26. package/dist/src/personalizer/personalizer-chrome.js +165 -0
  27. package/dist/src/personalizer/personalizer-chrome.js.map +1 -0
  28. package/dist/src/types.d.ts +13 -0
  29. package/dist/src/types.js.map +1 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +6 -6
@@ -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 '@material/web/button/text-button.js'\nimport '@operato/list-state/ox-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'\nimport { GridEmptyNote, renderEmptyNote } from '../empty-note-render.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?: GridEmptyNote\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 ? renderEmptyNote(this.emptyNote) : 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":";AACA,OAAO,4BAA4B,CAAA;AACnC,OAAO,qCAAqC,CAAA;AAC5C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,kBAAkB,CAAA;AAEzB,OAAO,EAAkB,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAiB,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGjE,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,eAAe;IAAtC;;QAqJY,aAAQ,GAAkB,EAAE,CAAA;QAC5B,UAAK,GAAY,KAAK,CAAA;QAE/B,WAAM,GAAW,CAAC,CAAA;QAClB,WAAM,GAAW,EAAE,CAAA;QACnB,UAAK,GAAW,CAAC,CAAA;QAEzB,+FAA+F;QACvF,eAAU,GAAkB,IAAI,CAAA;QAChC,kBAAa,GAAkB,IAAI,CAAA;QACnC,cAAS,GAA8B,IAAI,CAAA;IAkRrD,CAAC;IAhRC,YAAY,CAAC,OAA6B;QACxC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QAEjB,wBAAwB;QACxB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE;YAClC,MAAM,iBAAiB,GAAG,IAAI,CAAC,YAAY,CAAA;YAC3C,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAA;YACtC,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAA;YAEvC,IAAI,iBAAiB,IAAI,YAAY,GAAG,gBAAgB,GAAG,CAAC,EAAE,CAAC;gBAC7D,8BAA8B;gBAC9B,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;gBAEnD,IAAI,IAAI,CAAC,KAAK,GAAG,QAAQ,EAAE,CAAC;oBAC1B,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;gBACvF,CAAC;YACH,CAAC;YAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAA;QAClC,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,YAAY,EAAE,CAAA;IACrB,CAAC;IAED;;;;;OAKG;IACK,YAAY;QAClB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CAAQ,EAAE,EAAE;;YACzD,IAAI,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,IAAI,0CAAE,WAAW,CAAA;gBAAE,OAAM;YAE3C,MAAM,MAAM,GAAI,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,YAAY,CAAuB,CAAA;YACpF,IAAI,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,EAAE,CAAA;gBAAE,OAAM;YAEvB,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE,CAAA;YAC3B,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAC/B;YAAC,CAAe,CAAC,YAAa,CAAC,aAAa,GAAG,MAAM,CACrD;YAAA,MAAC,CAAe,CAAC,YAAY,0CAAE,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;QAClE,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAQ,EAAE,EAAE;;YACxD,IAAI,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,IAAI,0CAAE,WAAW,CAAA,IAAI,CAAC,IAAI,CAAC,UAAU;gBAAE,OAAM;YAC/D,CAAC,CAAC,cAAc,EAAE,CACjB;YAAC,CAAe,CAAC,YAAa,CAAC,UAAU,GAAG,MAAM,CAAA;YAEnD,MAAM,MAAM,GAAI,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,YAAY,CAAuB,CAAA;YACpF,IAAI,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,EAAE,CAAA,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,UAAU;gBAAE,OAAM;YAExD,MAAM,UAAU,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAA;YACjD,MAAM,QAAQ,GAAI,CAAe,CAAC,OAAO,GAAG,UAAU,CAAC,IAAI,CAAA;YAC3D,MAAM,IAAI,GAAG,QAAQ,GAAG,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAA;YAEjE,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,CAAC,EAAE,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;gBAChE,IAAI,CAAC,mBAAmB,EAAE,CAAA;gBAC1B,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAA;gBAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;gBACrB,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAA;YACxE,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CAAQ,EAAE,EAAE;;YACzD,IAAI,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,IAAI,0CAAE,WAAW,CAAA;gBAAE,OAAM;YAE3C,MAAM,MAAM,GAAI,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,YAAY,CAAuB,CAAA;YACpF,IAAI,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,EAAE,MAAK,IAAI,CAAC,aAAa,EAAE,CAAC;gBACtC,MAAM,OAAO,GAAI,CAAe,CAAC,aAAmC,CAAA;gBACpE,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC1C,IAAI,CAAC,mBAAmB,EAAE,CAAA;gBAC5B,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAc,CAAC,CAAC,CAAA;QAEpF,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,CAAQ,EAAE,EAAE;;YACvD,IAAI,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,IAAI,0CAAE,WAAW,CAAA;gBAAE,OAAM;YAE3C,IAAI,CAAC,mBAAmB,EAAE,CAAA;YAC1B,MAAM,MAAM,GAAI,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YAC1D,IAAI,MAAM;gBAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;YAC/C,IAAI,CAAC,eAAe,EAAE,CAAA;QACxB,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,OAAO,CAAC,CAAY;;QAC1B,IAAI,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,IAAI,0CAAE,WAAW,CAAA;YAAE,OAAM;QAC3C,CAAC,CAAC,cAAc,EAAE,CAAA;QAElB,wDAAwD;QACxD,MAAM,UAAU,GAAI,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,YAAY,CAAuB,CAAA;QACxF,IAAI,YAAY,GAAG,IAAI,CAAC,aAAa,CAAA;QACrC,IAAI,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAA;QAE7B,IAAI,CAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,EAAE,KAAI,UAAU,CAAC,EAAE,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACxD,YAAY,GAAG,UAAU,CAAC,EAAE,CAAA;YAC5B,MAAM,IAAI,GAAG,UAAU,CAAC,qBAAqB,EAAE,CAAA;YAC/C,QAAQ,GAAG,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAA;QACxE,CAAC;QAED,IAAI,CAAC,mBAAmB,EAAE,CAAA;QAE1B,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,cAAc,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC3G,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;QAEvC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,UAAU,KAAK,YAAY,EAAE,CAAC;YAC1E,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,OAAM;QACR,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAA;QAClC,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,UAAU,CAAC,CAAA;QACrE,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,OAAM;QACR,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAA;QACjC,MAAM,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;QACjD,IAAI,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC,CAAA;QAC/D,IAAI,QAAQ,KAAK,OAAO;YAAE,WAAW,EAAE,CAAA;QACvC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;QAEvC,IAAI,CAAC,eAAe,EAAE,CAAA;QAEtB,4BAA4B;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAA;QAC5D,MAAM,cAAc,GAAG,IAAI,GAAG,EAAmB,CAAA;QACjD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAA;QAEhF,kEAAkE;QAClE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;QAEvB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE;YAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAA;YACnE,IAAI,SAAS,GAAuB,IAAI,CAAA;YAExC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBAC1B,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBACzC,IAAI,CAAC,KAAK;oBAAE,OAAM;gBAElB,MAAM,IAAI,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAA;gBACzC,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;gBACjC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAA;gBAC/B,MAAM,EAAE,GAAG,IAAmB,CAAA;gBAE9B,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;oBAC1B,8CAA8C;oBAC9C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;oBACvC,SAAS,GAAG,EAAE,CAAA;gBAChB,CAAC;qBAAM,CAAC;oBACN,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;wBAAE,OAAM;oBAEhC,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,CAAA;oBAClD,EAAE,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAA;oBAE5B,qBAAqB,CAAC,GAAG,EAAE;wBACzB,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;wBAC/B,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,EAAE,CAAA;wBACvB,EAAE,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,CAAA;wBAExB,EAAE,CAAC,gBAAgB,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;oBAChG,CAAC,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAG,CAAC,CAAA;YACnC,MAAM,eAAe,GAAG,GAAG,EAAE;gBAC3B,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,SAAS,EAAE;oBACzB,OAAO,EAAE,IAAI;oBACb,QAAQ,EAAE,IAAI;oBACd,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE;iBACpC,CAAC,CACH,CAAA;YACH,CAAC,CAAA;YAED,IAAI,SAAS,EAAE,CAAC;gBACd,UAAU,CAAC,GAAG,EAAE;oBACd,MAAM,EAAE,GAAG,SAAwB,CAAA;oBACnC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;oBAC1C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;oBAChC,EAAE,CAAC,gBAAgB,CACjB,cAAc,EACd,GAAG,EAAE;wBACH,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;wBACnC,eAAe,EAAE,CAAA;oBACnB,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACf,CAAA;gBACH,CAAC,EAAE,GAAG,CAAC,CAAA;YACT,CAAC;iBAAM,CAAC;gBACN,eAAe,EAAE,CAAA;YACnB,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,mBAAmB;;QACzB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,MAAA,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,cAAc,IAAI,CAAC,aAAa,IAAI,CAAC,0CAAE,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,CAAA;QACpH,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACvB,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACvB,CAAC;IAED,OAAO,CAAC,OAA6B;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAA;YAClB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAA;QAChB,CAAC;QAED,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAA;YACjC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;YAClC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAA;YACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA;QAClC,CAAC;IACH,CAAC;IAED,MAAM;QACJ,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAA;QAClD,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAA;QAEjC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YAC/C,OAAO,GAAG,CAAC,GAAG,OAAO,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;QAC5C,CAAC;QAED,OAAO,IAAI,CAAA;QACP,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;YACjC,IAAI,EAAE,UAAU,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAA;YAClE,0FAA0F;YAC1F,IAAI,SAAS,GAAG,WAAW,IAAI,MAAM,CAAC,EAAE,CAAA;YAExC,OAAO,IAAI,CAAA;;sBAEG,IAAI,CAAC,MAAM;oBACb,IAAI,CAAC,IAAI;sBACP,MAAM;wBACJ,QAAQ;0BACN,UAAU;4BACR,MAAM,CAAC,cAAc,CAAC;qBAC7B,MAAM,CAAC,WAAW,CAAC;iBACvB,MAAM,CAAC,EAAE,IAAI,OAAO;wBACb,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;;SAG3C,CAAA;QACH,CAAC,CAAC;QACA,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,EAAE;QACrD,IAAI,CAAC,KAAK;YACV,CAAC,CAAC,IAAI,CAAA,EAAE;YACR,CAAC,CAAC,IAAI,CAAA,gCAAgC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B;KAChG,CAAA;IACH,CAAC;IAED,OAAO,CAAC,CAAQ;QACd,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QAElB,CAAC,CAAC,eAAe,EAAE,CAAA;IACrB,CAAC;IAED,IAAI,mBAAmB;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;;AA/aM,eAAM,GAAG;IACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2IF;CACF,AA7IY,CA6IZ;AAE4B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;uCAAgB;AAGhB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;2CAA0B;AAEpC;IAAhB,KAAK,EAAE;0CAAqC;AAC5B;IAAhB,KAAK,EAAE;uCAA+B;AAtJ5B,QAAQ;IADpB,aAAa,CAAC,SAAS,CAAC;GACZ,QAAQ,CAibpB","sourcesContent":["import i18next from 'i18next'\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/list-state/ox-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'\nimport { GridEmptyNote, renderEmptyNote } from '../empty-note-render.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 /*\n grid-template-rows: min-content only ever sizes the *first* row track - every row after\n it is implicit, and an implicit track with no grid-auto-rows falls back to the browser's\n default auto sizing, which is a looser (max-content-leaning) measurement than\n min-content. With enough records to wrap past one row, that mismatch showed up as a real\n gap: row 1 sized tight at ~201px, rows 2+ sized to ~319px while the cards inside still only\n filled ~201px of it (operato-board's board-list, 2026-09-23 - 17 boards, 7 per row).\n grid-auto-rows repeats the same rule for every implicit row, so all rows size alike.\n */\n grid-template-rows: min-content;\n grid-auto-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 /*\n 드래그 순서 바꾸기 (rows.reorderable) — board-list 의 board-tile-list.ts 에서 검증된 모양을\n 그대로 가져온다. 자리(placement)만 이 파일이 정하고, 지속(persist)은 소비자가 한다 — 그\n 경계는 위 types.ts RowsConfig.reorderable 문서에 있다.\n */\n [card][draggable='true'] {\n cursor: grab;\n transition:\n opacity 0.2s ease,\n transform 0.2s ease;\n }\n\n [card].dragging {\n opacity: 0.3;\n transform: scale(0.95);\n }\n\n [card].drop-before {\n border-left: 3px solid var(--md-sys-color-primary);\n }\n\n [card].drop-after {\n border-right: 3px solid var(--md-sys-color-primary);\n }\n\n [card].flip-moving {\n transition: transform 1s cubic-bezier(0.25, 0.1, 0.25, 1);\n }\n\n @keyframes ox-card-drop-land {\n 0% {\n opacity: 0;\n transform: scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n }\n\n [card].flip-dropped-hidden {\n opacity: 0;\n }\n\n [card].flip-dropped {\n animation: ox-card-drop-land 0.5s cubic-bezier(0.22, 0.6, 0.36, 1);\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?: GridEmptyNote\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 /* Drag-reorder state (`rows.reorderable`) - board-tile-list.ts's proven shape, generalized. */\n private _draggedId: string | null = null\n private _dropTargetId: string | null = null\n private _dropSide: 'before' | 'after' | null = null\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 this._wireReorder()\n }\n\n /*\n * `config.rows.reorderable` is read inside each handler, not at listener-registration time - the\n * listeners are cheap to hold and this sidesteps any question of whether `config` has already been\n * assigned by the time `firstUpdated` runs (it is a plain property set by the caller's template,\n * not guaranteed ordered against connection).\n */\n private _wireReorder() {\n this.renderRoot.addEventListener('dragstart', (e: Event) => {\n if (!this.config?.rows?.reorderable) return\n\n const target = (e.target as HTMLElement).closest('[card][id]') as HTMLElement | null\n if (!target?.id) return\n\n this._draggedId = target.id\n target.classList.add('dragging')\n ;(e as DragEvent).dataTransfer!.effectAllowed = 'move'\n ;(e as DragEvent).dataTransfer?.setData('text/plain', target.id)\n })\n\n this.renderRoot.addEventListener('dragover', (e: Event) => {\n if (!this.config?.rows?.reorderable || !this._draggedId) return\n e.preventDefault()\n ;(e as DragEvent).dataTransfer!.dropEffect = 'move'\n\n const target = (e.target as HTMLElement).closest('[card][id]') as HTMLElement | null\n if (!target?.id || target.id === this._draggedId) return\n\n const targetRect = target.getBoundingClientRect()\n const mousePos = (e as DragEvent).clientX - targetRect.left\n const side = mousePos < targetRect.width / 2 ? 'before' : 'after'\n\n if (this._dropTargetId !== target.id || this._dropSide !== side) {\n this._clearDropIndicator()\n this._dropTargetId = target.id\n this._dropSide = side\n target.classList.add(side === 'before' ? 'drop-before' : 'drop-after')\n }\n })\n\n this.renderRoot.addEventListener('dragleave', (e: Event) => {\n if (!this.config?.rows?.reorderable) return\n\n const target = (e.target as HTMLElement).closest('[card][id]') as HTMLElement | null\n if (target?.id === this._dropTargetId) {\n const related = (e as DragEvent).relatedTarget as HTMLElement | null\n if (!related || !target.contains(related)) {\n this._clearDropIndicator()\n }\n }\n })\n\n this.renderRoot.addEventListener('drop', (e: Event) => this._onDrop(e as DragEvent))\n\n this.renderRoot.addEventListener('dragend', (e: Event) => {\n if (!this.config?.rows?.reorderable) return\n\n this._clearDropIndicator()\n const target = (e.target as HTMLElement).closest('[card]')\n if (target) target.classList.remove('dragging')\n this._resetDragState()\n })\n }\n\n private _onDrop(e: DragEvent) {\n if (!this.config?.rows?.reorderable) return\n e.preventDefault()\n\n /* drop 이 먼저 계산한다 - dragleave 가 먼저 일어나 상태를 지울 수 있어서다. */\n const dropTarget = (e.target as HTMLElement).closest('[card][id]') as HTMLElement | null\n let dropTargetId = this._dropTargetId\n let dropSide = this._dropSide\n\n if (dropTarget?.id && dropTarget.id !== this._draggedId) {\n dropTargetId = dropTarget.id\n const rect = dropTarget.getBoundingClientRect()\n dropSide = e.clientX - rect.left < rect.width / 2 ? 'before' : 'after'\n }\n\n this._clearDropIndicator()\n\n const draggedEl = this._draggedId ? this.renderRoot.querySelector(`[card][id=\"${this._draggedId}\"]`) : null\n draggedEl?.classList.remove('dragging')\n\n if (!this._draggedId || !dropTargetId || this._draggedId === dropTargetId) {\n this._resetDragState()\n return\n }\n\n const records = [...this._records]\n const draggedIndex = records.findIndex(r => r.id === this._draggedId)\n if (draggedIndex === -1) {\n this._resetDragState()\n return\n }\n\n const draggedId = this._draggedId\n const [dragged] = records.splice(draggedIndex, 1)\n let targetIndex = records.findIndex(r => r.id === dropTargetId)\n if (dropSide === 'after') targetIndex++\n records.splice(targetIndex, 0, dragged)\n\n this._resetDragState()\n\n /* FLIP: 옮기기 전 자리를 먼저 잰다. */\n const cards = this.renderRoot.querySelectorAll('[card][id]')\n const firstPositions = new Map<string, DOMRect>()\n cards.forEach(card => firstPositions.set(card.id, card.getBoundingClientRect()))\n\n /* 낙관적 갱신 - 지속은 이 이벤트를 듣는 쪽의 몫이다(RowsConfig.reorderable 문서 참조). */\n this._records = records\n\n this.updateComplete.then(() => {\n const updatedCards = this.renderRoot.querySelectorAll('[card][id]')\n let droppedEl: HTMLElement | null = null\n\n updatedCards.forEach(card => {\n const first = firstPositions.get(card.id)\n if (!first) return\n\n const last = card.getBoundingClientRect()\n const dx = first.left - last.left\n const dy = first.top - last.top\n const el = card as HTMLElement\n\n if (card.id === draggedId) {\n /* 옮긴 카드는 다른 카드들이 밀리는 동안 숨겨 뒀다가 마지막에 착지시킨다. */\n el.classList.add('flip-dropped-hidden')\n droppedEl = el\n } else {\n if (dx === 0 && dy === 0) return\n\n el.style.transform = `translate(${dx}px, ${dy}px)`\n el.style.transition = 'none'\n\n requestAnimationFrame(() => {\n el.classList.add('flip-moving')\n el.style.transform = ''\n el.style.transition = ''\n\n el.addEventListener('transitionend', () => el.classList.remove('flip-moving'), { once: true })\n })\n }\n })\n\n const ids = records.map(r => r.id!)\n const dispatchReorder = () => {\n this.dispatchEvent(\n new CustomEvent('reorder', {\n bubbles: true,\n composed: true,\n detail: { ids, movedId: draggedId }\n })\n )\n }\n\n if (droppedEl) {\n setTimeout(() => {\n const el = droppedEl as HTMLElement\n el.classList.remove('flip-dropped-hidden')\n el.classList.add('flip-dropped')\n el.addEventListener(\n 'animationend',\n () => {\n el.classList.remove('flip-dropped')\n dispatchReorder()\n },\n { once: true }\n )\n }, 500)\n } else {\n dispatchReorder()\n }\n })\n }\n\n private _clearDropIndicator() {\n if (this._dropTargetId) {\n this.renderRoot.querySelector(`[card][id=\"${this._dropTargetId}\"]`)?.classList.remove('drop-before', 'drop-after')\n }\n this._dropTargetId = null\n this._dropSide = null\n }\n\n private _resetDragState() {\n this._draggedId = null\n this._dropTargetId = null\n this._dropSide = null\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, reorderable } = 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 /* Only a real record can be dragged - the trailing `appendable` placeholder has no id. */\n var draggable = reorderable && record.id\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 id=${record.id || nothing}\n draggable=${draggable ? 'true' : 'false'}\n card\n ></ox-record-card>\n `\n })}\n ${this.empty ? renderEmptyNote(this.emptyNote) : 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"]}
@@ -211,7 +211,7 @@ RecordCard.styles = [
211
211
  }
212
212
 
213
213
  :host(:hover) {
214
- background-color: var(--md-sys-color-surface);
214
+ background-color: var(--data-card-record-card-background-color-hover, var(--md-sys-color-surface));
215
215
  border: var(--data-card-record-card-border-hover);
216
216
  box-shadow: var(--data-card-record-card-boxshadow-hover);
217
217
  }
@@ -331,13 +331,44 @@ RecordCard.styles = [
331
331
  overflow: hidden;
332
332
  }
333
333
 
334
+ /*
335
+ The action row sits under the text by default. A catalog whose actions are one or two
336
+ small icons (board-ui's card lists) can instead park them in the card's bottom-right corner,
337
+ on the same line as the last text row, so the card does not spend a whole row on them:
338
+ --data-card-gutters-position: absolute; --data-card-gutters-inset: auto 8px 6px auto.
339
+ The host is already position: relative. The page then owes the text a right margin
340
+ (--data-card-item-margin) so a long name does not run under the icons.
341
+ */
334
342
  [gutters] {
335
343
  display: flex;
336
344
  flex-direction: row;
337
345
  gap: var(--spacing-small);
338
- margin: var(--spacing-small) var(--spacing-medium);
346
+ margin: var(--data-card-gutters-margin, var(--spacing-small) var(--spacing-medium));
339
347
  justify-content: end;
340
348
  align-items: stretch;
349
+ position: var(--data-card-gutters-position, static);
350
+ inset: var(--data-card-gutters-inset, auto);
351
+ }
352
+
353
+ /*
354
+ A quiet gutter (ColumnConfig.quiet) waits for the pointer. The row keeps its height either
355
+ way, so cards do not jump; only the buttons fade. On a device with no hover there is
356
+ nothing to wait for, so they are simply drawn.
357
+ */
358
+ [gutters] ox-card-gutter[quiet] {
359
+ opacity: 0;
360
+ transition: opacity 0.12s ease-out;
361
+ }
362
+
363
+ :host(:hover) [gutters] ox-card-gutter[quiet],
364
+ [gutters] ox-card-gutter[quiet]:focus-within {
365
+ opacity: 1;
366
+ }
367
+
368
+ @media (hover: none) {
369
+ [gutters] ox-card-gutter[quiet] {
370
+ opacity: 1;
371
+ }
341
372
  }
342
373
 
343
374
  ox-card-field {
@@ -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(--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"]}
1
+ {"version":3,"file":"record-card.js","sourceRoot":"","sources":["../../../src/data-card/record-card.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,+BAA+B,CAAA;AACtC,OAAO,sBAAsB,CAAA;AAC7B,OAAO,uBAAuB,CAAA;AAC9B,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAkB,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAG3D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAA;AACtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,kDAAkD,CAAA;AAE5F,kCAAkC;AAClC,MAAM,OAAO,GAA+B;IAC1C,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,KAAK;IACb,kCAAkC;CACnC,CAAA;AAED,SAAS,gBAAgB,CAAC,MAAc,EAAE,OAAmC;IAC3E,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,IAAI,OAAO,CAAA;QACpC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IACrD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAClD,CAAC;AACH,CAAC;AAED,MAAM,SAAS,GAAG,gBAAgB,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;AAGxD,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QAsMuB,WAAM,GAAgB,WAAW,CAAA;QACjC,SAAI,GAAc,SAAS,CAAA;QAC3B,WAAM,GAAgB,WAAW,CAAA;QACjC,aAAQ,GAAW,CAAC,CAAC,CAAA;QACjD;;;WAGG;QACqD,gBAAW,GAAY,KAAK,CAAA;QACpD,eAAU,GAAQ,KAAK,CAAA;QAG/C,iBAAY,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAkB,CAAA;QACjE,oBAAe,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAkB,CAAA;IA0KjF,CAAC;IAxKC,YAAY;QACV;;;UAGE;QACF,4DAA4D;QAC5D,2FAA2F;QAC3F,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;QAC5D,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,CAAA;QAElE,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAA;IACzE,CAAC;IAED,OAAO,CAAC,OAA6B;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC9C,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QACvC,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAElC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;YACjB,IAAI,CAAC,YAAY,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;YAEvC,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC1C,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,GAAG,UAAsB,CAAA;gBACjE,eAAe,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,2CAA2C,EAAE,eAAe,CAAC,CAAA;gBACvG,eAAe,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAA;YAC9F,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;IAED,MAAM;;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC9B,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,CAAA;QACrC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,IAAI,EAAE,CAAA;QAEjE,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;QACpH,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;QAEnG,MAAM,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAChG;;;;;;WAMG;QACH,MAAM,cAAc,GAAG,eAAe,CAAC,CAAC,CAAC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACnF,MAAM,YAAY;QAChB;;;WAGG;QACH,CAAA,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,MAAM,0CAAE,WAAW;YACpC,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;gBAC5B,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;gBAC3B,CAAC,CAAC,cAAc,KAAK,IAAI,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,EAAE,CAAC,CAAA;QAEvF,MAAM,OAAO,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,CAAA;QAE3F,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,IAAI,SAAS,CAAA;YAEb,QAAQ,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;gBACjC,KAAK,GAAG;oBACN,SAAS,GAAG,MAAM,CAAA;oBAClB,MAAK;gBACP,KAAK,GAAG;oBACN,SAAS,GAAG,KAAK,CAAA;oBACjB,MAAK;gBACP,KAAK,GAAG;oBACN,SAAS,GAAG,QAAQ,CAAA;oBACpB,MAAK;YACT,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAA;QACP,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA,mBAAmB,SAAS,aAAa,CAAC,CAAC,CAAC,IAAI,CAAA,EAAE;QAClE,eAAe;YACf,CAAC,CAAC,YAAY;gBACZ,CAAC,CAAC,IAAI,CAAA;;0BAEU,QAAQ;wBACV,eAAe;wBACf,MAAM;uBACP,MAAM,CAAC,CAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,IAAI,KAAI,EAAE,CAAC;8BAC5B;gBACpB,CAAC,CAAC,IAAI,CAAA;gBACA,CAAA,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,IAAI,EAAC,CAAC,CAAC,IAAI,CAAA,YAAY,cAAc,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE;gBAC3E,CAAA,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,KAAK,KAAI,EAAE;mBACxB;YACX,CAAC,CAAC,IAAI,CAAA,EAAE;;;;YAIJ,WAAW,CAAC,GAAG,CACf,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;4BAEL,QAAQ;0BACV,MAAM;0BACN,MAAM;yBACP,MAAM,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,KAAI,EAAE,CAAC;wBAC3B,GAAG,IAAI,CAAC;wBACR,GAAG,IAAI,CAAC;;aAEnB,CACF;;UAED,YAAY,CAAC,MAAM,GAAG,CAAC;YACvB,CAAC,CAAC,IAAI,CAAA;;kBAEE,YAAY,CAAC,GAAG,CAChB,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;kCAEL,QAAQ;gCACV,MAAM;gCACN,MAAM;+BACP,MAAM,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,KAAI,EAAE,CAAC;;mBAEtC,CACF;;aAEJ;YACH,CAAC,CAAC,IAAI,CAAA,EAAE;;;;UAIR,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;YACrB,OAAO,IAAI,CAAA;;0BAEK,QAAQ;wBACV,MAAM;wBACN,MAAM;uBACP,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;WAE/B,CAAA;QACH,CAAC,CAAC;;KAEL,CAAA;IACH,CAAC;IAED,eAAe;QACb,IAAI,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAA;QACrD,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;QAEnC,wCAAwC;QACxC,IAAI,OAAO,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,UAAU,CAAC,CAAA;YAC1E,KAAK,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,cAAc,CAAC,CAAA;QACjG,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,iBAAiB,CAClC,IAAI,CAAC,MAAM,CAAC,OAAO,EACnB,IAAI,CAAC,MAAM,EACX,IAAI,CAAC,QAAQ,EACb,IAAI,EACJ;YACE,KAAK;SACN,EACD,GAAG,EAAE;YACH,OAAO,IAAI,CAAC,WAAW,CAAA;QACzB,CAAC,CACF,CAAA;IACH,CAAC;;AA3XM,iBAAM,GAAG;IACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiMF;CACF,AAnMY,CAmMZ;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;0CAAkC;AACjC;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;wCAA4B;AAC3B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;0CAAkC;AACjC;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;4CAAsB;AAKO;IAAvD,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;+CAA6B;AACpD;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;8CAAwB;AA/M5C,UAAU;IADtB,aAAa,CAAC,gBAAgB,CAAC;GACnB,UAAU,CA6XtB","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"]}
@@ -821,7 +821,7 @@ DataGridField.styles = [
821
821
 
822
822
  color: var(--md-sys-color-on-primary);
823
823
  background-color: var(--md-sys-color-primary);
824
- border-radius: var(--data-card-item-btn-border-radius, var(--md-sys-shape-corner-tiny));
824
+ border-radius: var(--data-card-item-btn-border-radius, var(--md-sys-shape-corner-extra-small, 4px));
825
825
 
826
826
  font-size: 0.8rem;
827
827
  line-height: 0.8rem;
@@ -1 +1 @@
1
- {"version":3,"file":"data-grid-field.js","sourceRoot":"","sources":["../../../src/data-grid/data-grid-field.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport i18next from 'i18next'\nimport { css, html, LitElement, PropertyValues, TemplateResult } from 'lit'\n// import { guard } from 'lit/directives/guard.js'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { actionHeight } from '@operato/styles/action-heights.js'\nimport { TooltipReactiveController } from '@operato/utils'\n\nimport { ZERO_COLUMN } from '../configure/zero-config.js'\nimport { editorOpensPopup } from '../editors/registry.js'\nimport { ColumnConfig, GristRecord } from '../types.js'\n\nconst DEFAULT_TEXT_ALIGN = 'left'\n\n@customElement('ox-grid-field')\nexport class DataGridField extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n\n align-items: center;\n justify-content: var(--data-grid-field-justify-content, flex-start);\n position: relative;\n\n white-space: nowrap;\n background-color: var(--grid-record-background-color);\n padding: var(--grid-record-padding);\n border: 1px solid transparent;\n border-width: 1px 0;\n border-bottom: var(--grid-record-border-bottom);\n\n font-size: var(--grid-record-wide-fontsize);\n min-height: 30px;\n box-sizing: border-box;\n }\n\n /*\n * ── 팝업으로 고치는 칸에는 **표식이 있다** ──────────────────────────────\n *\n * 그런 칸은 글자 칸과 똑같이 생겼다. 열어 보기 전에는 고칠 수 있는지도, 어떻게 고치는지도\n * 모른다 — 「실제 더블클릭을 하기 전에는 모르는 막막함」(사용자, 2026-09-05).\n *\n * **마우스를 올렸을 때 보인다.** 늘 보이게 두었더니 한 화면에 54개가 깔려 표가 시끄러웠다\n * (2026-09-05 · 사용자 판단). 값을 읽으러 온 눈에 장치가 줄마다 끼어든다.\n *\n * **손가락 화면에서는 늘 보인다.** 거기엔 올리는 동작이 없어서 감추면 길이 아예 없어진다.\n * 그 화면들은 표가 성기므로 늘 보여도 시끄럽지 않다.\n *\n * ── 네모와 펜을 쓴다 ─────────────────────────────────────────────────\n * 처음에 삼각형(arrow_drop_down)을 썼다. 참조 칸만 생각한 선택이었다 — 「목록에서 고른다」.\n *\n * 그런데 이 표식은 **팝업으로 여는 편집기 전부**에 붙는다. JSON · 코드 · 권한 편집기도\n * 그렇고, 거기서 삼각형은 거짓말이다. 고를 목록이 없다.\n *\n * ── 모양이 **어디서 고치는지**를 말한다 ────────────────────────────────\n *\n * edit 칸 안에서 고친다\n * edit_square 팝업이 열린다\n *\n * 표식이 있으면 고칠 수 있는 칸이고, 없으면 못 고치는 칸이다. **읽기 전용에는 붙지 않는다.**\n *\n * 처음에 삼각형(arrow_drop_down)을 썼다 — 참조 칸만 생각한 선택이었다. 이 표식은 JSON ·\n * 코드 · 권한 편집기에도 붙고, 거기서 삼각형은 고를 목록이 있다는 거짓말이 된다.\n *\n * 누를 자리는 22px 이다. 33px 줄에서 더블클릭을 겨누는 것보다 한 번 누르는 편이 쉽다.\n */\n /* 마우스 화면 — 칸에 올렸을 때만. 자리는 늘 잡아 두므로 글자가 밀리지 않는다. */\n @media (pointer: fine) {\n [popup-hint] {\n opacity: 0;\n transition: opacity 0.12s ease;\n }\n\n :host(:hover) [popup-hint] {\n opacity: 1;\n }\n }\n\n /*\n * 손가락 화면 — **고른 칸에만 보인다.**\n *\n * 올리는 동작이 없으니 호버로는 아무것도 못 보여 준다. 그렇다고 늘 보이게 두면 좁은\n * 화면이 표식으로 뒤덮인다 — 시끄러우면 정작 필요한 표식도 안 보인다.\n *\n * 고른 칸 하나에만 두면 둘 다 풀린다. 두드려 고르는 것이 그 화면의 첫 동작이므로,\n * 고르는 순간 「이 칸은 이렇게 고친다」가 나타난다.\n */\n @media (pointer: coarse) {\n [popup-hint] {\n display: none;\n }\n\n :host([focused]) [popup-hint] {\n display: flex;\n }\n }\n\n /*\n * ── 표식은 늘 **칸의 끝**에 있다. 정렬을 따라가지 않는다 (2026-09-05) ──────\n *\n * 찾는 데 드는 값이 커진다 정렬은 열마다 다르다(이름은 왼쪽, 수량은 오른쪽). 표식이\n * 그것을 따라가면 자리가 열마다 달라져 눈이 매번 찾아야 한다.\n * 끝에 고정하면 규칙이 하나다 — 「손잡이는 칸 끝에 있다」\n * 남의 칸처럼 보인다 오른쪽 정렬 칸의 값 **앞**에 표식을 두면 왼쪽 칸의 값 뒤에\n * 붙어 보인다. 어느 칸의 손잡이인지 흐려진다\n *\n * ── **값이 없는 쪽**에 붙는다 ──────────────────────────────────────\n * 왼쪽으로 붙인 칸의 빈자리는 오른쪽이고, 오른쪽으로 붙인 칸(수량 · 금액)의 빈자리는\n * 왼쪽이다. 정렬의 반대쪽에 두면 표식이 값 위에 얹히는 일 자체가 없어진다.\n *\n * 자리를 비워 두는 방법도 해 봤다 — 칸의 오른쪽을 24px 늘 비우는 것. 되돌렸다.\n * **자료가 먼저다**(사용자, 2026-09-05): 마우스를 올렸을 때만 보이는 안내 때문에 고칠 수\n * 있는 열 전부가 영구히 좁아지는 것은 값을 잘못 매긴 거래다.\n *\n * 가운데 정렬 칸은 양쪽이 다 비므로 끝에 둔다.\n */\n [popup-hint] {\n position: absolute;\n inset-inline-end: 2px;\n top: 50%;\n transform: translateY(-50%);\n\n --md-icon-size: 16px;\n font-size: 16px;\n width: 16px;\n height: 16px;\n padding: 3px;\n border-radius: 50%;\n\n /*\n * ── 채우지 않는다 (2026-09-05) ──────────────────────────────────\n * md-icon 이 물려주는 값은 FILL 1 이라 글리프가 통짜로 나온다. 두 가지가 어긋난다.\n *\n * 뜻 채운 글리프는 「지금 그 상태다」를 말한다(고른 것 · 켠 것). 이 표식이 말하는\n * 것은 상태가 아니라 **누르면 할 수 있는 일**이다. 그건 윤곽선의 몫이다\n * 밀도 표는 글자밭이다. 같은 16px 에서 채운 글리프는 잉크가 몇 곱절이라 값보다\n * 먼저 눈에 들어온다. 안내가 자료보다 진해서는 안 된다\n *\n * edit_square 는 특히 그렇다 — 채우면 네모가 덩어리가 되어 「종이 위의 연필」이라는\n * 그림이 뭉개진다. 진하기는 색으로 조절한다(아래 :hover).\n */\n /*\n * 굵기는 한 단 올린다. 16px 에서 400 짜리 윤곽선은 획이 배경에 묻는다 — 채움을 뺀 만큼\n * 획으로 되받는다. 잉크가 늘지 않으면서 형태는 또렷해진다.\n */\n font-variation-settings:\n 'FILL' 0,\n 'wght' 500,\n 'opsz' 20;\n\n /*\n * ── 값 **위에 그려지되, 값보다 앞서지 않는다** ──────────────────\n * 자리가 겹치면 표식이 위에 얹힌다. 그래도 이것은 거들어 주는 것이지 읽을 것이 아니다 —\n * 본문 색으로 두었더니 값과 같은 무게가 되어 눈이 먼저 표식으로 갔다(사용자, 2026-09-05).\n * 한 단 낮춰 라벨 색으로 둔다. 손이 표식 위에 오면 그때 본문 색이 된다.\n */\n color: var(--ui-muted, #57676f);\n cursor: pointer;\n\n transition:\n background-color 0.12s ease,\n color 0.12s ease;\n }\n\n /* 오른쪽으로 붙인 칸 — 값이 오른쪽 끝에 있으므로 표식은 왼쪽의 빈자리로 간다. */\n :host([mark-start]) [popup-hint] {\n inset-inline-end: auto;\n inset-inline-start: 2px;\n }\n\n [popup-hint]:hover {\n color: var(--ui-ink, #17272f);\n background-color: var(--ui-hover, color-mix(in srgb, currentColor 12%, transparent));\n }\n\n :host([multiline]) {\n white-space: pre-line;\n }\n\n /*\n * ── 칸이 쓸 수 있는 용어 (2026-08-31) ──────────────────────────────────\n * 렌더러가 그린 것은 이 부품의 shadow DOM 안에 들어간다. 그래서 **화면의 CSS 가 닿지 않는다.**\n * 두 줄 칸이나 상태 알약을 그리려던 화면들이 그 벽에 막혀, 값에 인라인 style 을 박거나 칸마다\n * 부품을 새로 만들었다.\n *\n * 목록이면 어디서나 같은 것을 필요로 하므로 여기서 준다. 이름은 뜻으로 짓는다 —\n * 「늦었다」가 아니라 「심각하다(crit)」다. 늦은 것은 이 표의 일이 아니고 화면의 일이다.\n *\n * .sub 둘째 줄. 「1,200 EA」 아래의 「BOLT-8」처럼 딸린 사실\n * .mono 고정폭. 번호·시각·수량처럼 자릿수를 비교하는 값\n * .none 값이 없음. 빈 칸으로 두면 「아직 안 읽었다」와 구별되지 않는다\n * .pill 알약. good·warn·crit·info 로 뜻을 준다\n * .crit · .warn · .good 글자 강조\n *\n * 값은 --ui-* 토큰에서 오고 폴백을 둔다 — 테마가 없어도 무너지지 않는다.\n */\n :host([twoline]) {\n display: block;\n white-space: normal;\n line-height: 1.35;\n }\n\n .sub {\n display: block;\n margin-top: 2px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-faint, #8a959c);\n }\n\n .mono {\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-variant-numeric: tabular-nums;\n }\n\n /*\n * 그 줄이 무엇인지 가리키는 값 — 작업지시 번호, 설비 이름처럼. 두 줄 칸의 첫 줄이 이것이고\n * 둘째 줄(.sub)이 딸린 사실이다. 굵기만 올린다. 고정폭은 쓰지 않는다 — 자릿수를 세로로 견줄\n * 값이 아니고, 같은 글자 수에도 폭이 넓어져 옆 칸을 밀어낸다.\n */\n .id {\n font-weight: 620;\n }\n\n .none {\n color: var(--ui-faint, #8a959c);\n }\n\n .crit {\n color: var(--ui-crit, #c93f30);\n font-weight: 640;\n }\n\n .warn {\n color: var(--ui-warn, #9f5e02);\n font-weight: 640;\n }\n\n .good {\n color: var(--ui-good, #297d39);\n font-weight: 640;\n }\n\n /*\n * 값과 표식을 한 줄에 담는 영역이다. **간격과 높이를 여기서 정한다.**\n *\n * 처음에는 :not(.pill) + .pill { margin-left } 로 띄우려 했는데 안 걸렸다. 렌더러가 값을\n * 텍스트 노드로 내므로 알약의 앞 형제가 **요소가 아니고**, CSS 형제 선택자는 텍스트 노드를 보지\n * 못한다. 알약이 첫 요소이기도 해서 :first-child 로도 가려지지 않는다(실측 확인).\n *\n * 높이도 같은 자리에서 잡는다. baseline 으로 두면 14px 글자와 11.5px 알약의 기준선을 맞추느라\n * 알약이 내려앉는다.\n */\n /* 값이 있지만 눈에 걸릴 것은 아닌 것 — 「보통」 같은 기본값. .none(값 없음)과 구별한다. */\n .quiet {\n color: var(--ui-muted, #57676f);\n }\n\n /*\n * 별 같은 작은 기호를 글자 크기로 맞춘다. md-icon 의 기본은 24px 이라 줄 높이를 밀어낸다.\n * 색은 물려받는다 — 개수가 크기를 말하고 색은 담는 쪽이 정한다.\n */\n .star {\n --md-icon-size: 15px;\n font-size: 15px;\n width: 15px;\n height: 15px;\n color: inherit;\n }\n\n /* 별처럼 여럿이 붙는 기호는 사이를 좁게 둔다. 단어 사이의 6px 은 여기서 너무 넓다. */\n .line:has(.star) {\n gap: 1px;\n }\n\n /*\n * 셀 안의 아이콘 버튼. 그 줄에 하는 동작(들어가기 · 열기)을 담는다.\n *\n * 셀의 클릭 경로를 타지 않고 스스로 받으려는 렌더러가 쓴다. 그 경로에는 동작이 견딜 수 없는 관문이\n * 둘 있다.\n *\n * data-grid-body-click-handler.ts if (this.editTarget) return\n * data-grid-body-click-handler.ts (e.target).closest(\"ox-grid-field\")\n *\n * 앞은 다른 칸이 편집 중이면 첫 누름을 먹고, 뒤는 셀 경계를 스치면 칸을 못 찾는다. 진짜 button 을\n * 그리면 둘을 다 피하고 키보드로도 닿는다.\n *\n * 이 규칙이 여기 있는 이유: 렌더러의 결과는 이 셀의 그림자 안에 그려지므로 **페이지의 스타일이\n * 닿지 않는다.** 셀 안의 어휘는 셀이 갖는다.\n *\n * host 자식으로 적은 이유: 이 파일 아래의 host 자식 button 규칙이 채운 버튼을 그리고, 특이도가\n * 같으면 **뒤에 오는 쪽이 이긴다.** 그것 없이 두면 이 버튼이 채운 버튼의 모양으로 나왔다\n * (어두운 바탕 · 흰 글자, 실측 rgb(38, 46, 56)).\n *\n * 둘의 뜻이 다르다. 채운 버튼은 **실행하는 동작**이고, 이 버튼은 **이동**이다. 그래서 평소에는\n * 흐리고 배경이 없다.\n *\n * 배경은 **칸을 채우지 않고 동그라미로** 그린다. 칸을 다 채우면 「이 셀이 골라졌다」로 읽힌다 —\n * 표에서 채워진 사각형은 선택의 뜻이다. 동그라미는 버튼의 뜻이다.\n */\n :host > button.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: ${actionHeight('--cell-action-height')};\n height: ${actionHeight('--cell-action-height')};\n flex: none;\n /*\n * 누를 자리는 26px 로 두되 **줄 높이는 밀지 않는다.**\n *\n * 이 버튼이 들어간 칸 하나 때문에 표의 모든 줄이 46px 이 됐다(글자만 있는 줄은 38px). 스무 줄이면\n * 240px 을 잃는다. [확인함] grid-template-rows: 46px 34px\n *\n * 그렇다고 26px 아래로 줄이면 손가락으로 못 누른다(가로세로 24px 이 최소다). 그래서 상자는 26px\n * 로 두고 위아래 3px 씩을 여백에서 되돌려, 자리는 20px 만 차지하게 한다 — 글자 한 줄과 같다.\n */\n margin-top: calc((20px - ${actionHeight('--cell-action-height')}) / 2);\n margin-bottom: calc((20px - ${actionHeight('--cell-action-height')}) / 2);\n border: none;\n border-radius: 50%;\n background: transparent;\n padding: 0;\n color: var(--ui-faint, #8a959c);\n cursor: pointer;\n transition:\n background-color 0.12s ease,\n color 0.12s ease;\n }\n\n :host > button.icon:hover {\n background-color: var(--ui-hover, color-mix(in srgb, currentColor 12%, transparent));\n color: var(--ui-ink, #17272f);\n }\n\n :host > button.icon:active {\n background-color: var(--ui-active, color-mix(in srgb, currentColor 22%, transparent));\n }\n\n /* 키보드로 온 초점만 테를 보인다. 선택해서 온 초점에는 보이지 않는다. */\n :host > button.icon:focus-visible {\n outline: 2px solid var(--ui-accent, #00727e);\n outline-offset: -2px;\n }\n\n /*\n * 값을 함께 담는 버튼. 「4 ›」처럼 숫자와 화살이 나란히 놓인다.\n *\n * 아이콘만으로는 그 줄에 무엇이 있는지 말하지 못한다. 값이 뜻을 나르고 화살이 「누를 수 있다」를\n * 말한다. 그래서 동그라미가 아니라 알약 모양이다.\n */\n :host > button.icon.wide {\n width: auto;\n min-width: 44px;\n gap: 0;\n padding: 0 2px 0 7px;\n border-radius: 999px;\n font: inherit;\n font-size: var(--grid-record-wide-fontsize);\n font-variant-numeric: tabular-nums;\n }\n\n :host > button.icon.wide md-icon {\n --md-icon-size: 16px;\n font-size: 16px;\n width: 16px;\n height: 16px;\n }\n\n :host > button.icon md-icon {\n --md-icon-size: 18px;\n font-size: 18px;\n width: 18px;\n height: 18px;\n }\n\n .line {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n }\n\n /* 값이 길면 값이 줄고 표식은 그대로 남는다 — 표식이 먼저 잘리면 읽을 수 없다. */\n .line > :first-child {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* 표식 — 상태 같은 짧은 값에 두른다. 점이 색을 나르고 글자가 뜻을 나른다. */\n .pill {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 1px 8px;\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: 999px;\n font-size: var(--ui-t-small, 11.5px);\n font-weight: 400;\n color: var(--ui-muted, #57676f);\n white-space: nowrap;\n }\n\n /*\n * 색만으로 뜻을 전하지 않는다 — 알약 안의 글자가 함께 말한다. 점은 거드는 것이다.\n *\n * align-self: center 가 필요하다. 알약은 flex 이고 점은 그 안의 항목인데, 높이를 6px 로\n * 주어도 교차축에서 늘어나 **6×11.5 짜리 세로 타원**이 됐다(원이 아니라 길쭉했다).\n * .pill 의 align-items: center 는 부모 쪽 값이라 이 항목의 stretch 를 막지 못한다.\n */\n .pill .dot {\n align-self: center;\n width: 6px;\n height: 6px;\n /*\n * 이 파일 아래에 span, pre 의 min-height: 1em 이 있다. 값이 빈 칸에서도 줄 높이를\n * 유지하려는 규칙인데, 점도 span 이라 거기 걸려 **높이가 1em(11.5px)까지 늘어났다** —\n * 그래서 6px 을 줘도 6×11.5 세로 타원이 됐다. 여기서 되돌린다.\n */\n min-height: 0;\n border-radius: 50%;\n background: currentColor;\n flex: none;\n }\n\n .pill.good {\n color: var(--ui-good, #297d39);\n border-color: color-mix(in srgb, var(--ui-good, #297d39) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.info {\n color: var(--ui-accent, #00727e);\n border-color: color-mix(in srgb, var(--ui-accent, #00727e) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.warn {\n color: var(--ui-warn, #9f5e02);\n border-color: color-mix(in srgb, var(--ui-warn, #9f5e02) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.crit {\n color: var(--ui-crit, #c93f30);\n border-color: color-mix(in srgb, var(--ui-crit, #c93f30) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.quiet {\n color: var(--ui-faint, #8a959c);\n }\n\n :host([gutter]) {\n padding: var(--grid-gutter-padding);\n text-align: center;\n }\n\n :host([gutter]) * {\n cursor: default;\n overflow: hidden;\n }\n\n :host([editing]) {\n border-top: var(--grid-record-editing-border);\n border-bottom: var(--grid-record-editing-border);\n }\n\n /*\n * 초점이 갔다고 **줄을 나누는 선을 바꾸지 않는다.**\n *\n * 전에는 여기서 --grid-record-border-bottom 을 초점 표식 값으로 덮어썼다. 그래서 표식을\n * 감추려고 그 토큰을 none 이나 투명으로 두면 **그 셀만 줄 선이 사라졌다.** 한 토큰이 「초점\n * 표식」과 「줄을 나누는 선」 두 가지를 겸하고 있었던 것이다.\n *\n * 초점 표식은 outline 이 그린다(data-grid-body-style.ts). 줄 선은 줄 선으로 남는다.\n */\n\n :host([dirty])::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n\n width: 0px;\n height: 0px;\n border-top: 9px solid red;\n border-left: 9px solid transparent;\n }\n\n span,\n pre {\n display: block;\n text-overflow: ellipsis;\n overflow: hidden;\n min-height: 1em;\n }\n\n :host([multiline]) span,\n :host([multiline]) pre {\n text-overflow: initial;\n overflow: visible;\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n }\n\n * {\n margin: 0;\n text-align: var(--data-grid-field-text-align);\n }\n\n *[center] {\n flex: none;\n margin: 0 auto;\n }\n\n :host > button {\n display: flex;\n gap: var(--spacing-small);\n border: 0;\n align-items: center;\n padding: var(--spacing-small);\n\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-primary);\n border-radius: var(--data-card-item-btn-border-radius, var(--md-sys-shape-corner-tiny));\n\n font-size: 0.8rem;\n line-height: 0.8rem;\n\n md-icon {\n border: 0;\n padding: 0;\n background-color: unset;\n }\n }\n\n :host([danger]) > button {\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-error);\n border-color: var(--md-sys-color-error);\n }\n\n :host([danger]) > button:hover {\n color: var(--md-sys-color-on-secondary);\n background-color: var(--md-sys-color-secondary);\n border-color: var(--md-sys-color-secondary);\n }\n `\n ]\n\n @property({ type: String, attribute: true }) align = DEFAULT_TEXT_ALIGN\n @property({ type: Object }) record: GristRecord = {}\n @property({ type: Object }) column: ColumnConfig = ZERO_COLUMN\n @property({ type: Number }) rowIndex = 0\n @property({ type: Number }) columnIndex = 0\n @property({ type: Boolean }) editing = false\n @property({ type: Boolean, reflect: true }) multiline = false\n @property({ type: Object }) value = {}\n @property({ type: String }) valueWith: string | null = null\n @property({ attribute: false }) emphasized: any = false\n @property({ type: String, attribute: true }) fixed?: string\n @property({ type: String }) type?: string\n @property({ type: Boolean }) isWorking: boolean = false\n\n /* check in tree의 변화에 대응하기 위함임. 만일, record control 오브젝트로 통일되면 대체될 것임. */\n @property({ type: String }) checked?: string\n\n private _editCancelled?: boolean\n private _onFieldChange: (e: Event) => void = e => {}\n private _onKeydownInEditingMode: (e: KeyboardEvent) => void = e => {}\n\n private tooltipController?: TooltipReactiveController = new TooltipReactiveController(this)\n\n render(): TemplateResult {\n if (!this.column) {\n return html``\n }\n\n var { value, column, record, rowIndex } = this\n var { renderer, editor } = column.record\n\n return html`\n ${this.editing\n ? editor?.call(this, value, column, record, rowIndex, this)\n : renderer?.call(this, value, column, record, rowIndex, this)}\n <!--\n **편집 중에도 표식을 남긴다.**\n\n 칸을 누르면 편집 상태가 되고, 그때는 렌더러 대신 편집기가 그려진다. 표식을 !editing 으로\n 묶어 두었더니 **칸에 들어간 순간 사라졌다** — 들어간 사람에게 여는 길이 없어지는 셈이고,\n 다시 더블클릭이나 Enter 를 짐작해야 한다. 그 짐작을 없애려고 만든 표식이다.\n\n 팝업 편집기에만 붙으므로(opensPopup) 글자를 치는 편집기를 가릴 일은 없다.\n -->\n <!--\n ── 칸 안에서 고치는 중에는 표식을 걷는다 ────────────────────────────────\n 입력칸이 이미 그 자리에 있다. 펜을 겹쳐 두면 글자를 가리고, 고치는 중인 사람에게\n 「고칠 수 있다」를 다시 말할 이유도 없다.\n\n 팝업 쪽은 남긴다 — 편집 상태에 들어가도 팝업은 아직 안 열렸고, 그 표식이 여는 길이다.\n -->\n ${this.editMark && !(this.editing && this.editMark === 'inline')\n ? html`<md-icon\n popup-hint\n ?inline=${this.editMark === 'inline'}\n title=${i18next.t('text.grid-open-editor', { defaultValue: 'press to edit' })}\n @pointerdown=${async (e: Event) => {\n /*\n * **click 이 아니라 pointerdown 이다.**\n *\n * 이 표에서는 click 이벤트가 나지 않는다 — 몸통이 pointer 단계에서 기본 동작을\n * 막는다. document 에 capture 로 click 을 걸어 두고 표식을 선택해 봐도 한 번도\n * 울리지 않았다(2026-09-05 계측).\n */\n e.stopPropagation()\n e.preventDefault()\n\n /*\n * ── 포커스는 여기서 변경하지 않는다 ──────────────────────────────\n *\n * 팝업이 닫히면 오버레이가 **열릴 때 포커스가 있던 자리**로 돌려준다. 그런데 위에서\n * preventDefault 를 하므로 브라우저가 이 칸으로 포커스를 옮겨 주지 않고, 그래서\n * 팝업을 닫으면 엉뚱한 곳으로 간다(페이지 검색창이었다).\n *\n * 두 가지를 해 봤고 **둘 다 팝업이 안 열리게 만들었다.**\n *\n * focus-change 이벤트 몸통이 그 갱신에서 editTarget 을 지운다(shouldUpdate)\n * ime-buffer 에 직접 focus 같은 결과\n *\n * 이 표는 포커스를 ime-buffer 라는 숨은 입력칸에 담아 두고, 편집 상태를 그 갱신\n * 주기에 얹고 있다. 그 둘이 얽혀 있어서 밖에서 포커스를 옮기면 편집이 취소된다.\n *\n * 표의 포커스 모델을 손대야 풀리는 문제라 여기서 우회하지 않는다.\n * [확인함] 2026-09-05 · 두 방법 모두 팝업이 안 열림\n */\n\n this.dispatchEvent(\n new CustomEvent('start-edit', {\n bubbles: true,\n composed: true,\n detail: { row: this.rowIndex, column: this.columnIndex }\n })\n )\n\n /*\n * 칸 안에서 고치는 편집기는 여기서 끝난다 — 편집이 열리면 그 자리에 입력칸이 뜬다.\n * 팝업 편집기는 한 걸음 더 필요하다.\n */\n if (this.editMark !== 'popup') {\n return\n }\n\n /*\n * ── 편집을 열고, **그다음** 더블클릭을 편집기에 보낸다 ──────────────\n *\n * 사람이 더블클릭할 때의 순서를 계측해 보면 이렇다.\n *\n * startEditTarget 첫 클릭이 편집을 연다 → 편집기가 그려진다\n * dblclick 그다음에 도착해 **이미 있는 편집기**가 받아 팝업을 연다\n *\n * 편집기마다 여는 메서드 이름이 달라서 부품이 직접 부를 수는 없다. 사람이 하던\n * 동작을 같은 순서로 보내면 경로가 하나로 남는다.\n */\n for (let i = 0; i < 5 && !this.editing; i++) {\n await new Promise(resolve => requestAnimationFrame(resolve))\n }\n\n await this.updateComplete\n\n /*\n * Both events, in the order a browser sends them.\n *\n * The editors split on which one they open from, and sending only `dblclick`\n * reached two of the eight:\n *\n * _onclick json5 · code · resource-code · privilege · json · resource-id\n * _ondblclick resource-object · resource-object-legacy\n *\n * Those last two define `_onclick` as a no-op, so nothing opens twice.\n * [확인함] 2026-09-07 · 사용자 보고 — 코드 편집기가 아이콘으로 안 열렸다\n */\n const editor = this.renderRoot.firstElementChild as HTMLElement | null\n editor?.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }))\n editor?.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, composed: true }))\n }}\n >${this.editMark === 'popup' ? 'edit_square' : 'edit'}</md-icon\n >`\n : ''}\n `\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('editing')) {\n if (this.editing) {\n this._onKeydownInEditingMode = ((e: KeyboardEvent) => {\n if (e.key === 'Esc' || e.key === 'Escape') {\n /* 편집 취소 */\n this._editCancelled = true\n }\n }).bind(this)\n\n this._onFieldChange = ((e: Event) => {\n this._editCancelled && e.stopPropagation()\n }).bind(this)\n\n delete this._editCancelled\n this.addEventListener('field-change', this._onFieldChange)\n this.addEventListener('keydown', this._onKeydownInEditingMode)\n } else {\n this.removeEventListener('field-change', this._onFieldChange)\n this.removeEventListener('keydown', this._onKeydownInEditingMode)\n }\n }\n\n if (changes.has('column')) {\n /*\n * 정렬은 **넣을 때만이 아니라 되돌릴 때도** 손대야 한다.\n *\n * 이 요소는 재사용된다. 전에는 기본값이 아닐 때만 인라인 값을 넣고 지우지 않았다. 그래서 한 번\n * 오른쪽 정렬 칸을 그린 셀이 다음에 왼쪽 정렬 칸을 그려도 오른쪽에 남았다.\n *\n * [확인함] 품질 화면 「판단」 칸에서 머리글은 x=380(왼쪽)인데 값은 x=452(flex-end)였다. 숫자 칸을\n * 오른쪽으로 바꾼 뒤 드러났다 — 오른쪽 정렬 칸이 드물던 동안은 눈에 안 띄었다. 2026-09-02\n */\n var align = this.column.record.align || DEFAULT_TEXT_ALIGN\n\n if (align == DEFAULT_TEXT_ALIGN) {\n this.style.removeProperty('--data-grid-field-justify-content')\n this.style.removeProperty('--data-grid-field-text-align')\n } else {\n let justify = 'center'\n switch (align) {\n case 'right':\n justify = 'flex-end'\n break\n }\n this.style.setProperty('--data-grid-field-justify-content', justify)\n this.style.setProperty('--data-grid-field-text-align', align)\n }\n\n /* 표식은 값의 반대쪽 빈자리에 선다 — 위 :host([mark-start]) 주석 참고. */\n this.toggleAttribute('mark-start', align === 'right')\n\n const { danger } = this.column\n if (danger) {\n this.setAttribute('danger', '')\n } else {\n this.removeAttribute('danger')\n }\n }\n\n if (changes.has('fixed')) {\n if (this.fixed) {\n this.style.left = this.fixed + 'px'\n } else {\n this.style.left = 'unset'\n }\n }\n\n let disabled = this.column.record.disabled || false\n if (typeof disabled === 'function') {\n disabled = disabled.call(this, this.record[this.column.name], this.column, this.record, this.rowIndex, this)\n }\n\n if (!!disabled) {\n // 비황성화 여부 표시\n this.setAttribute('disabled', '')\n } else {\n // 비황성화 여부 제거\n this.removeAttribute('disabled')\n }\n\n const emphasized =\n /*\n * classifier 는 없을 수 있다. 열 빌더가 채워 주지만 손으로 만든 열에는 없고, 그때 여기서\n * 던진다. 갱신 중에 나는 예외라 화면이 오류보고없이 멈춘다.\n *\n * [확인함] 2026-09-05 · wtr 에서 Cannot read properties of undefined (reading 'call')\n * 팝업 표식을 넣어 칸이 전보다 자주 갱신되면서 드러났다. 원래 있던 자리다.\n */\n this.column.record.classifier?.call(this, this.record, this.rowIndex)?.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 /**\n * 이 칸에 **팝업 표식**을 붙이나 — 팝업으로 고쳐지고, 지금 고칠 수 있을 때만.\n *\n * 고칠 수 있는지는 열이 함수로 정할 수도 있다(줄마다 다른 경우). 그때는 몸통이 편집을 열 때\n * 하는 것과 같은 방식으로 물어본다. 표식은 있는데 선택해도 안 열리면 그것이 더 나쁘다.\n */\n get opensPopup(): boolean {\n return this.editMark === 'popup'\n }\n\n /**\n * 이 칸을 **고칠 수 있나, 그리고 어디서 고치나.**\n *\n * popup 팝업이 열린다 — 네모와 펜\n * inline 칸 안에서 고친다 — 펜\n * null 못 고친다 — 표식 없음\n *\n * 표식이 있으면 고칠 수 있는 칸이고, 모양이 어디서 고치는지를 말한다.\n *\n * 고칠 수 있는지는 열이 함수로 정할 수도 있다(줄마다 다른 경우). 그때는 몸통이 편집을 열 때\n * 하는 것과 같은 방식으로 물어본다. 표식은 있는데 선택해도 안 열리면 그것이 더 나쁘다.\n */\n get editMark(): 'popup' | 'inline' | null {\n const column = this.column\n\n if (!column) {\n return null\n }\n\n /* 거터에는 붙이지 않는다 — 순번과 고르기 칸은 고치는 자리가 아니다. */\n if (column.type === 'gutter') {\n return null\n }\n\n /*\n * 표 오른쪽의 **빈 채움 열**에도 붙이지 않는다. 남는 자리를 메우는 20px 짜리 빈 열이라\n * 이름도 타입도 없다 — 고칠 것이 없는데 표식만 떴다.\n *\n * [확인함] 2026-09-05 · 인원 화면 맨 끝 칸 · { type: '', name: '', label: false } · 폭 20\n */\n if (!column.name) {\n return null\n }\n\n let { editable } = column.record || ({} as any)\n\n if (typeof editable === 'function') {\n editable = editable.call(this, this.value, column, this.record, this.rowIndex, this)\n }\n\n /*\n * **밝히지 않은 열에는 붙이지 않는다.**\n *\n * 표 전체가 고칠 수 있는지를 판단하는 자리가 같은 기준을 쓴다.\n *\n * [읽었음] data-grid.ts · editableGrid = columns.some(c => !!c.record?.editable)\n *\n * 참일 때만 고칠 수 있는 열로 본다. 선언하지 않은 열까지 세면 **고르기만 하는 표**에도\n * 표식이 깔린다 — 참조 팝업의 목록이 그랬다(2026-09-05). 그 표의 칸은 고르는 자리이지\n * 고치는 자리가 아니다.\n */\n if (!editable) {\n return null\n }\n\n /*\n * 편집기를 **함수로 직접 준 열**은 타입으로 판단할 수 없다. 그리기를 다른 렌더러로 하려고\n * 타입을 바꾼 열이 그렇다 — 관계를 여럿 다는 칸이 `ops-list` 로 그려지면서 편집은 참조\n * 팝업으로 한다(ops-master 의 reference-list).\n *\n * 그런 열은 `record.opensPopup` 으로 스스로 밝힌다. 밝히지 않으면 타입으로 본다.\n *\n * [확인함] 2026-09-05 · 보유 자격 · 타입 ops-list · 팝업으로 열리는데 표식이 inline 이었다\n */\n const declared = (column.record as any)?.opensPopup\n\n if (typeof declared === 'boolean') {\n return declared ? 'popup' : 'inline'\n }\n\n return editorOpensPopup(column.type) ? 'popup' : 'inline'\n }\n\n get editableOnClick() {\n const renderer = this.renderRoot.firstElementChild as HTMLElement\n //@ts-ignore\n return renderer && 'editableOnClick' in renderer ? renderer.editableOnClick : true\n }\n}\n"]}
1
+ {"version":3,"file":"data-grid-field.js","sourceRoot":"","sources":["../../../src/data-grid/data-grid-field.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport i18next from 'i18next'\nimport { css, html, LitElement, PropertyValues, TemplateResult } from 'lit'\n// import { guard } from 'lit/directives/guard.js'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { actionHeight } from '@operato/styles/action-heights.js'\nimport { TooltipReactiveController } from '@operato/utils'\n\nimport { ZERO_COLUMN } from '../configure/zero-config.js'\nimport { editorOpensPopup } from '../editors/registry.js'\nimport { ColumnConfig, GristRecord } from '../types.js'\n\nconst DEFAULT_TEXT_ALIGN = 'left'\n\n@customElement('ox-grid-field')\nexport class DataGridField extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n\n align-items: center;\n justify-content: var(--data-grid-field-justify-content, flex-start);\n position: relative;\n\n white-space: nowrap;\n background-color: var(--grid-record-background-color);\n padding: var(--grid-record-padding);\n border: 1px solid transparent;\n border-width: 1px 0;\n border-bottom: var(--grid-record-border-bottom);\n\n font-size: var(--grid-record-wide-fontsize);\n min-height: 30px;\n box-sizing: border-box;\n }\n\n /*\n * ── 팝업으로 고치는 칸에는 **표식이 있다** ──────────────────────────────\n *\n * 그런 칸은 글자 칸과 똑같이 생겼다. 열어 보기 전에는 고칠 수 있는지도, 어떻게 고치는지도\n * 모른다 — 「실제 더블클릭을 하기 전에는 모르는 막막함」(사용자, 2026-09-05).\n *\n * **마우스를 올렸을 때 보인다.** 늘 보이게 두었더니 한 화면에 54개가 깔려 표가 시끄러웠다\n * (2026-09-05 · 사용자 판단). 값을 읽으러 온 눈에 장치가 줄마다 끼어든다.\n *\n * **손가락 화면에서는 늘 보인다.** 거기엔 올리는 동작이 없어서 감추면 길이 아예 없어진다.\n * 그 화면들은 표가 성기므로 늘 보여도 시끄럽지 않다.\n *\n * ── 네모와 펜을 쓴다 ─────────────────────────────────────────────────\n * 처음에 삼각형(arrow_drop_down)을 썼다. 참조 칸만 생각한 선택이었다 — 「목록에서 고른다」.\n *\n * 그런데 이 표식은 **팝업으로 여는 편집기 전부**에 붙는다. JSON · 코드 · 권한 편집기도\n * 그렇고, 거기서 삼각형은 거짓말이다. 고를 목록이 없다.\n *\n * ── 모양이 **어디서 고치는지**를 말한다 ────────────────────────────────\n *\n * edit 칸 안에서 고친다\n * edit_square 팝업이 열린다\n *\n * 표식이 있으면 고칠 수 있는 칸이고, 없으면 못 고치는 칸이다. **읽기 전용에는 붙지 않는다.**\n *\n * 처음에 삼각형(arrow_drop_down)을 썼다 — 참조 칸만 생각한 선택이었다. 이 표식은 JSON ·\n * 코드 · 권한 편집기에도 붙고, 거기서 삼각형은 고를 목록이 있다는 거짓말이 된다.\n *\n * 누를 자리는 22px 이다. 33px 줄에서 더블클릭을 겨누는 것보다 한 번 누르는 편이 쉽다.\n */\n /* 마우스 화면 — 칸에 올렸을 때만. 자리는 늘 잡아 두므로 글자가 밀리지 않는다. */\n @media (pointer: fine) {\n [popup-hint] {\n opacity: 0;\n transition: opacity 0.12s ease;\n }\n\n :host(:hover) [popup-hint] {\n opacity: 1;\n }\n }\n\n /*\n * 손가락 화면 — **고른 칸에만 보인다.**\n *\n * 올리는 동작이 없으니 호버로는 아무것도 못 보여 준다. 그렇다고 늘 보이게 두면 좁은\n * 화면이 표식으로 뒤덮인다 — 시끄러우면 정작 필요한 표식도 안 보인다.\n *\n * 고른 칸 하나에만 두면 둘 다 풀린다. 두드려 고르는 것이 그 화면의 첫 동작이므로,\n * 고르는 순간 「이 칸은 이렇게 고친다」가 나타난다.\n */\n @media (pointer: coarse) {\n [popup-hint] {\n display: none;\n }\n\n :host([focused]) [popup-hint] {\n display: flex;\n }\n }\n\n /*\n * ── 표식은 늘 **칸의 끝**에 있다. 정렬을 따라가지 않는다 (2026-09-05) ──────\n *\n * 찾는 데 드는 값이 커진다 정렬은 열마다 다르다(이름은 왼쪽, 수량은 오른쪽). 표식이\n * 그것을 따라가면 자리가 열마다 달라져 눈이 매번 찾아야 한다.\n * 끝에 고정하면 규칙이 하나다 — 「손잡이는 칸 끝에 있다」\n * 남의 칸처럼 보인다 오른쪽 정렬 칸의 값 **앞**에 표식을 두면 왼쪽 칸의 값 뒤에\n * 붙어 보인다. 어느 칸의 손잡이인지 흐려진다\n *\n * ── **값이 없는 쪽**에 붙는다 ──────────────────────────────────────\n * 왼쪽으로 붙인 칸의 빈자리는 오른쪽이고, 오른쪽으로 붙인 칸(수량 · 금액)의 빈자리는\n * 왼쪽이다. 정렬의 반대쪽에 두면 표식이 값 위에 얹히는 일 자체가 없어진다.\n *\n * 자리를 비워 두는 방법도 해 봤다 — 칸의 오른쪽을 24px 늘 비우는 것. 되돌렸다.\n * **자료가 먼저다**(사용자, 2026-09-05): 마우스를 올렸을 때만 보이는 안내 때문에 고칠 수\n * 있는 열 전부가 영구히 좁아지는 것은 값을 잘못 매긴 거래다.\n *\n * 가운데 정렬 칸은 양쪽이 다 비므로 끝에 둔다.\n */\n [popup-hint] {\n position: absolute;\n inset-inline-end: 2px;\n top: 50%;\n transform: translateY(-50%);\n\n --md-icon-size: 16px;\n font-size: 16px;\n width: 16px;\n height: 16px;\n padding: 3px;\n border-radius: 50%;\n\n /*\n * ── 채우지 않는다 (2026-09-05) ──────────────────────────────────\n * md-icon 이 물려주는 값은 FILL 1 이라 글리프가 통짜로 나온다. 두 가지가 어긋난다.\n *\n * 뜻 채운 글리프는 「지금 그 상태다」를 말한다(고른 것 · 켠 것). 이 표식이 말하는\n * 것은 상태가 아니라 **누르면 할 수 있는 일**이다. 그건 윤곽선의 몫이다\n * 밀도 표는 글자밭이다. 같은 16px 에서 채운 글리프는 잉크가 몇 곱절이라 값보다\n * 먼저 눈에 들어온다. 안내가 자료보다 진해서는 안 된다\n *\n * edit_square 는 특히 그렇다 — 채우면 네모가 덩어리가 되어 「종이 위의 연필」이라는\n * 그림이 뭉개진다. 진하기는 색으로 조절한다(아래 :hover).\n */\n /*\n * 굵기는 한 단 올린다. 16px 에서 400 짜리 윤곽선은 획이 배경에 묻는다 — 채움을 뺀 만큼\n * 획으로 되받는다. 잉크가 늘지 않으면서 형태는 또렷해진다.\n */\n font-variation-settings:\n 'FILL' 0,\n 'wght' 500,\n 'opsz' 20;\n\n /*\n * ── 값 **위에 그려지되, 값보다 앞서지 않는다** ──────────────────\n * 자리가 겹치면 표식이 위에 얹힌다. 그래도 이것은 거들어 주는 것이지 읽을 것이 아니다 —\n * 본문 색으로 두었더니 값과 같은 무게가 되어 눈이 먼저 표식으로 갔다(사용자, 2026-09-05).\n * 한 단 낮춰 라벨 색으로 둔다. 손이 표식 위에 오면 그때 본문 색이 된다.\n */\n color: var(--ui-muted, #57676f);\n cursor: pointer;\n\n transition:\n background-color 0.12s ease,\n color 0.12s ease;\n }\n\n /* 오른쪽으로 붙인 칸 — 값이 오른쪽 끝에 있으므로 표식은 왼쪽의 빈자리로 간다. */\n :host([mark-start]) [popup-hint] {\n inset-inline-end: auto;\n inset-inline-start: 2px;\n }\n\n [popup-hint]:hover {\n color: var(--ui-ink, #17272f);\n background-color: var(--ui-hover, color-mix(in srgb, currentColor 12%, transparent));\n }\n\n :host([multiline]) {\n white-space: pre-line;\n }\n\n /*\n * ── 칸이 쓸 수 있는 용어 (2026-08-31) ──────────────────────────────────\n * 렌더러가 그린 것은 이 부품의 shadow DOM 안에 들어간다. 그래서 **화면의 CSS 가 닿지 않는다.**\n * 두 줄 칸이나 상태 알약을 그리려던 화면들이 그 벽에 막혀, 값에 인라인 style 을 박거나 칸마다\n * 부품을 새로 만들었다.\n *\n * 목록이면 어디서나 같은 것을 필요로 하므로 여기서 준다. 이름은 뜻으로 짓는다 —\n * 「늦었다」가 아니라 「심각하다(crit)」다. 늦은 것은 이 표의 일이 아니고 화면의 일이다.\n *\n * .sub 둘째 줄. 「1,200 EA」 아래의 「BOLT-8」처럼 딸린 사실\n * .mono 고정폭. 번호·시각·수량처럼 자릿수를 비교하는 값\n * .none 값이 없음. 빈 칸으로 두면 「아직 안 읽었다」와 구별되지 않는다\n * .pill 알약. good·warn·crit·info 로 뜻을 준다\n * .crit · .warn · .good 글자 강조\n *\n * 값은 --ui-* 토큰에서 오고 폴백을 둔다 — 테마가 없어도 무너지지 않는다.\n */\n :host([twoline]) {\n display: block;\n white-space: normal;\n line-height: 1.35;\n }\n\n .sub {\n display: block;\n margin-top: 2px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-faint, #8a959c);\n }\n\n .mono {\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-variant-numeric: tabular-nums;\n }\n\n /*\n * 그 줄이 무엇인지 가리키는 값 — 작업지시 번호, 설비 이름처럼. 두 줄 칸의 첫 줄이 이것이고\n * 둘째 줄(.sub)이 딸린 사실이다. 굵기만 올린다. 고정폭은 쓰지 않는다 — 자릿수를 세로로 견줄\n * 값이 아니고, 같은 글자 수에도 폭이 넓어져 옆 칸을 밀어낸다.\n */\n .id {\n font-weight: 620;\n }\n\n .none {\n color: var(--ui-faint, #8a959c);\n }\n\n .crit {\n color: var(--ui-crit, #c93f30);\n font-weight: 640;\n }\n\n .warn {\n color: var(--ui-warn, #9f5e02);\n font-weight: 640;\n }\n\n .good {\n color: var(--ui-good, #297d39);\n font-weight: 640;\n }\n\n /*\n * 값과 표식을 한 줄에 담는 영역이다. **간격과 높이를 여기서 정한다.**\n *\n * 처음에는 :not(.pill) + .pill { margin-left } 로 띄우려 했는데 안 걸렸다. 렌더러가 값을\n * 텍스트 노드로 내므로 알약의 앞 형제가 **요소가 아니고**, CSS 형제 선택자는 텍스트 노드를 보지\n * 못한다. 알약이 첫 요소이기도 해서 :first-child 로도 가려지지 않는다(실측 확인).\n *\n * 높이도 같은 자리에서 잡는다. baseline 으로 두면 14px 글자와 11.5px 알약의 기준선을 맞추느라\n * 알약이 내려앉는다.\n */\n /* 값이 있지만 눈에 걸릴 것은 아닌 것 — 「보통」 같은 기본값. .none(값 없음)과 구별한다. */\n .quiet {\n color: var(--ui-muted, #57676f);\n }\n\n /*\n * 별 같은 작은 기호를 글자 크기로 맞춘다. md-icon 의 기본은 24px 이라 줄 높이를 밀어낸다.\n * 색은 물려받는다 — 개수가 크기를 말하고 색은 담는 쪽이 정한다.\n */\n .star {\n --md-icon-size: 15px;\n font-size: 15px;\n width: 15px;\n height: 15px;\n color: inherit;\n }\n\n /* 별처럼 여럿이 붙는 기호는 사이를 좁게 둔다. 단어 사이의 6px 은 여기서 너무 넓다. */\n .line:has(.star) {\n gap: 1px;\n }\n\n /*\n * 셀 안의 아이콘 버튼. 그 줄에 하는 동작(들어가기 · 열기)을 담는다.\n *\n * 셀의 클릭 경로를 타지 않고 스스로 받으려는 렌더러가 쓴다. 그 경로에는 동작이 견딜 수 없는 관문이\n * 둘 있다.\n *\n * data-grid-body-click-handler.ts if (this.editTarget) return\n * data-grid-body-click-handler.ts (e.target).closest(\"ox-grid-field\")\n *\n * 앞은 다른 칸이 편집 중이면 첫 누름을 먹고, 뒤는 셀 경계를 스치면 칸을 못 찾는다. 진짜 button 을\n * 그리면 둘을 다 피하고 키보드로도 닿는다.\n *\n * 이 규칙이 여기 있는 이유: 렌더러의 결과는 이 셀의 그림자 안에 그려지므로 **페이지의 스타일이\n * 닿지 않는다.** 셀 안의 어휘는 셀이 갖는다.\n *\n * host 자식으로 적은 이유: 이 파일 아래의 host 자식 button 규칙이 채운 버튼을 그리고, 특이도가\n * 같으면 **뒤에 오는 쪽이 이긴다.** 그것 없이 두면 이 버튼이 채운 버튼의 모양으로 나왔다\n * (어두운 바탕 · 흰 글자, 실측 rgb(38, 46, 56)).\n *\n * 둘의 뜻이 다르다. 채운 버튼은 **실행하는 동작**이고, 이 버튼은 **이동**이다. 그래서 평소에는\n * 흐리고 배경이 없다.\n *\n * 배경은 **칸을 채우지 않고 동그라미로** 그린다. 칸을 다 채우면 「이 셀이 골라졌다」로 읽힌다 —\n * 표에서 채워진 사각형은 선택의 뜻이다. 동그라미는 버튼의 뜻이다.\n */\n :host > button.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: ${actionHeight('--cell-action-height')};\n height: ${actionHeight('--cell-action-height')};\n flex: none;\n /*\n * 누를 자리는 26px 로 두되 **줄 높이는 밀지 않는다.**\n *\n * 이 버튼이 들어간 칸 하나 때문에 표의 모든 줄이 46px 이 됐다(글자만 있는 줄은 38px). 스무 줄이면\n * 240px 을 잃는다. [확인함] grid-template-rows: 46px 34px\n *\n * 그렇다고 26px 아래로 줄이면 손가락으로 못 누른다(가로세로 24px 이 최소다). 그래서 상자는 26px\n * 로 두고 위아래 3px 씩을 여백에서 되돌려, 자리는 20px 만 차지하게 한다 — 글자 한 줄과 같다.\n */\n margin-top: calc((20px - ${actionHeight('--cell-action-height')}) / 2);\n margin-bottom: calc((20px - ${actionHeight('--cell-action-height')}) / 2);\n border: none;\n border-radius: 50%;\n background: transparent;\n padding: 0;\n color: var(--ui-faint, #8a959c);\n cursor: pointer;\n transition:\n background-color 0.12s ease,\n color 0.12s ease;\n }\n\n :host > button.icon:hover {\n background-color: var(--ui-hover, color-mix(in srgb, currentColor 12%, transparent));\n color: var(--ui-ink, #17272f);\n }\n\n :host > button.icon:active {\n background-color: var(--ui-active, color-mix(in srgb, currentColor 22%, transparent));\n }\n\n /* 키보드로 온 초점만 테를 보인다. 선택해서 온 초점에는 보이지 않는다. */\n :host > button.icon:focus-visible {\n outline: 2px solid var(--ui-accent, #00727e);\n outline-offset: -2px;\n }\n\n /*\n * 값을 함께 담는 버튼. 「4 ›」처럼 숫자와 화살이 나란히 놓인다.\n *\n * 아이콘만으로는 그 줄에 무엇이 있는지 말하지 못한다. 값이 뜻을 나르고 화살이 「누를 수 있다」를\n * 말한다. 그래서 동그라미가 아니라 알약 모양이다.\n */\n :host > button.icon.wide {\n width: auto;\n min-width: 44px;\n gap: 0;\n padding: 0 2px 0 7px;\n border-radius: 999px;\n font: inherit;\n font-size: var(--grid-record-wide-fontsize);\n font-variant-numeric: tabular-nums;\n }\n\n :host > button.icon.wide md-icon {\n --md-icon-size: 16px;\n font-size: 16px;\n width: 16px;\n height: 16px;\n }\n\n :host > button.icon md-icon {\n --md-icon-size: 18px;\n font-size: 18px;\n width: 18px;\n height: 18px;\n }\n\n .line {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n }\n\n /* 값이 길면 값이 줄고 표식은 그대로 남는다 — 표식이 먼저 잘리면 읽을 수 없다. */\n .line > :first-child {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* 표식 — 상태 같은 짧은 값에 두른다. 점이 색을 나르고 글자가 뜻을 나른다. */\n .pill {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 1px 8px;\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: 999px;\n font-size: var(--ui-t-small, 11.5px);\n font-weight: 400;\n color: var(--ui-muted, #57676f);\n white-space: nowrap;\n }\n\n /*\n * 색만으로 뜻을 전하지 않는다 — 알약 안의 글자가 함께 말한다. 점은 거드는 것이다.\n *\n * align-self: center 가 필요하다. 알약은 flex 이고 점은 그 안의 항목인데, 높이를 6px 로\n * 주어도 교차축에서 늘어나 **6×11.5 짜리 세로 타원**이 됐다(원이 아니라 길쭉했다).\n * .pill 의 align-items: center 는 부모 쪽 값이라 이 항목의 stretch 를 막지 못한다.\n */\n .pill .dot {\n align-self: center;\n width: 6px;\n height: 6px;\n /*\n * 이 파일 아래에 span, pre 의 min-height: 1em 이 있다. 값이 빈 칸에서도 줄 높이를\n * 유지하려는 규칙인데, 점도 span 이라 거기 걸려 **높이가 1em(11.5px)까지 늘어났다** —\n * 그래서 6px 을 줘도 6×11.5 세로 타원이 됐다. 여기서 되돌린다.\n */\n min-height: 0;\n border-radius: 50%;\n background: currentColor;\n flex: none;\n }\n\n .pill.good {\n color: var(--ui-good, #297d39);\n border-color: color-mix(in srgb, var(--ui-good, #297d39) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.info {\n color: var(--ui-accent, #00727e);\n border-color: color-mix(in srgb, var(--ui-accent, #00727e) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.warn {\n color: var(--ui-warn, #9f5e02);\n border-color: color-mix(in srgb, var(--ui-warn, #9f5e02) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.crit {\n color: var(--ui-crit, #c93f30);\n border-color: color-mix(in srgb, var(--ui-crit, #c93f30) 40%, transparent);\n font-weight: 400;\n }\n\n .pill.quiet {\n color: var(--ui-faint, #8a959c);\n }\n\n :host([gutter]) {\n padding: var(--grid-gutter-padding);\n text-align: center;\n }\n\n :host([gutter]) * {\n cursor: default;\n overflow: hidden;\n }\n\n :host([editing]) {\n border-top: var(--grid-record-editing-border);\n border-bottom: var(--grid-record-editing-border);\n }\n\n /*\n * 초점이 갔다고 **줄을 나누는 선을 바꾸지 않는다.**\n *\n * 전에는 여기서 --grid-record-border-bottom 을 초점 표식 값으로 덮어썼다. 그래서 표식을\n * 감추려고 그 토큰을 none 이나 투명으로 두면 **그 셀만 줄 선이 사라졌다.** 한 토큰이 「초점\n * 표식」과 「줄을 나누는 선」 두 가지를 겸하고 있었던 것이다.\n *\n * 초점 표식은 outline 이 그린다(data-grid-body-style.ts). 줄 선은 줄 선으로 남는다.\n */\n\n :host([dirty])::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n\n width: 0px;\n height: 0px;\n border-top: 9px solid red;\n border-left: 9px solid transparent;\n }\n\n span,\n pre {\n display: block;\n text-overflow: ellipsis;\n overflow: hidden;\n min-height: 1em;\n }\n\n :host([multiline]) span,\n :host([multiline]) pre {\n text-overflow: initial;\n overflow: visible;\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n }\n\n * {\n margin: 0;\n text-align: var(--data-grid-field-text-align);\n }\n\n *[center] {\n flex: none;\n margin: 0 auto;\n }\n\n :host > button {\n display: flex;\n gap: var(--spacing-small);\n border: 0;\n align-items: center;\n padding: var(--spacing-small);\n\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-primary);\n border-radius: var(--data-card-item-btn-border-radius, var(--md-sys-shape-corner-extra-small, 4px));\n\n font-size: 0.8rem;\n line-height: 0.8rem;\n\n md-icon {\n border: 0;\n padding: 0;\n background-color: unset;\n }\n }\n\n :host([danger]) > button {\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-error);\n border-color: var(--md-sys-color-error);\n }\n\n :host([danger]) > button:hover {\n color: var(--md-sys-color-on-secondary);\n background-color: var(--md-sys-color-secondary);\n border-color: var(--md-sys-color-secondary);\n }\n `\n ]\n\n @property({ type: String, attribute: true }) align = DEFAULT_TEXT_ALIGN\n @property({ type: Object }) record: GristRecord = {}\n @property({ type: Object }) column: ColumnConfig = ZERO_COLUMN\n @property({ type: Number }) rowIndex = 0\n @property({ type: Number }) columnIndex = 0\n @property({ type: Boolean }) editing = false\n @property({ type: Boolean, reflect: true }) multiline = false\n @property({ type: Object }) value = {}\n @property({ type: String }) valueWith: string | null = null\n @property({ attribute: false }) emphasized: any = false\n @property({ type: String, attribute: true }) fixed?: string\n @property({ type: String }) type?: string\n @property({ type: Boolean }) isWorking: boolean = false\n\n /* check in tree의 변화에 대응하기 위함임. 만일, record control 오브젝트로 통일되면 대체될 것임. */\n @property({ type: String }) checked?: string\n\n private _editCancelled?: boolean\n private _onFieldChange: (e: Event) => void = e => {}\n private _onKeydownInEditingMode: (e: KeyboardEvent) => void = e => {}\n\n private tooltipController?: TooltipReactiveController = new TooltipReactiveController(this)\n\n render(): TemplateResult {\n if (!this.column) {\n return html``\n }\n\n var { value, column, record, rowIndex } = this\n var { renderer, editor } = column.record\n\n return html`\n ${this.editing\n ? editor?.call(this, value, column, record, rowIndex, this)\n : renderer?.call(this, value, column, record, rowIndex, this)}\n <!--\n **편집 중에도 표식을 남긴다.**\n\n 칸을 누르면 편집 상태가 되고, 그때는 렌더러 대신 편집기가 그려진다. 표식을 !editing 으로\n 묶어 두었더니 **칸에 들어간 순간 사라졌다** — 들어간 사람에게 여는 길이 없어지는 셈이고,\n 다시 더블클릭이나 Enter 를 짐작해야 한다. 그 짐작을 없애려고 만든 표식이다.\n\n 팝업 편집기에만 붙으므로(opensPopup) 글자를 치는 편집기를 가릴 일은 없다.\n -->\n <!--\n ── 칸 안에서 고치는 중에는 표식을 걷는다 ────────────────────────────────\n 입력칸이 이미 그 자리에 있다. 펜을 겹쳐 두면 글자를 가리고, 고치는 중인 사람에게\n 「고칠 수 있다」를 다시 말할 이유도 없다.\n\n 팝업 쪽은 남긴다 — 편집 상태에 들어가도 팝업은 아직 안 열렸고, 그 표식이 여는 길이다.\n -->\n ${this.editMark && !(this.editing && this.editMark === 'inline')\n ? html`<md-icon\n popup-hint\n ?inline=${this.editMark === 'inline'}\n title=${i18next.t('text.grid-open-editor', { defaultValue: 'press to edit' })}\n @pointerdown=${async (e: Event) => {\n /*\n * **click 이 아니라 pointerdown 이다.**\n *\n * 이 표에서는 click 이벤트가 나지 않는다 — 몸통이 pointer 단계에서 기본 동작을\n * 막는다. document 에 capture 로 click 을 걸어 두고 표식을 선택해 봐도 한 번도\n * 울리지 않았다(2026-09-05 계측).\n */\n e.stopPropagation()\n e.preventDefault()\n\n /*\n * ── 포커스는 여기서 변경하지 않는다 ──────────────────────────────\n *\n * 팝업이 닫히면 오버레이가 **열릴 때 포커스가 있던 자리**로 돌려준다. 그런데 위에서\n * preventDefault 를 하므로 브라우저가 이 칸으로 포커스를 옮겨 주지 않고, 그래서\n * 팝업을 닫으면 엉뚱한 곳으로 간다(페이지 검색창이었다).\n *\n * 두 가지를 해 봤고 **둘 다 팝업이 안 열리게 만들었다.**\n *\n * focus-change 이벤트 몸통이 그 갱신에서 editTarget 을 지운다(shouldUpdate)\n * ime-buffer 에 직접 focus 같은 결과\n *\n * 이 표는 포커스를 ime-buffer 라는 숨은 입력칸에 담아 두고, 편집 상태를 그 갱신\n * 주기에 얹고 있다. 그 둘이 얽혀 있어서 밖에서 포커스를 옮기면 편집이 취소된다.\n *\n * 표의 포커스 모델을 손대야 풀리는 문제라 여기서 우회하지 않는다.\n * [확인함] 2026-09-05 · 두 방법 모두 팝업이 안 열림\n */\n\n this.dispatchEvent(\n new CustomEvent('start-edit', {\n bubbles: true,\n composed: true,\n detail: { row: this.rowIndex, column: this.columnIndex }\n })\n )\n\n /*\n * 칸 안에서 고치는 편집기는 여기서 끝난다 — 편집이 열리면 그 자리에 입력칸이 뜬다.\n * 팝업 편집기는 한 걸음 더 필요하다.\n */\n if (this.editMark !== 'popup') {\n return\n }\n\n /*\n * ── 편집을 열고, **그다음** 더블클릭을 편집기에 보낸다 ──────────────\n *\n * 사람이 더블클릭할 때의 순서를 계측해 보면 이렇다.\n *\n * startEditTarget 첫 클릭이 편집을 연다 → 편집기가 그려진다\n * dblclick 그다음에 도착해 **이미 있는 편집기**가 받아 팝업을 연다\n *\n * 편집기마다 여는 메서드 이름이 달라서 부품이 직접 부를 수는 없다. 사람이 하던\n * 동작을 같은 순서로 보내면 경로가 하나로 남는다.\n */\n for (let i = 0; i < 5 && !this.editing; i++) {\n await new Promise(resolve => requestAnimationFrame(resolve))\n }\n\n await this.updateComplete\n\n /*\n * Both events, in the order a browser sends them.\n *\n * The editors split on which one they open from, and sending only `dblclick`\n * reached two of the eight:\n *\n * _onclick json5 · code · resource-code · privilege · json · resource-id\n * _ondblclick resource-object · resource-object-legacy\n *\n * Those last two define `_onclick` as a no-op, so nothing opens twice.\n * [확인함] 2026-09-07 · 사용자 보고 — 코드 편집기가 아이콘으로 안 열렸다\n */\n const editor = this.renderRoot.firstElementChild as HTMLElement | null\n editor?.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }))\n editor?.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, composed: true }))\n }}\n >${this.editMark === 'popup' ? 'edit_square' : 'edit'}</md-icon\n >`\n : ''}\n `\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('editing')) {\n if (this.editing) {\n this._onKeydownInEditingMode = ((e: KeyboardEvent) => {\n if (e.key === 'Esc' || e.key === 'Escape') {\n /* 편집 취소 */\n this._editCancelled = true\n }\n }).bind(this)\n\n this._onFieldChange = ((e: Event) => {\n this._editCancelled && e.stopPropagation()\n }).bind(this)\n\n delete this._editCancelled\n this.addEventListener('field-change', this._onFieldChange)\n this.addEventListener('keydown', this._onKeydownInEditingMode)\n } else {\n this.removeEventListener('field-change', this._onFieldChange)\n this.removeEventListener('keydown', this._onKeydownInEditingMode)\n }\n }\n\n if (changes.has('column')) {\n /*\n * 정렬은 **넣을 때만이 아니라 되돌릴 때도** 손대야 한다.\n *\n * 이 요소는 재사용된다. 전에는 기본값이 아닐 때만 인라인 값을 넣고 지우지 않았다. 그래서 한 번\n * 오른쪽 정렬 칸을 그린 셀이 다음에 왼쪽 정렬 칸을 그려도 오른쪽에 남았다.\n *\n * [확인함] 품질 화면 「판단」 칸에서 머리글은 x=380(왼쪽)인데 값은 x=452(flex-end)였다. 숫자 칸을\n * 오른쪽으로 바꾼 뒤 드러났다 — 오른쪽 정렬 칸이 드물던 동안은 눈에 안 띄었다. 2026-09-02\n */\n var align = this.column.record.align || DEFAULT_TEXT_ALIGN\n\n if (align == DEFAULT_TEXT_ALIGN) {\n this.style.removeProperty('--data-grid-field-justify-content')\n this.style.removeProperty('--data-grid-field-text-align')\n } else {\n let justify = 'center'\n switch (align) {\n case 'right':\n justify = 'flex-end'\n break\n }\n this.style.setProperty('--data-grid-field-justify-content', justify)\n this.style.setProperty('--data-grid-field-text-align', align)\n }\n\n /* 표식은 값의 반대쪽 빈자리에 선다 — 위 :host([mark-start]) 주석 참고. */\n this.toggleAttribute('mark-start', align === 'right')\n\n const { danger } = this.column\n if (danger) {\n this.setAttribute('danger', '')\n } else {\n this.removeAttribute('danger')\n }\n }\n\n if (changes.has('fixed')) {\n if (this.fixed) {\n this.style.left = this.fixed + 'px'\n } else {\n this.style.left = 'unset'\n }\n }\n\n let disabled = this.column.record.disabled || false\n if (typeof disabled === 'function') {\n disabled = disabled.call(this, this.record[this.column.name], this.column, this.record, this.rowIndex, this)\n }\n\n if (!!disabled) {\n // 비황성화 여부 표시\n this.setAttribute('disabled', '')\n } else {\n // 비황성화 여부 제거\n this.removeAttribute('disabled')\n }\n\n const emphasized =\n /*\n * classifier 는 없을 수 있다. 열 빌더가 채워 주지만 손으로 만든 열에는 없고, 그때 여기서\n * 던진다. 갱신 중에 나는 예외라 화면이 오류보고없이 멈춘다.\n *\n * [확인함] 2026-09-05 · wtr 에서 Cannot read properties of undefined (reading 'call')\n * 팝업 표식을 넣어 칸이 전보다 자주 갱신되면서 드러났다. 원래 있던 자리다.\n */\n this.column.record.classifier?.call(this, this.record, this.rowIndex)?.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 /**\n * 이 칸에 **팝업 표식**을 붙이나 — 팝업으로 고쳐지고, 지금 고칠 수 있을 때만.\n *\n * 고칠 수 있는지는 열이 함수로 정할 수도 있다(줄마다 다른 경우). 그때는 몸통이 편집을 열 때\n * 하는 것과 같은 방식으로 물어본다. 표식은 있는데 선택해도 안 열리면 그것이 더 나쁘다.\n */\n get opensPopup(): boolean {\n return this.editMark === 'popup'\n }\n\n /**\n * 이 칸을 **고칠 수 있나, 그리고 어디서 고치나.**\n *\n * popup 팝업이 열린다 — 네모와 펜\n * inline 칸 안에서 고친다 — 펜\n * null 못 고친다 — 표식 없음\n *\n * 표식이 있으면 고칠 수 있는 칸이고, 모양이 어디서 고치는지를 말한다.\n *\n * 고칠 수 있는지는 열이 함수로 정할 수도 있다(줄마다 다른 경우). 그때는 몸통이 편집을 열 때\n * 하는 것과 같은 방식으로 물어본다. 표식은 있는데 선택해도 안 열리면 그것이 더 나쁘다.\n */\n get editMark(): 'popup' | 'inline' | null {\n const column = this.column\n\n if (!column) {\n return null\n }\n\n /* 거터에는 붙이지 않는다 — 순번과 고르기 칸은 고치는 자리가 아니다. */\n if (column.type === 'gutter') {\n return null\n }\n\n /*\n * 표 오른쪽의 **빈 채움 열**에도 붙이지 않는다. 남는 자리를 메우는 20px 짜리 빈 열이라\n * 이름도 타입도 없다 — 고칠 것이 없는데 표식만 떴다.\n *\n * [확인함] 2026-09-05 · 인원 화면 맨 끝 칸 · { type: '', name: '', label: false } · 폭 20\n */\n if (!column.name) {\n return null\n }\n\n let { editable } = column.record || ({} as any)\n\n if (typeof editable === 'function') {\n editable = editable.call(this, this.value, column, this.record, this.rowIndex, this)\n }\n\n /*\n * **밝히지 않은 열에는 붙이지 않는다.**\n *\n * 표 전체가 고칠 수 있는지를 판단하는 자리가 같은 기준을 쓴다.\n *\n * [읽었음] data-grid.ts · editableGrid = columns.some(c => !!c.record?.editable)\n *\n * 참일 때만 고칠 수 있는 열로 본다. 선언하지 않은 열까지 세면 **고르기만 하는 표**에도\n * 표식이 깔린다 — 참조 팝업의 목록이 그랬다(2026-09-05). 그 표의 칸은 고르는 자리이지\n * 고치는 자리가 아니다.\n */\n if (!editable) {\n return null\n }\n\n /*\n * 편집기를 **함수로 직접 준 열**은 타입으로 판단할 수 없다. 그리기를 다른 렌더러로 하려고\n * 타입을 바꾼 열이 그렇다 — 관계를 여럿 다는 칸이 `ops-list` 로 그려지면서 편집은 참조\n * 팝업으로 한다(ops-master 의 reference-list).\n *\n * 그런 열은 `record.opensPopup` 으로 스스로 밝힌다. 밝히지 않으면 타입으로 본다.\n *\n * [확인함] 2026-09-05 · 보유 자격 · 타입 ops-list · 팝업으로 열리는데 표식이 inline 이었다\n */\n const declared = (column.record as any)?.opensPopup\n\n if (typeof declared === 'boolean') {\n return declared ? 'popup' : 'inline'\n }\n\n return editorOpensPopup(column.type) ? 'popup' : 'inline'\n }\n\n get editableOnClick() {\n const renderer = this.renderRoot.firstElementChild as HTMLElement\n //@ts-ignore\n return renderer && 'editableOnClick' in renderer ? renderer.editableOnClick : true\n }\n}\n"]}
@@ -25,7 +25,7 @@ export const FilterStyles = css `
25
25
  }
26
26
 
27
27
  span {
28
- font-weight: var(--md-sys-typescale-label-medium-weight);
28
+ font-weight: var(--md-sys-typescale-label-medium-weight, var(--md-ref-typeface-weight-medium, 500));
29
29
  text-transform: capitalize;
30
30
  }
31
31
 
@@ -1 +1 @@
1
- {"version":3,"file":"filter-styles.js","sourceRoot":"","sources":["../../../src/filters/filter-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAA;AAEhF,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+CjB,sBAAsB,CAAC,6BAA6B,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiF1E,CAAA","sourcesContent":["import { css } from 'lit'\n\nimport { suiteFormElementHeight } from '@operato/styles/form-element-heights.js'\n\nexport const FilterStyles = css`\n :host {\n --ox-filters-input-placeholder-color: var(--input-placeholder-color, var(--md-sys-color-on-surface));\n\n --ox-filters-input-border: var(--input-border, 1px solid rgba(0, 0, 0, 0.2));\n --ox-filters-input-focus-border: var(--input-focus-border, 1px solid var(--md-sys-color-outline-variant));\n --ox-filters-input-font: var(--input-font, normal 14px var(--theme-font));\n --ox-filters-input-color: var(--input-color, var(--md-sys-color-on-surface));\n --ox-filters-input-focus-color: var(--input-focus-color, var(--md-sys-color-on-surface-variant));\n --ox-filters-label-font: var(--label-font, normal 14px var(--theme-font));\n --ox-filters-label-color: var(--label-color, var(--md-sys-color-on-surface));\n --ox-filters-input-background-color: transparent;\n\n --ox-filters-form-gap: var(--input-gap-vertical, 8px) var(--input-gap-horizontal, 16px);\n --ox-filters-input-padding: var(--spacing-small, 4px);\n\n --ox-select-padding: var(--ox-filters-input-padding);\n --ox-checkbox-background-color: var(--ox-filters-input-background-color, transparent);\n }\n\n label {\n color: var(--ox-filters-label-color, var(--md-sys-color-on-primary-container));\n }\n\n span {\n font-weight: var(--md-sys-typescale-label-medium-weight);\n text-transform: capitalize;\n }\n\n input::placeholder {\n color: var(--ox-filters-input-placeholder-color, var(--md-sys-color-surface-dim));\n opacity: 0.6;\n }\n\n input,\n ox-input-search,\n [filter-input] {\n padding: var(--ox-filters-input-padding);\n }\n\n /* A grid filter bar is a dense row: the operato products give its inputs the small input height; the\n * platform keeps their natural height. */\n ox-select,\n ox-input-search,\n input:not([type='checkbox']):not([type='radio']),\n [filter-input] {\n box-sizing: border-box;\n height: ${suiteFormElementHeight('--form-element-height-small', 'auto')};\n }\n\n ox-select,\n ox-input-search,\n input,\n [filter-input] {\n border: none;\n border-bottom: var(--ox-filters-input-border, var(--md-sys-color-outline));\n font: var(--ox-filters-input-font);\n color: var(--ox-filters-input-color, var(--md-sys-color-on-surface));\n background-color: var(--ox-filters-input-background-color, transparent);\n vertical-align: middle;\n }\n\n ox-select:focus,\n input:focus,\n [filter-input]:focus {\n outline: none;\n border-bottom: var(--ox-filters-input-focus-border, var(--md-sys-color-outline-variant));\n color: var(--ox-filters-input-focus-color, var(--md-sys-color-on-surface-variant));\n }\n\n ox-select {\n min-width: 90px;\n max-width: 170px;\n }\n\n ox-input-search {\n max-width: 150px;\n }\n\n input[type='number'] {\n max-width: 90px;\n }\n\n input[type*='date'],\n input[type*='time'],\n input[type='week'],\n input[type='month'] {\n max-width: 170px;\n }\n\n [filter-input] {\n min-width: 140px;\n max-width: 170px;\n }\n\n @media only screen and (max-width: 460px) {\n :host {\n --ox-filters-form-label-font: bold 13px var(--theme-font);\n --ox-filters-input-font: normal 16px var(--theme-font);\n }\n\n ox-input-barcode {\n max-width: unset;\n flex: 1;\n }\n\n ox-input-search {\n max-width: unset;\n }\n\n ox-select {\n max-width: unset;\n }\n\n ox-checkbox {\n max-width: unset;\n }\n\n input[type='number'] {\n max-width: unset;\n }\n\n input {\n background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n flex: 1;\n }\n }\n`\n"]}
1
+ {"version":3,"file":"filter-styles.js","sourceRoot":"","sources":["../../../src/filters/filter-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAA;AAEhF,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+CjB,sBAAsB,CAAC,6BAA6B,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiF1E,CAAA","sourcesContent":["import { css } from 'lit'\n\nimport { suiteFormElementHeight } from '@operato/styles/form-element-heights.js'\n\nexport const FilterStyles = css`\n :host {\n --ox-filters-input-placeholder-color: var(--input-placeholder-color, var(--md-sys-color-on-surface));\n\n --ox-filters-input-border: var(--input-border, 1px solid rgba(0, 0, 0, 0.2));\n --ox-filters-input-focus-border: var(--input-focus-border, 1px solid var(--md-sys-color-outline-variant));\n --ox-filters-input-font: var(--input-font, normal 14px var(--theme-font));\n --ox-filters-input-color: var(--input-color, var(--md-sys-color-on-surface));\n --ox-filters-input-focus-color: var(--input-focus-color, var(--md-sys-color-on-surface-variant));\n --ox-filters-label-font: var(--label-font, normal 14px var(--theme-font));\n --ox-filters-label-color: var(--label-color, var(--md-sys-color-on-surface));\n --ox-filters-input-background-color: transparent;\n\n --ox-filters-form-gap: var(--input-gap-vertical, 8px) var(--input-gap-horizontal, 16px);\n --ox-filters-input-padding: var(--spacing-small, 4px);\n\n --ox-select-padding: var(--ox-filters-input-padding);\n --ox-checkbox-background-color: var(--ox-filters-input-background-color, transparent);\n }\n\n label {\n color: var(--ox-filters-label-color, var(--md-sys-color-on-primary-container));\n }\n\n span {\n font-weight: var(--md-sys-typescale-label-medium-weight, var(--md-ref-typeface-weight-medium, 500));\n text-transform: capitalize;\n }\n\n input::placeholder {\n color: var(--ox-filters-input-placeholder-color, var(--md-sys-color-surface-dim));\n opacity: 0.6;\n }\n\n input,\n ox-input-search,\n [filter-input] {\n padding: var(--ox-filters-input-padding);\n }\n\n /* A grid filter bar is a dense row: the operato products give its inputs the small input height; the\n * platform keeps their natural height. */\n ox-select,\n ox-input-search,\n input:not([type='checkbox']):not([type='radio']),\n [filter-input] {\n box-sizing: border-box;\n height: ${suiteFormElementHeight('--form-element-height-small', 'auto')};\n }\n\n ox-select,\n ox-input-search,\n input,\n [filter-input] {\n border: none;\n border-bottom: var(--ox-filters-input-border, var(--md-sys-color-outline));\n font: var(--ox-filters-input-font);\n color: var(--ox-filters-input-color, var(--md-sys-color-on-surface));\n background-color: var(--ox-filters-input-background-color, transparent);\n vertical-align: middle;\n }\n\n ox-select:focus,\n input:focus,\n [filter-input]:focus {\n outline: none;\n border-bottom: var(--ox-filters-input-focus-border, var(--md-sys-color-outline-variant));\n color: var(--ox-filters-input-focus-color, var(--md-sys-color-on-surface-variant));\n }\n\n ox-select {\n min-width: 90px;\n max-width: 170px;\n }\n\n ox-input-search {\n max-width: 150px;\n }\n\n input[type='number'] {\n max-width: 90px;\n }\n\n input[type*='date'],\n input[type*='time'],\n input[type='week'],\n input[type='month'] {\n max-width: 170px;\n }\n\n [filter-input] {\n min-width: 140px;\n max-width: 170px;\n }\n\n @media only screen and (max-width: 460px) {\n :host {\n --ox-filters-form-label-font: bold 13px var(--theme-font);\n --ox-filters-input-font: normal 16px var(--theme-font);\n }\n\n ox-input-barcode {\n max-width: unset;\n flex: 1;\n }\n\n ox-input-search {\n max-width: unset;\n }\n\n ox-select {\n max-width: unset;\n }\n\n ox-checkbox {\n max-width: unset;\n }\n\n input[type='number'] {\n max-width: unset;\n }\n\n input {\n background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n flex: 1;\n }\n }\n`\n"]}
@@ -9,6 +9,7 @@ export declare class GutterSequence {
9
9
  icon?: string | undefined;
10
10
  iconOnly?: boolean | undefined;
11
11
  danger?: boolean | undefined;
12
+ quiet?: boolean | ((record: import("../types.js").GristRecord) => boolean) | undefined;
12
13
  header: string | import("../types.js").HeaderConfig;
13
14
  record: import("../types.js").RecordConfig | {
14
15
  renderer: FieldRenderer;
@@ -1,4 +1,4 @@
1
- import '@material/web/button/outlined-button.js';
1
+ import '@material/web/icon/icon.js';
2
2
  import { LitElement } from 'lit';
3
3
  export declare class OxGristFilterPersonalizer extends LitElement {
4
4
  static styles: import("lit").CSSResult[];