@operato/data-grist 10.29.1 → 10.29.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,15 @@ function getsec(time) {
46
46
  var arr = time.split(':');
47
47
  return Number(arr[0]) * 3600 + Number(arr[1]) * 60 + (Number(arr[2]) || 0);
48
48
  }
49
+ /*
50
+ * ── 달력의 날은 시간대를 타지 않는다 (2026-09-30) ──────────────────────────
51
+ * 'YYYY-MM-DD' 는 어느 나라에서든 같은 날이다(입사일·퇴사일). 그런데 new Date('2025-12-31') 은 그것을
52
+ * UTC 자정으로 읽어서, UTC 서쪽(아메리카 — America/Tijuana 등)에서는 격자에 2025-12-30 이 나왔다.
53
+ *
54
+ * date 칸의 날 문자열은 UTC 자정으로 만들고 UTC 로 적는다. 브라우저 시간대가 끼어들 자리가 없다.
55
+ * datetime 과 숫자 타임스탬프는 순간이므로 전처럼 브라우저 시간대로 적는다.
56
+ */
57
+ const CALENDAR_DAY = /^\d{4}-\d{2}-\d{2}$/;
49
58
  /*
50
59
  TODO date, time, month, week 등 datetime 이 외의 타입인 경우에는 문자열로 처리하도록 한다.
51
60
  따라서, 이 경우는 타임존에 대한 처리가 불필요하다.
@@ -59,8 +68,14 @@ export const OxGristRendererDate = (value, column, record, rowIndex, field) => {
59
68
  var options = column.record.options || OPTIONS[column.type];
60
69
  var formatter = new Intl.DateTimeFormat(locale(), options);
61
70
  switch (column.type) {
62
- case 'datetime':
63
71
  case 'date':
72
+ if (typeof value === 'string' && CALENDAR_DAY.test(value)) {
73
+ const [y, m, d] = value.split('-').map(Number);
74
+ value = new Intl.DateTimeFormat(locale(), { ...options, timeZone: 'UTC' }).format(new Date(Date.UTC(y, m - 1, d)));
75
+ break;
76
+ }
77
+ // falls through
78
+ case 'datetime':
64
79
  value = formatter.format(new Date(value));
65
80
  break;
66
81
  case 'time':
@@ -1 +1 @@
1
- {"version":3,"file":"ox-grist-renderer-date.js","sourceRoot":"","sources":["../../../src/renderers/ox-grist-renderer-date.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAE1B,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC;;;;;;;;GAQG;AACH,SAAS,MAAM;IACb,OAAO,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,KAAI,SAAS,CAAA;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG;IACvB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,KAAK;CACd,CAAA;AAED,MAAM,YAAY,GAAG;IACnB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;CACf,CAAA;AAED,MAAM,YAAY,GAAG;IACnB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;CAClB,CAAA;AAED,MAAM,OAAO,GAAG;IACd,QAAQ,EAAE,gBAAgB;IAC1B,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;CACnB,CAAA;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5E,CAAC;AAED;;;EAGE;AACF,MAAM,CAAC,MAAM,mBAAmB,GAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAC3F,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAA;IACX,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAEhD,IAAI,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,IAAoC,CAAC,CAAA;IAC3F,IAAI,SAAS,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAA;IAE1D,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,UAAU,CAAC;QAChB,KAAK,MAAM;YACT,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;YACzC,MAAK;QACP,KAAK,MAAM,CAAC;QACZ,QAAQ;IACV,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA,oBAAoB,CAAA;AACvE,CAAC,CAAA","sourcesContent":["import { FieldRenderer } from '../types.js'\nimport { html } from 'lit'\n\nimport { i18next } from '@operato/i18n'\n\n/*\n * ── 로케일은 앱이 정한다 (2026-09-01) ──────────────────────────────────────\n * 전에는 navigator.language 를 썼다. 그것은 **브라우저의 언어**이고 앱의 언어 설정이 아니다. 그래서\n * 화면은 한국어인데 날짜가 9/1/2026 처럼 미국식으로 나왔다. 이 파일에 그 사실을 아는 TODO 가 남아\n * 있었다.\n *\n * i18next.language 를 쓴다 — 사용자가 화면에서 고른 그 언어다. 값이 없을 때는 undefined 를 넘겨\n * 브라우저 기본으로 떨어진다(빈 문자열을 넘기면 Intl 이 던진다).\n */\nfunction locale(): string | undefined {\n return i18next?.language || undefined\n}\n\n/*\n * ── 초는 적지 않는다 ───────────────────────────────────────────────────────\n * 목록에서 초는 아무도 쓰지 않는 정밀도인데 기본값이 그것을 넣고 있었다 — 「9/1/2026, 12:48:27」.\n * 칸 하나가 그만큼 넓어지고, 읽는 사람은 앞의 날짜를 찾느라 뒤의 숫자를 지나쳐야 한다.\n *\n * 초가 필요한 화면은 컬럼에 options 를 적어 그 뜻을 밝힌다. 그 자리가 예외를 적는 자리다.\n */\nconst DATETIME_OPTIONS = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: false\n}\n\nconst DATE_OPTIONS = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric'\n}\n\nconst TIME_OPTIONS = {\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric'\n}\n\nconst OPTIONS = {\n datetime: DATETIME_OPTIONS,\n time: TIME_OPTIONS,\n date: DATE_OPTIONS\n}\n\nfunction getsec(time: string): number {\n var arr = time.split(':')\n return Number(arr[0]) * 3600 + Number(arr[1]) * 60 + (Number(arr[2]) || 0)\n}\n\n/* \n TODO date, time, month, week 등 datetime 이 외의 타입인 경우에는 문자열로 처리하도록 한다. \n 따라서, 이 경우는 타임존에 대한 처리가 불필요하다.\n*/\nexport const OxGristRendererDate: FieldRenderer = (value, column, record, rowIndex, field) => {\n if (!value) {\n return ''\n }\n\n if (!isNaN(Number(value))) value = Number(value)\n\n var options = column.record.options || OPTIONS[column.type as 'datetime' | 'date' | 'time']\n var formatter = new Intl.DateTimeFormat(locale(), options)\n\n switch (column.type) {\n case 'datetime':\n case 'date':\n value = formatter.format(new Date(value))\n break\n case 'time':\n default:\n }\n\n return value ? html`<span>${value}</span>` : html`<span>&nbsp</span>`\n}\n"]}
1
+ {"version":3,"file":"ox-grist-renderer-date.js","sourceRoot":"","sources":["../../../src/renderers/ox-grist-renderer-date.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldRenderer } from '../types.js'\nimport { html } from 'lit'\n\nimport { i18next } from '@operato/i18n'\n\n/*\n * ── 로케일은 앱이 정한다 (2026-09-01) ──────────────────────────────────────\n * 전에는 navigator.language 를 썼다. 그것은 **브라우저의 언어**이고 앱의 언어 설정이 아니다. 그래서\n * 화면은 한국어인데 날짜가 9/1/2026 처럼 미국식으로 나왔다. 이 파일에 그 사실을 아는 TODO 가 남아\n * 있었다.\n *\n * i18next.language 를 쓴다 — 사용자가 화면에서 고른 그 언어다. 값이 없을 때는 undefined 를 넘겨\n * 브라우저 기본으로 떨어진다(빈 문자열을 넘기면 Intl 이 던진다).\n */\nfunction locale(): string | undefined {\n return i18next?.language || undefined\n}\n\n/*\n * ── 초는 적지 않는다 ───────────────────────────────────────────────────────\n * 목록에서 초는 아무도 쓰지 않는 정밀도인데 기본값이 그것을 넣고 있었다 — 「9/1/2026, 12:48:27」.\n * 칸 하나가 그만큼 넓어지고, 읽는 사람은 앞의 날짜를 찾느라 뒤의 숫자를 지나쳐야 한다.\n *\n * 초가 필요한 화면은 컬럼에 options 를 적어 그 뜻을 밝힌다. 그 자리가 예외를 적는 자리다.\n */\nconst DATETIME_OPTIONS = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: false\n}\n\nconst DATE_OPTIONS = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric'\n}\n\nconst TIME_OPTIONS = {\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric'\n}\n\nconst OPTIONS = {\n datetime: DATETIME_OPTIONS,\n time: TIME_OPTIONS,\n date: DATE_OPTIONS\n}\n\nfunction getsec(time: string): number {\n var arr = time.split(':')\n return Number(arr[0]) * 3600 + Number(arr[1]) * 60 + (Number(arr[2]) || 0)\n}\n\n/*\n * ── 달력의 날은 시간대를 타지 않는다 (2026-09-30) ──────────────────────────\n * 'YYYY-MM-DD' 는 어느 나라에서든 같은 날이다(입사일·퇴사일). 그런데 new Date('2025-12-31') 은 그것을\n * UTC 자정으로 읽어서, UTC 서쪽(아메리카 — America/Tijuana 등)에서는 격자에 2025-12-30 이 나왔다.\n *\n * date 칸의 날 문자열은 UTC 자정으로 만들고 UTC 로 적는다. 브라우저 시간대가 끼어들 자리가 없다.\n * datetime 과 숫자 타임스탬프는 순간이므로 전처럼 브라우저 시간대로 적는다.\n */\nconst CALENDAR_DAY = /^\\d{4}-\\d{2}-\\d{2}$/\n\n/* \n TODO date, time, month, week 등 datetime 이 외의 타입인 경우에는 문자열로 처리하도록 한다. \n 따라서, 이 경우는 타임존에 대한 처리가 불필요하다.\n*/\nexport const OxGristRendererDate: FieldRenderer = (value, column, record, rowIndex, field) => {\n if (!value) {\n return ''\n }\n\n if (!isNaN(Number(value))) value = Number(value)\n\n var options = column.record.options || OPTIONS[column.type as 'datetime' | 'date' | 'time']\n var formatter = new Intl.DateTimeFormat(locale(), options)\n\n switch (column.type) {\n case 'date':\n if (typeof value === 'string' && CALENDAR_DAY.test(value)) {\n const [y, m, d] = value.split('-').map(Number)\n value = new Intl.DateTimeFormat(locale(), { ...options, timeZone: 'UTC' }).format(new Date(Date.UTC(y, m - 1, d)))\n break\n }\n // falls through\n case 'datetime':\n value = formatter.format(new Date(value))\n break\n case 'time':\n default:\n }\n\n return value ? html`<span>${value}</span>` : html`<span>&nbsp</span>`\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { render } from 'lit';
3
+ import { OxGristRendererDate } from '../src/renderers/ox-grist-renderer-date.js';
4
+ /**
5
+ * 'YYYY-MM-DD' 는 달력의 날이다. 브라우저가 UTC 서쪽에 있어도 그 날 그대로 나와야 한다.
6
+ *
7
+ * 시험 브라우저의 시간대는 고를 수 없으므로, 시간대를 적지 않은 Intl.DateTimeFormat 을 America/Tijuana
8
+ * 로 돌린다 — 그 시간대의 브라우저가 날짜를 적는 길과 같다.
9
+ */
10
+ const WEST = 'America/Tijuana';
11
+ function renderText(value, type) {
12
+ const column = { type, record: {} };
13
+ const host = document.createElement('div');
14
+ render(OxGristRendererDate(value, column, {}, 0, undefined), host);
15
+ return host.textContent.trim();
16
+ }
17
+ describe('OxGristRendererDate — calendar day', () => {
18
+ const Original = Intl.DateTimeFormat;
19
+ beforeEach(() => {
20
+ ;
21
+ Intl.DateTimeFormat = function (locales, options = {}) {
22
+ return new Original(locales, { timeZone: WEST, ...options });
23
+ };
24
+ });
25
+ afterEach(() => {
26
+ ;
27
+ Intl.DateTimeFormat = Original;
28
+ });
29
+ it("renders '2025-12-31' as Dec 31 west of UTC", () => {
30
+ const expected = new Original(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', timeZone: 'UTC' }).format(Date.UTC(2025, 11, 31));
31
+ expect(renderText('2025-12-31', 'date')).to.equal(expected);
32
+ });
33
+ it('still renders a datetime instant in the browser time zone', () => {
34
+ // 2025-12-31T03:00Z is Dec 30 evening in Tijuana.
35
+ const text = renderText('2025-12-31T03:00:00Z', 'datetime');
36
+ const expected = new Original(undefined, {
37
+ year: 'numeric',
38
+ month: 'numeric',
39
+ day: 'numeric',
40
+ hour: 'numeric',
41
+ minute: '2-digit',
42
+ hour12: false,
43
+ timeZone: WEST
44
+ }).format(new Date('2025-12-31T03:00:00Z'));
45
+ expect(text).to.equal(expected);
46
+ });
47
+ });
48
+ //# sourceMappingURL=renderer-date-calendar-day.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer-date-calendar-day.test.js","sourceRoot":"","sources":["../../test/renderer-date-calendar-day.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAA;AAEhF;;;;;GAKG;AACH,MAAM,IAAI,GAAG,iBAAiB,CAAA;AAE9B,SAAS,UAAU,CAAC,KAAU,EAAE,IAAY;IAC1C,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAS,CAAA;IAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC1C,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,SAAgB,CAAQ,EAAE,IAAI,CAAC,CAAA;IAChF,OAAO,IAAI,CAAC,WAAY,CAAC,IAAI,EAAE,CAAA;AACjC,CAAC;AAED,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAA;IAEpC,UAAU,CAAC,GAAG,EAAE;QACd,CAAC;QAAC,IAAY,CAAC,cAAc,GAAG,UAAU,OAAa,EAAE,UAAe,EAAE;YACxE,OAAO,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAA;QAC9D,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,CAAC;QAAC,IAAY,CAAC,cAAc,GAAG,QAAQ,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CACrH,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CACvB,CAAA;QACD,MAAM,CAAC,UAAU,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,kDAAkD;QAClD,MAAM,IAAI,GAAG,UAAU,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAA;QAC3D,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,SAAS,EAAE;YACvC,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAA;QAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { expect } from '@open-wc/testing'\nimport { render } from 'lit'\n\nimport { OxGristRendererDate } from '../src/renderers/ox-grist-renderer-date.js'\n\n/**\n * 'YYYY-MM-DD' 는 달력의 날이다. 브라우저가 UTC 서쪽에 있어도 그 날 그대로 나와야 한다.\n *\n * 시험 브라우저의 시간대는 고를 수 없으므로, 시간대를 적지 않은 Intl.DateTimeFormat 을 America/Tijuana\n * 로 돌린다 — 그 시간대의 브라우저가 날짜를 적는 길과 같다.\n */\nconst WEST = 'America/Tijuana'\n\nfunction renderText(value: any, type: string): string {\n const column = { type, record: {} } as any\n const host = document.createElement('div')\n render(OxGristRendererDate(value, column, {}, 0, undefined as any) as any, host)\n return host.textContent!.trim()\n}\n\ndescribe('OxGristRendererDate — calendar day', () => {\n const Original = Intl.DateTimeFormat\n\n beforeEach(() => {\n ;(Intl as any).DateTimeFormat = function (locales?: any, options: any = {}) {\n return new Original(locales, { timeZone: WEST, ...options })\n }\n })\n\n afterEach(() => {\n ;(Intl as any).DateTimeFormat = Original\n })\n\n it(\"renders '2025-12-31' as Dec 31 west of UTC\", () => {\n const expected = new Original(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', timeZone: 'UTC' }).format(\n Date.UTC(2025, 11, 31)\n )\n expect(renderText('2025-12-31', 'date')).to.equal(expected)\n })\n\n it('still renders a datetime instant in the browser time zone', () => {\n // 2025-12-31T03:00Z is Dec 30 evening in Tijuana.\n const text = renderText('2025-12-31T03:00:00Z', 'datetime')\n const expected = new Original(undefined, {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: false,\n timeZone: WEST\n }).format(new Date('2025-12-31T03:00:00Z'))\n expect(text).to.equal(expected)\n })\n})\n"]}