@operato/data-grist 10.10.0 → 10.10.1

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 (39) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/src/configure/column-builder.js +6 -7
  3. package/dist/src/configure/column-builder.js.map +1 -1
  4. package/dist/src/data-grid/data-grid-body-style.js +4 -4
  5. package/dist/src/data-grid/data-grid-body-style.js.map +1 -1
  6. package/dist/src/data-grid/data-grid-body.d.ts +11 -11
  7. package/dist/src/data-grid/data-grid-body.js +33 -35
  8. package/dist/src/data-grid/data-grid-body.js.map +1 -1
  9. package/dist/src/data-grid/data-grid-header.d.ts +6 -6
  10. package/dist/src/data-grid/data-grid-header.js +22 -21
  11. package/dist/src/data-grid/data-grid-header.js.map +1 -1
  12. package/dist/src/data-grid/data-grid.d.ts +4 -4
  13. package/dist/src/data-grid/data-grid.js +24 -24
  14. package/dist/src/data-grid/data-grid.js.map +1 -1
  15. package/dist/src/data-grist.d.ts +3 -3
  16. package/dist/src/data-grist.js.map +1 -1
  17. package/dist/src/editors/ox-grist-editor.d.ts +9 -7
  18. package/dist/src/editors/ox-grist-editor.js +9 -7
  19. package/dist/src/editors/ox-grist-editor.js.map +1 -1
  20. package/dist/src/filters/filter-input.js +6 -5
  21. package/dist/src/filters/filter-input.js.map +1 -1
  22. package/dist/src/filters/filter-range-date.js +3 -3
  23. package/dist/src/filters/filter-range-date.js.map +1 -1
  24. package/dist/src/filters/filter-range-number.js +3 -3
  25. package/dist/src/filters/filter-range-number.js.map +1 -1
  26. package/dist/src/gutters/gutter-row-selector.js +6 -6
  27. package/dist/src/gutters/gutter-row-selector.js.map +1 -1
  28. package/dist/src/renderers/ox-grist-renderer-boolean.js +3 -3
  29. package/dist/src/renderers/ox-grist-renderer-boolean.js.map +1 -1
  30. package/dist/src/types.d.ts +5 -5
  31. package/dist/src/types.js.map +1 -1
  32. package/dist/src/utils/column-text.d.ts +10 -10
  33. package/dist/src/utils/column-text.js +10 -10
  34. package/dist/src/utils/column-text.js.map +1 -1
  35. package/dist/test/accessible-name.test.js +84 -86
  36. package/dist/test/accessible-name.test.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +3 -3
  39. package/test/accessible-name.test.ts +84 -86
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/data-grist",
3
- "version": "10.10.0",
3
+ "version": "10.10.1",
4
4
  "description": "User interface for grid (desktop) and list (mobile)",
5
5
  "author": "heartyoh",
6
6
  "type": "module",
@@ -70,7 +70,7 @@
70
70
  "@material/web": "^2.0.0",
71
71
  "@operato/headroom": "^10.0.0",
72
72
  "@operato/i18n": "^10.0.0",
73
- "@operato/input": "^10.10.0",
73
+ "@operato/input": "^10.10.1",
74
74
  "@operato/p13n": "^10.10.0",
75
75
  "@operato/popup": "^10.10.0",
76
76
  "@operato/pull-to-refresh": "^10.0.0",
@@ -115,5 +115,5 @@
115
115
  "prettier --write"
116
116
  ]
117
117
  },
118
- "gitHead": "15f722c8131e5a04b5b4186ac71ada9f94ef9a4f"
118
+ "gitHead": "b40e170b9e6a24e28935300dd2c77d8b8e5ce8a2"
119
119
  }
@@ -8,62 +8,62 @@ import { columnText } from '../src/utils/column-text.js'
8
8
  import { ZERO_CONFIG, ZERO_DATA } from '../src/configure/zero-config.js'
9
9
 
10
10
  /**
11
- * 입력칸의 이름.
11
+ * Accessible names on the inputs inside the grid.
12
12
  *
13
- * ## 왜 시험이 필요한가
13
+ * ## Why this is tested
14
14
  *
15
- * 이 grid 안에는 이름 없는 입력칸이 여럿 있었다 — 줄을 고르는 체크박스, 머리줄의 filter 상자,
16
- * 그리고 포커스를 담고 있는 IME buffer. 눈으로 보는 사람에게는 **자리**가 뜻을 말해 준다(맨 왼쪽
17
- * 좁은 칸이면 줄 선택, 머리줄 이름 바로 아래면 그 column 의 filter). 그런데 자리는 그림일 뿐이고
18
- * 관계가 아니라서, 화면을 못 보는 사람에게는 이름 없는 상자들만 남는다.
15
+ * Several inputs here had no name: the row-select checkboxes, the header filter boxes, and the IME
16
+ * buffer that holds focus. Position carries the meaning for a sighted reader — a narrow leftmost
17
+ * column means row select, a box under a header name filters that column — but position is a
18
+ * picture, not a relationship, so nothing of it reaches a screen reader.
19
19
  *
20
- * ## 무엇을 지키려는가
20
+ * ## What has to hold
21
21
  *
22
- * 이름의 출처가 하나여야 한다. `header` 를 고쳤을 때 읽히는 이름도 함께 바뀌어야 하고, 그러려면
23
- * 부르는 쪽이 이름을 다시 적어 두면 안 된다. `columnText()` 가 그 유일한 출처다.
22
+ * The name has one source. Editing `header` must change what is announced, which means callers
23
+ * cannot spell the name out again. `columnText()` is that source.
24
24
  *
25
- * `buildColumn` 이 문자열 header 를 `() => header` 함수로 바꿔 버리는 자리가 있었는데, 그 함수가
26
- * 돌려주는 것은 lit template 이라 이름을 되꺼낼 수 없다 — 여기서 이름이 사라지고 있었다.
25
+ * `buildColumn` turns a string header into `() => header`, and that function returns a lit template
26
+ * the name cannot be read back out of. The name was disappearing right there.
27
27
  */
28
28
 
29
- describe('column 이름의 출처는 하나다', () => {
30
- it('문자열로 준 header 는 함수로 바뀌어도 글자가 남는다', () => {
29
+ describe('column name has one source', () => {
30
+ it('keeps the plain text after a string header becomes a function', () => {
31
31
  const column = buildColumn({ type: 'string', name: 'palletId', header: '파렛트 번호' })
32
32
 
33
33
  expect(columnText(column)).to.equal('파렛트 번호')
34
34
  })
35
35
 
36
- it('header.renderer 에 문자열을 준 경우도 같다', () => {
36
+ it('keeps it when the string arrives as header.renderer', () => {
37
37
  const column = buildColumn({ type: 'string', name: 'palletId', header: { renderer: '파렛트 번호' } })
38
38
 
39
39
  expect(columnText(column)).to.equal('파렛트 번호')
40
40
  })
41
41
 
42
- it('label: true 는 header 의 이름을 물려받는다', () => {
42
+ it('lets label: true inherit the header name', () => {
43
43
  const column = buildColumn({ type: 'string', name: 'palletId', header: '파렛트 번호', label: true })
44
44
 
45
45
  expect(columnText(column)).to.equal('파렛트 번호')
46
46
  })
47
47
 
48
- it('header 를 함수로 그리면 이름을 되꺼낼 수 없다 — 그때는 column 이름으로 물러난다', () => {
48
+ it('falls back to column.name when the header is drawn by a function', () => {
49
49
  const column = buildColumn({ type: 'string', name: 'palletId', header: { renderer: () => html`<i>x</i>` } })
50
50
 
51
51
  expect(columnText(column)).to.equal('palletId')
52
52
  })
53
53
 
54
- it('column 이 없으면 이름을 지어내지 않는다', () => {
54
+ it('invents no name when there is no column', () => {
55
55
  expect(columnText(undefined)).to.equal('')
56
56
  expect(columnText(null)).to.equal('')
57
57
  })
58
58
  })
59
59
 
60
- describe('줄을 고르는 칸에는 이름이 있다', () => {
60
+ describe('row-select boxes are named', () => {
61
61
  const columns = [
62
62
  { type: 'gutter', gutterName: 'row-selector', name: '__selected__', multiple: true },
63
63
  { type: 'string', name: 'palletId', header: '파렛트 번호' }
64
64
  ].map(buildColumn)
65
65
 
66
- it('행마다 놓인 체크박스가 몇 번째 줄인지 말한다', async () => {
66
+ it('says which row each checkbox selects', async () => {
67
67
  const body: any = await fixture(html`
68
68
  <ox-grid-body
69
69
  .config=${{ ...ZERO_CONFIG, columns }}
@@ -74,7 +74,7 @@ describe('줄을 고르는 칸에는 이름이 있다', () => {
74
74
 
75
75
  await body.updateComplete
76
76
 
77
- /* 체크박스는 각 칸(ox-grid-field)의 shadow root 안에 그려진다 — 한 겹 더 들어가야 보인다. */
77
+ /* The checkboxes live one shadow root deeper, inside each ox-grid-field. */
78
78
  const fields = Array.from(body.renderRoot.querySelectorAll('ox-grid-field')) as any[]
79
79
 
80
80
  await Promise.all(fields.map(field => field.updateComplete))
@@ -83,14 +83,14 @@ describe('줄을 고르는 칸에는 이름이 있다', () => {
83
83
  Array.from(field.renderRoot?.querySelectorAll('input[type=checkbox], input[type=radio]') || [])
84
84
  ) as HTMLInputElement[]
85
85
 
86
- expect(boxes.length, '줄을 고르는 칸을 찾지 못했다').to.be.greaterThan(0)
86
+ expect(boxes.length, 'found no row-select box').to.be.greaterThan(0)
87
87
  boxes.forEach(box =>
88
- expect(box.getAttribute('aria-label'), '이름 없는 체크박스가 남아 있다').to.be.a('string').and.not.equal('')
88
+ expect(box.getAttribute('aria-label'), 'an unnamed checkbox is left').to.be.a('string').and.not.equal('')
89
89
  )
90
90
  })
91
91
  })
92
92
 
93
- describe('포커스를 담고 있는 IME buffer 가 지금 선 칸을 말한다', () => {
93
+ describe('IME buffer names the cell it stands in for', () => {
94
94
  const columns = [
95
95
  { type: 'string', name: 'palletId', header: '파렛트 번호' },
96
96
  { type: 'string', name: 'location', header: '보관 위치' }
@@ -111,41 +111,40 @@ describe('포커스를 담고 있는 IME buffer 가 지금 선 칸을 말한다'
111
111
  return body.renderRoot.querySelector('#ime-buffer') as HTMLInputElement
112
112
  }
113
113
 
114
- it('첫 칸에 서 있으면 첫 칸의 이름을 읽는다', async () => {
114
+ it('reads the first column while focus sits there', async () => {
115
115
  expect((await bodyFocusedAt(0)).getAttribute('aria-label')).to.contain('파렛트 번호')
116
116
  })
117
117
 
118
- it('옆 칸으로 옮기면 읽히는 이름도 바뀐다 — 안 바뀌면 어디에 선 것인지 알 수 없다', async () => {
118
+ it('changes what it reads when focus moves a column over', async () => {
119
119
  expect((await bodyFocusedAt(1)).getAttribute('aria-label')).to.contain('보관 위치')
120
120
  })
121
121
 
122
- it('보조기술에서 지우지 않는다 — 포커스를 받는 자리다', async () => {
122
+ it('stays visible to assistive tech — it is where focus lands', async () => {
123
123
  expect((await bodyFocusedAt(0)).getAttribute('aria-hidden')).to.be.null
124
124
  })
125
125
  })
126
126
 
127
127
  /**
128
- * 표를 표라고 선언했나.
128
+ * Table semantics.
129
129
  *
130
- * ## 왜 시험이 필요한가
130
+ * ## Why this is tested
131
131
  *
132
- * 이 표는 CSS grid 로 그린다 — 칸이 평평하게 놓여 있고 **줄이라는 요소가 없다.** 보기에는 줄이
133
- * 있지만 그것은 같은 높이에 놓였다는 그림일 뿐이다. 그래서 스크린 리더에게 이 화면은 「여기가
134
- * 표다」도, 「지금 3행 3열이다」도, 「전체는 40행이다」도 아닌 **입력칸 더미**였다.
132
+ * The grid is drawn with CSS grid: cells sit flat and there is no row element. Rows are a picture
133
+ * made of equal offsetTop, so a screen reader saw a pile of inputs rather than a table — no "this
134
+ * is a table", no "row 3 column 3", no "40 rows in all".
135
135
  *
136
- * 줄 감싸개를 넣으면서 두 가지가 동시에 깨질 수 있다.
136
+ * Adding row wrappers can break two things at once:
137
137
  *
138
138
  * ```
139
- * 뜻이 서나 role=grid / row / columnheader / gridcell 이 제대로 겹쳐 있나
140
- * 모양이 사나 display: contents 가 정말로 상자를 안 만드나 — 만들면 표가 한 줄로 무너진다
139
+ * semantics grid / row / columnheader / gridcell have to nest correctly
140
+ * layout display: contents must generate no box; if it does, the columns collapse
141
141
  * ```
142
142
  *
143
- * 두 번째가 특히 중요하다. `display: contents` 를 잘못 걸면 칸들이 grid 항목이 아니게 되어
144
- * 열이 사라진다. 그래서 **자리를 실제로 확인한다** — 한 줄 안의 칸은 왼쪽으로 갈수록 x 가 커지고,
145
- * 줄과 줄은 y 가 달라야 한다.
143
+ * The second one is why geometry is asserted: cells in a row must step right, and rows must step
144
+ * down.
146
145
  */
147
- describe('표를 표라고 선언한다', () => {
148
- /* ox-grid 는 이미 다듬어진 column 을 받는다 — 다듬는 것은 ox-grist 의 일이다. */
146
+ describe('grid declares itself a grid', () => {
147
+ /* ox-grid takes already-compiled columns; compiling them is ox-grist's job. */
149
148
  const columns = [
150
149
  { type: 'gutter', gutterName: 'row-selector', name: '__selected__', multiple: true },
151
150
  { type: 'string', name: 'palletId', header: '파렛트 번호', sortable: true },
@@ -181,12 +180,12 @@ describe('표를 표라고 선언한다', () => {
181
180
  return { el, header, body }
182
181
  }
183
182
 
184
- it('표 · 줄 · 칸이 겹쳐 있다', async () => {
183
+ it('nests grid, row and cell', async () => {
185
184
  const { el, header, body } = await grid()
186
185
 
187
186
  const grid_ = el.renderRoot.querySelector('[role=grid]')
188
187
 
189
- expect(grid_, 'role=grid 가 없다').to.exist
188
+ expect(grid_, 'no role=grid').to.exist
190
189
  expect(header.getAttribute('role')).to.equal('rowgroup')
191
190
  expect(body.getAttribute('role')).to.equal('rowgroup')
192
191
 
@@ -195,38 +194,38 @@ describe('표를 표라고 선언한다', () => {
195
194
  expect(header.renderRoot.querySelectorAll('[role=columnheader]').length).to.equal(3)
196
195
  })
197
196
 
198
- it('전체가 몇 줄 몇 열인지 말한다 — 지금 페이지가 아니라', async () => {
197
+ it('counts the whole table, not the page on screen', async () => {
199
198
  const { el } = await grid()
200
199
 
201
200
  const grid_ = el.renderRoot.querySelector('[role=grid]')
202
201
 
203
- /* 머리 한 줄 + 전체 40건. 화면에 두 줄만 그려도 40건짜리 표다. */
202
+ /* One header row plus 40 records. Two rows drawn is still a 40-row table. */
204
203
  expect(grid_.getAttribute('aria-rowcount')).to.equal('41')
205
204
  expect(grid_.getAttribute('aria-colcount')).to.equal('3')
206
205
  expect(grid_.getAttribute('aria-label')).to.equal('입고 대기 목록')
207
206
  })
208
207
 
209
- it('2페이지의 첫 줄은 21번째 줄이다 — 페이지마다 1번으로 돌아가지 않는다', async () => {
208
+ it('numbers page 2 from row 22, not back at 1', async () => {
210
209
  const { body } = await grid({ page: 2 })
211
210
 
212
211
  const rows = Array.from(body.renderRoot.querySelectorAll('[role=row]')) as HTMLElement[]
213
212
 
214
- /* 머리 한 줄(1) + 앞 페이지 20건 = 22번째부터 */
213
+ /* One header row plus 20 records on page 1. */
215
214
  expect(rows[0].getAttribute('aria-rowindex')).to.equal('22')
216
215
  expect(rows[1].getAttribute('aria-rowindex')).to.equal('23')
217
216
  })
218
217
 
219
- it('칸이 몇 번째 열인지 말한다 — 남는 폭을 메우는 칸은 세지 않는다', async () => {
218
+ it('numbers columns and leaves the width filler uncounted', async () => {
220
219
  const { body } = await grid()
221
220
 
222
221
  const row = body.renderRoot.querySelector('[role=row]') as HTMLElement
223
222
  const cells = Array.from(row.querySelectorAll('[role=gridcell]')) as HTMLElement[]
224
223
 
225
224
  expect(cells.map(cell => cell.getAttribute('aria-colindex'))).to.deep.equal(['1', '2', '3'])
226
- expect(row.querySelectorAll('[role=presentation]').length, '메우는 칸이 표의 칸으로 세어졌다').to.equal(1)
225
+ expect(row.querySelectorAll('[role=presentation]').length, 'the filler was counted as a cell').to.equal(1)
227
226
  })
228
227
 
229
- it('정렬 상태를 글자로도 말한다 — 화살표는 눈으로만 읽힌다', async () => {
228
+ it('states sort direction in text, not only as an arrow', async () => {
230
229
  const el: any = await fixture(html`
231
230
  <ox-grid
232
231
  .config=${{ ...ZERO_CONFIG, columns, rows: { ...ZERO_CONFIG.rows } }}
@@ -243,40 +242,39 @@ describe('표를 표라고 선언한다', () => {
243
242
  const headers = Array.from(header.renderRoot.querySelectorAll('[role=columnheader]')) as HTMLElement[]
244
243
  const sorts = headers.map(cell => cell.getAttribute('aria-sort'))
245
244
 
246
- /* 거터는 정렬할 수 없다 — 있지도 않은 기능을 있다고 말하지 않는다. */
245
+ /* A gutter cannot be sorted; claiming otherwise announces a control that is not there. */
247
246
  expect(sorts[0]).to.be.null
248
- expect(sorts[1], '내림차순으로 정렬된 열').to.equal('descending')
249
- expect(sorts[2], '정렬할 수 있으나 지금은 아닌 열').to.equal('none')
247
+ expect(sorts[1], 'sorted descending').to.equal('descending')
248
+ expect(sorts[2], 'sortable but not currently sorted').to.equal('none')
250
249
  })
251
250
 
252
- it('줄 감싸개가 배치를 건드리지 않는다 — 상자를 만들면 표가 무너진다', async () => {
251
+ it('keeps the layout — a row wrapper that generates a box collapses the grid', async () => {
253
252
  const { body } = await grid()
254
253
 
255
254
  const rows = Array.from(body.renderRoot.querySelectorAll('[role=row]')) as HTMLElement[]
256
255
  const first = Array.from(rows[0].children) as HTMLElement[]
257
256
 
258
- /* 감싸개 자신은 상자가 없다 */
259
257
  expect(getComputedStyle(rows[0]).display).to.equal('contents')
260
258
 
261
- /* 한 줄 안의 칸은 오른쪽으로 나아간다 — 한 자리에 겹쳐 있으면 열이 사라진 것이다 */
259
+ /* Cells step right; stacked at one x means the columns are gone. */
262
260
  const lefts = first.map(cell => cell.offsetLeft)
263
- expect(new Set(lefts).size, '칸들이 같은 x 에 겹쳐 있다').to.be.greaterThan(1)
261
+ expect(new Set(lefts).size, 'cells stacked at the same x').to.be.greaterThan(1)
264
262
  lefts.slice(1).forEach((left, index) => expect(left).to.be.greaterThan(lefts[index]))
265
263
 
266
- /* 줄과 줄은 아래로 내려간다 */
264
+ /* Rows step down. */
267
265
  const firstTop = (rows[0].children[0] as HTMLElement).offsetTop
268
266
  const secondTop = (rows[1].children[0] as HTMLElement).offsetTop
269
- expect(secondTop, '두 줄이 같은 y 에 있다').to.be.greaterThan(firstTop)
267
+ expect(secondTop, 'two rows at the same y').to.be.greaterThan(firstTop)
270
268
  })
271
269
  })
272
270
 
273
271
  /**
274
- * 묶음 머리가 있는 표.
272
+ * Grouped headers.
275
273
  *
276
- * 머리가 두 줄이 되고, 묶이지 않은 열은 두 줄에 걸친다(`.span-both`). 배치가 CSS grid 의
277
- * `grid-row` / `grid-column` 으로 이루어지므로 **줄 감싸개가 가장 위험한 자리**다.
274
+ * The header becomes two rows and ungrouped columns span both (`.span-both`). Placement runs
275
+ * through CSS `grid-row` / `grid-column`, which makes this the riskiest spot for a row wrapper.
278
276
  */
279
- describe('묶음 머리 — 머리가 두 줄이다', () => {
277
+ describe('grouped header spans two rows', () => {
280
278
  const columns = [
281
279
  { type: 'string', name: 'palletId', header: '파렛트 번호' },
282
280
  { type: 'string', name: 'width', header: { renderer: '폭', group: '치수' } },
@@ -302,67 +300,67 @@ describe('묶음 머리 — 머리가 두 줄이다', () => {
302
300
  return { el, header, body }
303
301
  }
304
302
 
305
- it('머리가 두 줄로 세어진다 — 본문은 3번째 줄부터', async () => {
303
+ it('counts two header rows so the body starts at row 3', async () => {
306
304
  const { el, header, body } = await grouped()
307
305
 
308
306
  expect(header.renderRoot.querySelectorAll('[role=row]').length).to.equal(2)
309
307
 
310
- /* 머리 두 줄 + 1건 */
308
+ /* Two header rows plus one record. */
311
309
  expect(el.renderRoot.querySelector('[role=grid]').getAttribute('aria-rowcount')).to.equal('3')
312
310
 
313
311
  const rows = Array.from(body.renderRoot.querySelectorAll('[role=row]')) as HTMLElement[]
314
312
  expect(rows[0].getAttribute('aria-rowindex')).to.equal('3')
315
313
  })
316
314
 
317
- it('묶음 머리가 몇 열을 덮는지 말한다', async () => {
315
+ it('says how many columns a group covers', async () => {
318
316
  const { header } = await grouped()
319
317
 
320
318
  const cells = Array.from(header.renderRoot.querySelectorAll('[role=columnheader]')) as HTMLElement[]
321
319
  const first = cells[0]
322
320
  const group = cells.find(cell => cell.getAttribute('aria-colspan'))
323
321
 
324
- /* 묶이지 않은 열은 두 줄에 걸친다 */
322
+ /* An ungrouped column spans both header rows. */
325
323
  expect(first.getAttribute('aria-rowspan')).to.equal('2')
326
324
  expect(first.getAttribute('aria-colindex')).to.equal('1')
327
325
 
328
- /* 치수 묶음은 2 · 3열을 덮는다 */
329
- expect(group, '묶음 머리를 찾지 못했다').to.exist
326
+ /* The group header covers columns 2 and 3. */
327
+ expect(group, 'found no group header').to.exist
330
328
  expect(group!.getAttribute('aria-colindex')).to.equal('2')
331
329
  expect(group!.getAttribute('aria-colspan')).to.equal('2')
332
330
  })
333
331
 
334
- it('두 줄짜리 머리의 배치가 그대로다 — 묶음이 아래 줄보다 위에 있다', async () => {
332
+ it('keeps two-row header placement — group sits above its leaf columns', async () => {
335
333
  const { header } = await grouped()
336
334
 
337
335
  const cells = Array.from(header.renderRoot.querySelectorAll('[role=columnheader]')) as HTMLElement[]
338
336
  const group = cells.find(cell => cell.getAttribute('aria-colspan'))!
339
337
  const leaf = cells.find(cell => !cell.getAttribute('aria-colspan') && cell !== cells[0])!
340
338
 
341
- expect(leaf.offsetTop, '묶음 머리와 그 아래 열이 같은 높이에 있다').to.be.greaterThan(group.offsetTop)
342
- expect(group.offsetWidth, '묶음 머리가 두 열을 덮지 않는다').to.be.greaterThan(leaf.offsetWidth)
339
+ expect(leaf.offsetTop, 'group header and its leaf column at the same height').to.be.greaterThan(group.offsetTop)
340
+ expect(group.offsetWidth, 'group header does not cover two columns').to.be.greaterThan(leaf.offsetWidth)
343
341
  })
344
342
  })
345
343
 
346
344
  /**
347
- * 표 감싸개가 바깥 배치를 건드리지 않나.
345
+ * The grid wrapper must not disturb the surrounding layout.
348
346
  *
349
- * `role=grid` 안에는 줄만 들어갈 수 있어서, 설정 단추 자리와 페이지 넘김은 감싸개 **밖**으로
350
- * 나왔다. 그러면서 두 가지가 이 구조에 매인다.
347
+ * `role=grid` admits only rows, so the settings-button slot and the pager moved outside the
348
+ * wrapper. Two things then depend on this structure:
351
349
  *
352
350
  * ```
353
- * 세로 배치 호스트는 flex column 이다. 감싸개가 상자를 만들면 머리와 몸이 그 안으로 들어가
354
- * flex 항목이 하나가 되고, 몸의 flex: 1 이 듣지 않아 높이가 무너진다
355
- * 형제 선택자 「머리에 올렸을 때 설정 단추를 보인다」가 인접 형제 선택자다. 설정 단추가 감싸개
356
- * 바로 뒤에 있어야 그 규칙이 성립한다
351
+ * column layout the host is a flex column. A wrapper that generates a box swallows header
352
+ * and body into one flex item, and the body's flex: 1 stops working
353
+ * sibling selector "show the settings button on header hover" is an adjacent-sibling rule, so
354
+ * the button has to sit directly after the wrapper
357
355
  * ```
358
356
  */
359
- describe('표 감싸개는 배치에 끼어들지 않는다', () => {
357
+ describe('grid wrapper stays out of the layout', () => {
360
358
  const columns = [
361
359
  { type: 'string', name: 'palletId', header: '파렛트 번호' },
362
360
  { type: 'string', name: 'location', header: '보관 위치' }
363
361
  ].map(buildColumn)
364
362
 
365
- it('설정 단추는 표 바로 뒤에 있다 — 표 안이 아니라', async () => {
363
+ it('places the settings button after the grid, not inside it', async () => {
366
364
  const el: any = await fixture(html`
367
365
  <ox-grid
368
366
  .config=${{ ...ZERO_CONFIG, columns, rows: { ...ZERO_CONFIG.rows } }}
@@ -375,12 +373,12 @@ describe('표 감싸개는 배치에 끼어들지 않는다', () => {
375
373
  const grid = el.renderRoot.querySelector('[role=grid]') as HTMLElement
376
374
  const setting = el.renderRoot.querySelector('div[setting]') as HTMLElement
377
375
 
378
- expect(setting, '설정 단추 자리가 없다').to.exist
379
- expect(grid.contains(setting), '설정 단추가 표 안에 들어갔다 — 표 읽기가 깨진다').to.be.false
380
- expect(grid.nextElementSibling, '인접 형제 규칙이 성립하지 않는다').to.equal(setting)
376
+ expect(setting, 'no settings slot').to.exist
377
+ expect(grid.contains(setting), 'settings button inside the grid breaks table reading').to.be.false
378
+ expect(grid.nextElementSibling, 'adjacent-sibling rule no longer holds').to.equal(setting)
381
379
  })
382
380
 
383
- it('머리와 몸이 그대로 호스트의 세로 배치를 쓴다', async () => {
381
+ it('leaves header and body as flex items of the host', async () => {
384
382
  const el: any = await fixture(html`
385
383
  <div style="height: 300px; display: flex">
386
384
  <ox-grid
@@ -400,9 +398,9 @@ describe('표 감싸개는 배치에 끼어들지 않는다', () => {
400
398
 
401
399
  expect(getComputedStyle(wrapper).display).to.equal('contents')
402
400
 
403
- /* 몸이 남는 높이를 다 쓴다 — 감싸개가 상자를 만들면 이 값이 머리 높이만큼으로 줄어든다 */
404
- expect(body.offsetHeight, '몸이 남는 높이를 쓰지 못한다').to.be.greaterThan(100)
405
- expect(body.offsetTop, '몸이 머리 아래에 있지 않다').to.be.greaterThan(0)
401
+ /* The body takes the remaining height; a wrapper with a box shrinks it to header height. */
402
+ expect(body.offsetHeight, 'body does not take the remaining height').to.be.greaterThan(100)
403
+ expect(body.offsetTop, 'body is not below the header').to.be.greaterThan(0)
406
404
  expect(header.offsetTop).to.equal(0)
407
405
  })
408
406
  })