@operato/event-view 10.10.2 → 10.15.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.
- package/CHANGELOG.md +35 -0
- package/coverage/lcov-report/index.html +66 -21
- package/coverage/lcov.info +6394 -3195
- package/dist/src/calendar-monthly-styles.js +242 -12
- package/dist/src/calendar-monthly-styles.js.map +1 -1
- package/dist/src/calendar-monthly.d.ts +8 -1
- package/dist/src/calendar-monthly.js +90 -17
- package/dist/src/calendar-monthly.js.map +1 -1
- package/dist/src/calendar-weekly-styles.js +136 -13
- package/dist/src/calendar-weekly-styles.js.map +1 -1
- package/dist/src/calendar-weekly.d.ts +8 -1
- package/dist/src/calendar-weekly.js +92 -12
- package/dist/src/calendar-weekly.js.map +1 -1
- package/dist/src/day-overflow.d.ts +42 -0
- package/dist/src/day-overflow.js +71 -0
- package/dist/src/day-overflow.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/ox-event-view.d.ts +3 -0
- package/dist/src/ox-event-view.js +33 -2
- package/dist/src/ox-event-view.js.map +1 -1
- package/dist/src/types.d.ts +66 -1
- package/dist/src/types.js +64 -1
- package/dist/src/types.js.map +1 -1
- package/dist/test/calendar-layout.test.js +102 -0
- package/dist/test/calendar-layout.test.js.map +1 -1
- package/dist/test/day-overflow.test.d.ts +1 -0
- package/dist/test/day-overflow.test.js +81 -0
- package/dist/test/day-overflow.test.js.map +1 -0
- package/dist/test/day-state.test.d.ts +1 -0
- package/dist/test/day-state.test.js +115 -0
- package/dist/test/day-state.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/test/calendar-layout.test.ts +130 -0
- package/test/day-overflow.test.ts +103 -0
- package/test/day-state.test.ts +139 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What did not fit in a day's cell.
|
|
3
|
+
*
|
|
4
|
+
* ── Why the grid has to cap a cell at all ─────────────────────────────────────
|
|
5
|
+
* A grid row is as tall as its tallest cell, so one busy day sets the height of every day beside
|
|
6
|
+
* it. Measured on the plant calendar's week view: 767 events on the 8th made all seven cells
|
|
7
|
+
* 670px, the two empty ones included.
|
|
8
|
+
*
|
|
9
|
+
* Folding the rows helps and does not finish the job — fold by production line and a factory with
|
|
10
|
+
* twenty lines still has twenty folded rows in that cell. So the cell takes a ceiling.
|
|
11
|
+
*
|
|
12
|
+
* ── A ceiling that says nothing is the defect it replaces ─────────────────────
|
|
13
|
+
* Clipping hides rows exactly the way the old "+763 more" hid them: you cannot tell what went
|
|
14
|
+
* missing. The difference has to be that somebody says so.
|
|
15
|
+
*
|
|
16
|
+
* This component is the wrong one to say it. It does not know the host's words, and the host is
|
|
17
|
+
* already drawing the fold rows and owns the popup that opens them. So the component measures and
|
|
18
|
+
* reports, and the host decides — fold harder, draw its own marker, raise the ceiling. A host that
|
|
19
|
+
* does nothing gets a grid with even rows and no notice, which is still better than one cell
|
|
20
|
+
* setting the height of a week, but it is not the finished shape.
|
|
21
|
+
*
|
|
22
|
+
* ── Why not scroll the cell instead ──────────────────────────────────────────
|
|
23
|
+
* A scrollbar does announce itself, and in a month grid it is a 120px scroll area inside a page
|
|
24
|
+
* that also scrolls. The nesting costs more than the announcement is worth at that size.
|
|
25
|
+
*/
|
|
26
|
+
export type DayOverflow = {
|
|
27
|
+
date: Date;
|
|
28
|
+
/** How many of the day's rows are cut off or entirely below the fold. */
|
|
29
|
+
hidden: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Measure every capped cell and tell the host what it lost, when that changes.
|
|
33
|
+
*
|
|
34
|
+
* Returns a signature to hand back on the next call. The comparison matters: this runs after
|
|
35
|
+
* every render, and a host that re-renders in response to the event would otherwise be told the
|
|
36
|
+
* same thing forever.
|
|
37
|
+
*
|
|
38
|
+
* @param host the element the event comes from
|
|
39
|
+
* @param root where the cells live
|
|
40
|
+
* @param previous the signature this function returned last time
|
|
41
|
+
*/
|
|
42
|
+
export declare function reportDayOverflow(host: HTMLElement, root: ShadowRoot | null, previous: string): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What did not fit in a day's cell.
|
|
3
|
+
*
|
|
4
|
+
* ── Why the grid has to cap a cell at all ─────────────────────────────────────
|
|
5
|
+
* A grid row is as tall as its tallest cell, so one busy day sets the height of every day beside
|
|
6
|
+
* it. Measured on the plant calendar's week view: 767 events on the 8th made all seven cells
|
|
7
|
+
* 670px, the two empty ones included.
|
|
8
|
+
*
|
|
9
|
+
* Folding the rows helps and does not finish the job — fold by production line and a factory with
|
|
10
|
+
* twenty lines still has twenty folded rows in that cell. So the cell takes a ceiling.
|
|
11
|
+
*
|
|
12
|
+
* ── A ceiling that says nothing is the defect it replaces ─────────────────────
|
|
13
|
+
* Clipping hides rows exactly the way the old "+763 more" hid them: you cannot tell what went
|
|
14
|
+
* missing. The difference has to be that somebody says so.
|
|
15
|
+
*
|
|
16
|
+
* This component is the wrong one to say it. It does not know the host's words, and the host is
|
|
17
|
+
* already drawing the fold rows and owns the popup that opens them. So the component measures and
|
|
18
|
+
* reports, and the host decides — fold harder, draw its own marker, raise the ceiling. A host that
|
|
19
|
+
* does nothing gets a grid with even rows and no notice, which is still better than one cell
|
|
20
|
+
* setting the height of a week, but it is not the finished shape.
|
|
21
|
+
*
|
|
22
|
+
* ── Why not scroll the cell instead ──────────────────────────────────────────
|
|
23
|
+
* A scrollbar does announce itself, and in a month grid it is a 120px scroll area inside a page
|
|
24
|
+
* that also scrolls. The nesting costs more than the announcement is worth at that size.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Measure every capped cell and tell the host what it lost, when that changes.
|
|
28
|
+
*
|
|
29
|
+
* Returns a signature to hand back on the next call. The comparison matters: this runs after
|
|
30
|
+
* every render, and a host that re-renders in response to the event would otherwise be told the
|
|
31
|
+
* same thing forever.
|
|
32
|
+
*
|
|
33
|
+
* @param host the element the event comes from
|
|
34
|
+
* @param root where the cells live
|
|
35
|
+
* @param previous the signature this function returned last time
|
|
36
|
+
*/
|
|
37
|
+
export function reportDayOverflow(host, root, previous) {
|
|
38
|
+
if (!root) {
|
|
39
|
+
return previous;
|
|
40
|
+
}
|
|
41
|
+
const overflows = [];
|
|
42
|
+
root.querySelectorAll('li[date]').forEach(cell => {
|
|
43
|
+
/* The week view's date band is an li[date] with no row box; nothing to measure there. */
|
|
44
|
+
const box = cell.querySelector('div');
|
|
45
|
+
if (!box) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
/*
|
|
49
|
+
* Measured against the cell, not against the row container.
|
|
50
|
+
*
|
|
51
|
+
* The ceiling is on the cell, and the container inside has no height of its own - it grows to
|
|
52
|
+
* fit its rows. Comparing rows to their own container therefore always says everything fits,
|
|
53
|
+
* however hard the cell is clipping. That was the first version, and it reported nothing on a
|
|
54
|
+
* cell that was visibly cutting half its rows.
|
|
55
|
+
*
|
|
56
|
+
* One pixel of slack, because sub-pixel layout puts the last row's bottom a hair past the
|
|
57
|
+
* cell's on cells that fit exactly, and every one of those would report a phantom row.
|
|
58
|
+
*/
|
|
59
|
+
const floor = cell.getBoundingClientRect().bottom + 1;
|
|
60
|
+
const hidden = Array.from(box.children).filter(row => row.getBoundingClientRect().bottom > floor).length;
|
|
61
|
+
if (hidden > 0) {
|
|
62
|
+
overflows.push({ date: cell.date, hidden });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const signature = overflows.map(one => `${one.date?.getTime()}:${one.hidden}`).join(',');
|
|
66
|
+
if (signature !== previous) {
|
|
67
|
+
host.dispatchEvent(new CustomEvent('day-overflow', { detail: overflows, bubbles: true, composed: true }));
|
|
68
|
+
}
|
|
69
|
+
return signature;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=day-overflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"day-overflow.js","sourceRoot":"","sources":["../../src/day-overflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAQH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAiB,EAAE,IAAuB,EAAE,QAAgB;IAC5F,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,MAAM,SAAS,GAAkB,EAAE,CAAA;IAEnC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QAC/C,yFAAyF;QACzF,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;QACrC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAM;QACR,CAAC;QAED;;;;;;;;;;WAUG;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,qBAAqB,EAAE,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAA;QAExG,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAG,IAAqC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QAC/E,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAExF,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAgB,cAAc,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CACrG,CAAA;IACH,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC","sourcesContent":["/**\n * What did not fit in a day's cell.\n *\n * ── Why the grid has to cap a cell at all ─────────────────────────────────────\n * A grid row is as tall as its tallest cell, so one busy day sets the height of every day beside\n * it. Measured on the plant calendar's week view: 767 events on the 8th made all seven cells\n * 670px, the two empty ones included.\n *\n * Folding the rows helps and does not finish the job — fold by production line and a factory with\n * twenty lines still has twenty folded rows in that cell. So the cell takes a ceiling.\n *\n * ── A ceiling that says nothing is the defect it replaces ─────────────────────\n * Clipping hides rows exactly the way the old \"+763 more\" hid them: you cannot tell what went\n * missing. The difference has to be that somebody says so.\n *\n * This component is the wrong one to say it. It does not know the host's words, and the host is\n * already drawing the fold rows and owns the popup that opens them. So the component measures and\n * reports, and the host decides — fold harder, draw its own marker, raise the ceiling. A host that\n * does nothing gets a grid with even rows and no notice, which is still better than one cell\n * setting the height of a week, but it is not the finished shape.\n *\n * ── Why not scroll the cell instead ──────────────────────────────────────────\n * A scrollbar does announce itself, and in a month grid it is a 120px scroll area inside a page\n * that also scrolls. The nesting costs more than the announcement is worth at that size.\n */\n\nexport type DayOverflow = {\n date: Date\n /** How many of the day's rows are cut off or entirely below the fold. */\n hidden: number\n}\n\n/**\n * Measure every capped cell and tell the host what it lost, when that changes.\n *\n * Returns a signature to hand back on the next call. The comparison matters: this runs after\n * every render, and a host that re-renders in response to the event would otherwise be told the\n * same thing forever.\n *\n * @param host the element the event comes from\n * @param root where the cells live\n * @param previous the signature this function returned last time\n */\nexport function reportDayOverflow(host: HTMLElement, root: ShadowRoot | null, previous: string): string {\n if (!root) {\n return previous\n }\n\n const overflows: DayOverflow[] = []\n\n root.querySelectorAll('li[date]').forEach(cell => {\n /* The week view's date band is an li[date] with no row box; nothing to measure there. */\n const box = cell.querySelector('div')\n if (!box) {\n return\n }\n\n /*\n * Measured against the cell, not against the row container.\n *\n * The ceiling is on the cell, and the container inside has no height of its own - it grows to\n * fit its rows. Comparing rows to their own container therefore always says everything fits,\n * however hard the cell is clipping. That was the first version, and it reported nothing on a\n * cell that was visibly cutting half its rows.\n *\n * One pixel of slack, because sub-pixel layout puts the last row's bottom a hair past the\n * cell's on cells that fit exactly, and every one of those would report a phantom row.\n */\n const floor = cell.getBoundingClientRect().bottom + 1\n const hidden = Array.from(box.children).filter(row => row.getBoundingClientRect().bottom > floor).length\n\n if (hidden > 0) {\n overflows.push({ date: (cell as HTMLElement & { date: Date }).date, hidden })\n }\n })\n\n const signature = overflows.map(one => `${one.date?.getTime()}:${one.hidden}`).join(',')\n\n if (signature !== previous) {\n host.dispatchEvent(\n new CustomEvent<DayOverflow[]>('day-overflow', { detail: overflows, bubbles: true, composed: true })\n )\n }\n\n return signature\n}\n"]}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA","sourcesContent":["export * from './types.js'\nexport * from './ox-event-view.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA","sourcesContent":["export * from './types.js'\nexport * from './day-overflow.js'\nexport * from './ox-event-view.js'\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js';
|
|
2
|
+
import '@material/web/iconbutton/icon-button.js';
|
|
2
3
|
import './calendar-monthly.js';
|
|
3
4
|
import './calendar-weekly.js';
|
|
4
5
|
import { LitElement } from 'lit';
|
|
@@ -7,6 +8,8 @@ export declare class OxEventView extends LitElement {
|
|
|
7
8
|
static styles: import("lit").CSSResult[];
|
|
8
9
|
mode: 'monthly' | 'weekly';
|
|
9
10
|
eventProvider?: EventProvider;
|
|
11
|
+
monthlyLabel: string;
|
|
12
|
+
weeklyLabel: string;
|
|
10
13
|
/**
|
|
11
14
|
* Switch view, and tell the host.
|
|
12
15
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import '@material/web/icon/icon.js';
|
|
3
|
+
import '@material/web/iconbutton/icon-button.js';
|
|
3
4
|
import './calendar-monthly.js';
|
|
4
5
|
import './calendar-weekly.js';
|
|
5
6
|
import { css, html, LitElement } from 'lit';
|
|
@@ -9,6 +10,18 @@ let OxEventView = class OxEventView extends LitElement {
|
|
|
9
10
|
constructor() {
|
|
10
11
|
super(...arguments);
|
|
11
12
|
this.mode = 'monthly';
|
|
13
|
+
/*
|
|
14
|
+
* What the view switch is called.
|
|
15
|
+
*
|
|
16
|
+
* It was a bare md-icon: 24px, no padding, no hit area, and no name, sitting flush against a
|
|
17
|
+
* 40px button. A reader met four vertical bars and a screen reader met nothing at all.
|
|
18
|
+
*
|
|
19
|
+
* The dates around it come from Intl, which has no phrase for "week view", so these are
|
|
20
|
+
* properties with an English default rather than invented translations. A host that has a
|
|
21
|
+
* dictionary passes its own words; one that does not still gets a named button.
|
|
22
|
+
*/
|
|
23
|
+
this.monthlyLabel = 'month view';
|
|
24
|
+
this.weeklyLabel = 'week view';
|
|
12
25
|
}
|
|
13
26
|
static { this.styles = [
|
|
14
27
|
/*
|
|
@@ -57,12 +70,24 @@ let OxEventView = class OxEventView extends LitElement {
|
|
|
57
70
|
return this.mode == 'weekly'
|
|
58
71
|
? html `
|
|
59
72
|
<calendar-weekly .eventProvider=${this.eventProvider}>
|
|
60
|
-
<md-icon
|
|
73
|
+
<md-icon-button
|
|
74
|
+
aria-label=${this.monthlyLabel}
|
|
75
|
+
title=${this.monthlyLabel}
|
|
76
|
+
@click=${() => this.switchTo('monthly')}
|
|
77
|
+
>
|
|
78
|
+
<md-icon>calendar_month</md-icon>
|
|
79
|
+
</md-icon-button>
|
|
61
80
|
</calendar-weekly>
|
|
62
81
|
`
|
|
63
82
|
: html `
|
|
64
83
|
<calendar-monthly .eventProvider=${this.eventProvider}>
|
|
65
|
-
<md-icon
|
|
84
|
+
<md-icon-button
|
|
85
|
+
aria-label=${this.weeklyLabel}
|
|
86
|
+
title=${this.weeklyLabel}
|
|
87
|
+
@click=${() => this.switchTo('weekly')}
|
|
88
|
+
>
|
|
89
|
+
<md-icon>calendar_view_week</md-icon>
|
|
90
|
+
</md-icon-button>
|
|
66
91
|
</calendar-monthly>
|
|
67
92
|
`;
|
|
68
93
|
}
|
|
@@ -73,6 +98,12 @@ __decorate([
|
|
|
73
98
|
__decorate([
|
|
74
99
|
property({ type: Object })
|
|
75
100
|
], OxEventView.prototype, "eventProvider", void 0);
|
|
101
|
+
__decorate([
|
|
102
|
+
property({ type: String, attribute: 'monthly-label' })
|
|
103
|
+
], OxEventView.prototype, "monthlyLabel", void 0);
|
|
104
|
+
__decorate([
|
|
105
|
+
property({ type: String, attribute: 'weekly-label' })
|
|
106
|
+
], OxEventView.prototype, "weeklyLabel", void 0);
|
|
76
107
|
OxEventView = __decorate([
|
|
77
108
|
customElement('ox-event-view')
|
|
78
109
|
], OxEventView);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ox-event-view.js","sourceRoot":"","sources":["../../src/ox-event-view.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"ox-event-view.js","sourceRoot":"","sources":["../../src/ox-event-view.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,yCAAyC,CAAA;AAEhD,OAAO,uBAAuB,CAAA;AAC9B,OAAO,sBAAsB,CAAA;AAE7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAK1C,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,UAAU;IAApC;;QA4BuB,SAAI,GAAyB,SAAS,CAAA;QAGlE;;;;;;;;;WASG;QACqD,iBAAY,GAAW,YAAY,CAAA;QACpC,gBAAW,GAAW,WAAW,CAAA;IA6C1F,CAAC;aAtFQ,WAAM,GAAG;QACd;;;;;WAKG;QACH,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;KAgBF;KACF,AAzBY,CAyBZ;IAkBD;;;;;;;;;;;;OAYG;IACK,QAAQ,CAAC,IAA0B;QACzC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACzG,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,IAAI,IAAI,QAAQ;YAC1B,CAAC,CAAC,IAAI,CAAA;4CACgC,IAAI,CAAC,aAAa;;2BAEnC,IAAI,CAAC,YAAY;sBACtB,IAAI,CAAC,YAAY;uBAChB,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;;;;;SAK5C;YACH,CAAC,CAAC,IAAI,CAAA;6CACiC,IAAI,CAAC,aAAa;;2BAEpC,IAAI,CAAC,WAAW;sBACrB,IAAI,CAAC,WAAW;uBACf,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;;;;;SAK3C,CAAA;IACP,CAAC;;AA1D2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;yCAAuC;AACtC;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;kDAA8B;AAYD;IAAvD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC;iDAAoC;AACpC;IAAtD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;gDAAkC;AA1C7E,WAAW;IADvB,aAAa,CAAC,eAAe,CAAC;GAClB,WAAW,CAuFvB","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/iconbutton/icon-button.js'\n\nimport './calendar-monthly.js'\nimport './calendar-weekly.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { EventProvider } from './types.js'\n\n@customElement('ox-event-view')\nexport class OxEventView extends LitElement {\n static styles = [\n /*\n * The host page is the one that decides this element scrolls — operato-plant sets\n * overflow: auto on it. A rule written here still reaches that scrollbar: measured in\n * Chrome, :host::-webkit-scrollbar from inside the shadow root gives the same 5px as the\n * identical rule written on the outer page. [확인함] 2026-09-07\n */\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n background-color: var(--md-sys-color-background);\n color: var(--md-sys-color-on-background);\n }\n\n calendar-weekly,\n calendar-monthly {\n flex: 1;\n\n padding: 0;\n margin: 0;\n }\n `\n ]\n\n @property({ type: String }) mode: 'monthly' | 'weekly' = 'monthly'\n @property({ type: Object }) eventProvider?: EventProvider\n\n /*\n * What the view switch is called.\n *\n * It was a bare md-icon: 24px, no padding, no hit area, and no name, sitting flush against a\n * 40px button. A reader met four vertical bars and a screen reader met nothing at all.\n *\n * The dates around it come from Intl, which has no phrase for \"week view\", so these are\n * properties with an English default rather than invented translations. A host that has a\n * dictionary passes its own words; one that does not still gets a named button.\n */\n @property({ type: String, attribute: 'monthly-label' }) monthlyLabel: string = 'month view'\n @property({ type: String, attribute: 'weekly-label' }) weeklyLabel: string = 'week view'\n\n /**\n * Switch view, and tell the host.\n *\n * There are two switches for one piece of state: the icon in here, and whatever the host page\n * puts on its own toolbar. Without the event the host never learns that the icon was used, and\n * anything it derives from the mode goes stale.\n *\n * What that cost on screen: the factory calendar slices each day to a per-view maximum (4 in\n * monthly, 32 in weekly). Switching by this icon left the host on monthly, so a weekly cell\n * over 1000px tall showed four rows and \"+56 more\". [확인함] 2026-09-07\n *\n * The mode still changes here, so a host that ignores the event keeps working on its own.\n */\n private switchTo(mode: 'monthly' | 'weekly') {\n this.mode = mode\n this.dispatchEvent(new CustomEvent('mode-change', { detail: { mode }, bubbles: true, composed: true }))\n }\n\n render() {\n return this.mode == 'weekly'\n ? html`\n <calendar-weekly .eventProvider=${this.eventProvider}>\n <md-icon-button\n aria-label=${this.monthlyLabel}\n title=${this.monthlyLabel}\n @click=${() => this.switchTo('monthly')}\n >\n <md-icon>calendar_month</md-icon>\n </md-icon-button>\n </calendar-weekly>\n `\n : html`\n <calendar-monthly .eventProvider=${this.eventProvider}>\n <md-icon-button\n aria-label=${this.weeklyLabel}\n title=${this.weeklyLabel}\n @click=${() => this.switchTo('weekly')}\n >\n <md-icon>calendar_view_week</md-icon>\n </md-icon-button>\n </calendar-monthly>\n `\n }\n}\n"]}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -7,14 +7,79 @@ export type BizEvent = {
|
|
|
7
7
|
template?: TemplateResult;
|
|
8
8
|
};
|
|
9
9
|
export type CALENDAR = {
|
|
10
|
+
/**
|
|
11
|
+
* The number as it was formatted when the grid was built.
|
|
12
|
+
*
|
|
13
|
+
* ⚠ It goes stale. The grid is rebuilt on a month or week change, not on a language change,
|
|
14
|
+
* so after the reader switches language this still holds the old language's text. The
|
|
15
|
+
* templates format from `date` and `withMonth` instead; this stays for hosts that read it.
|
|
16
|
+
*/
|
|
10
17
|
label: string;
|
|
18
|
+
/** First cell of a run, where the month is worth naming beside the number. */
|
|
19
|
+
withMonth?: boolean;
|
|
11
20
|
date: Date;
|
|
12
21
|
thisMonth?: boolean;
|
|
13
22
|
thisWeek?: boolean;
|
|
14
23
|
today?: boolean;
|
|
15
24
|
}[];
|
|
25
|
+
/**
|
|
26
|
+
* Something true of the day itself, rather than something that happened in it.
|
|
27
|
+
*
|
|
28
|
+
* A non-working day is the clearest case. It is not one more thing on the calendar alongside the
|
|
29
|
+
* production runs and the maintenance — it is the frame you read those through, and a reader who
|
|
30
|
+
* misses it misreads everything else in the cell. Inspection days and cut-off dates are the same
|
|
31
|
+
* shape.
|
|
32
|
+
*
|
|
33
|
+
* ── Why this exists ───────────────────────────────────────────────────────────
|
|
34
|
+
* Until now a host had no way to say anything about a day. CALENDAR carries what this component
|
|
35
|
+
* works out for itself — which month a date belongs to, whether it is today — and nothing a host
|
|
36
|
+
* can add. So the plant calendar said "this day is a holiday" the only way that was open to it:
|
|
37
|
+
* as an event, drawn as a 6px dot among the dots for the day's actual work.
|
|
38
|
+
*
|
|
39
|
+
* That was this component's shape, not that screen's choice.
|
|
40
|
+
*
|
|
41
|
+
* ── The host names it and paints it ───────────────────────────────────────────
|
|
42
|
+
* `name` lands on the cell as a `day-state` attribute, so screens, tests and assistive tech can
|
|
43
|
+
* reach it. `color` is the cell's surface.
|
|
44
|
+
*
|
|
45
|
+
* Both come from the host because **this component does not know what the word means.** A
|
|
46
|
+
* calendar cannot decide what colour a holiday is; the factory can, and the next app will mark
|
|
47
|
+
* something else entirely. Pass a `var(...)` so the value still follows the theme.
|
|
48
|
+
*
|
|
49
|
+
* `color` is required rather than optional on purpose. A state with no colour would set the
|
|
50
|
+
* attribute and paint nothing, which looks exactly like a state that failed to arrive.
|
|
51
|
+
*
|
|
52
|
+
* ── One day, one surface ──────────────────────────────────────────────────────
|
|
53
|
+
* A cell has one background, so it takes one state. A host that needs to express two conditions
|
|
54
|
+
* at once should name the combination rather than send two — the component cannot stack them,
|
|
55
|
+
* and silently drawing only the first would be worse than making the host choose.
|
|
56
|
+
*/
|
|
57
|
+
export type DayState = {
|
|
58
|
+
/** The host's own word for it — `non-working`, `shutdown`, `cut-off`. Not shown to the reader. */
|
|
59
|
+
name: string;
|
|
60
|
+
/** The cell's surface. A `var(--...)` is preferred so it follows the theme. */
|
|
61
|
+
color: string;
|
|
62
|
+
};
|
|
16
63
|
export interface EventProvider {
|
|
17
64
|
fetchEventsForCalendar(calendar: CALENDAR): Promise<Map<Date, BizEvent[]>>;
|
|
65
|
+
/**
|
|
66
|
+
* Optional. What is true of the days themselves — see {@link DayState}.
|
|
67
|
+
*
|
|
68
|
+
* Asked alongside the events, with the same calendar and the same keys, so a host that already
|
|
69
|
+
* knows both can answer from one round trip.
|
|
70
|
+
*/
|
|
71
|
+
fetchDayStatesForCalendar?(calendar: CALENDAR): Promise<Map<Date, DayState>>;
|
|
18
72
|
}
|
|
19
|
-
export declare
|
|
73
|
+
export declare function documentLanguage(): string;
|
|
74
|
+
/** Sun..Sat in the reader's language, starting on Sunday as the grid does. */
|
|
75
|
+
export declare function weekdayNames(lang?: string): string[];
|
|
76
|
+
/** "2026년 9월" · "September 2026" — the year sits where the language puts it. */
|
|
77
|
+
export declare function monthTitle(year: number, month: number, lang?: string): string;
|
|
78
|
+
/** The number alone, or with its month on the first cell of a month. */
|
|
79
|
+
export declare function dayLabel(date: Date, withMonth: boolean, lang?: string): string;
|
|
80
|
+
export declare function todayLabel(lang?: string): string;
|
|
81
|
+
export declare function previousMonthLabel(lang?: string): string;
|
|
82
|
+
export declare function nextMonthLabel(lang?: string): string;
|
|
83
|
+
export declare function previousWeekLabel(lang?: string): string;
|
|
84
|
+
export declare function nextWeekLabel(lang?: string): string;
|
|
20
85
|
export declare function isToday(date: Date): boolean;
|
package/dist/src/types.js
CHANGED
|
@@ -3,7 +3,70 @@ import weekday from 'dayjs/esm/plugin/weekday/index.js';
|
|
|
3
3
|
import weekOfYear from 'dayjs/esm/plugin/weekOfYear/index.js';
|
|
4
4
|
dayjs.extend(weekday);
|
|
5
5
|
dayjs.extend(weekOfYear);
|
|
6
|
-
|
|
6
|
+
/*
|
|
7
|
+
* Dates in the reader's language.
|
|
8
|
+
*
|
|
9
|
+
* The weekday row was a literal English array and the month names came from dayjs with no
|
|
10
|
+
* locale loaded, so a Korean screen drew "Sun Mon Tue" over "September 2026" while every
|
|
11
|
+
* other word on it was Korean.
|
|
12
|
+
*
|
|
13
|
+
* Intl is in the browser already and follows the language the document declares, so this
|
|
14
|
+
* needs no dictionary of its own and no new dependency - which matters, because a leaf
|
|
15
|
+
* component taking an i18n dependency is a boundary decision, not a wording fix.
|
|
16
|
+
*
|
|
17
|
+
* Formatters are cached: constructing one costs more than formatting with it, and the
|
|
18
|
+
* weekday row builds seven on every render.
|
|
19
|
+
*/
|
|
20
|
+
export function documentLanguage() {
|
|
21
|
+
return (typeof document !== 'undefined' && document.documentElement.lang) || 'en';
|
|
22
|
+
}
|
|
23
|
+
const formatters = new Map();
|
|
24
|
+
function formatter(lang, options) {
|
|
25
|
+
const key = lang + JSON.stringify(options);
|
|
26
|
+
let found = formatters.get(key);
|
|
27
|
+
if (!found) {
|
|
28
|
+
found = new Intl.DateTimeFormat(lang, options);
|
|
29
|
+
formatters.set(key, found);
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
/** Sun..Sat in the reader's language, starting on Sunday as the grid does. */
|
|
34
|
+
export function weekdayNames(lang = documentLanguage()) {
|
|
35
|
+
/* 2026-01-04 is a Sunday. Any Sunday works; this one keeps the intent readable. */
|
|
36
|
+
const sunday = new Date(2026, 0, 4);
|
|
37
|
+
return Array.from({ length: 7 }, (_, i) => formatter(lang, { weekday: 'short' }).format(new Date(2026, 0, sunday.getDate() + i)));
|
|
38
|
+
}
|
|
39
|
+
/** "2026년 9월" · "September 2026" — the year sits where the language puts it. */
|
|
40
|
+
export function monthTitle(year, month, lang = documentLanguage()) {
|
|
41
|
+
return formatter(lang, { year: 'numeric', month: 'long' }).format(new Date(year, month, 1));
|
|
42
|
+
}
|
|
43
|
+
/** The number alone, or with its month on the first cell of a month. */
|
|
44
|
+
export function dayLabel(date, withMonth, lang = documentLanguage()) {
|
|
45
|
+
return withMonth
|
|
46
|
+
? formatter(lang, { month: 'short', day: 'numeric' }).format(date)
|
|
47
|
+
: formatter(lang, { day: 'numeric' }).format(date);
|
|
48
|
+
}
|
|
49
|
+
/*
|
|
50
|
+
* "오늘" · "지난달" · "다음 주" — RelativeTimeFormat names these without a dictionary.
|
|
51
|
+
*
|
|
52
|
+
* The step buttons carry them as their accessible name and tooltip. They used to be the
|
|
53
|
+
* characters "<" and ">", which a screen reader reads as "less than" and "greater than".
|
|
54
|
+
*/
|
|
55
|
+
export function todayLabel(lang = documentLanguage()) {
|
|
56
|
+
return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(0, 'day');
|
|
57
|
+
}
|
|
58
|
+
export function previousMonthLabel(lang = documentLanguage()) {
|
|
59
|
+
return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(-1, 'month');
|
|
60
|
+
}
|
|
61
|
+
export function nextMonthLabel(lang = documentLanguage()) {
|
|
62
|
+
return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(1, 'month');
|
|
63
|
+
}
|
|
64
|
+
export function previousWeekLabel(lang = documentLanguage()) {
|
|
65
|
+
return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(-1, 'week');
|
|
66
|
+
}
|
|
67
|
+
export function nextWeekLabel(lang = documentLanguage()) {
|
|
68
|
+
return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(1, 'week');
|
|
69
|
+
}
|
|
7
70
|
export function isToday(date) {
|
|
8
71
|
const today = new Date();
|
|
9
72
|
return (date.getDate() == today.getDate() &&
|
package/dist/src/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,oBAAoB,CAAA;AACtC,OAAO,OAAO,MAAM,mCAAmC,CAAA;AACvD,OAAO,UAAU,MAAM,sCAAsC,CAAA;AAE7D,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AACrB,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;AAsBxB,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;AAEzE,MAAM,UAAU,OAAO,CAAC,IAAU;IAChC,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAA;IACxB,OAAO,CACL,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE;QACjC,IAAI,CAAC,QAAQ,EAAE,IAAI,KAAK,CAAC,QAAQ,EAAE;QACnC,IAAI,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,WAAW,EAAE,CAC1C,CAAA;AACH,CAAC","sourcesContent":["import { TemplateResult } from 'lit'\nimport dayjs from 'dayjs/esm/index.js'\nimport weekday from 'dayjs/esm/plugin/weekday/index.js'\nimport weekOfYear from 'dayjs/esm/plugin/weekOfYear/index.js'\n\ndayjs.extend(weekday)\ndayjs.extend(weekOfYear)\n\nexport type BizEvent = {\n due?: string\n title: string\n color: string\n clickEvent?: any\n template?: TemplateResult\n}\n\nexport type CALENDAR = {\n label: string\n date: Date\n thisMonth?: boolean\n thisWeek?: boolean\n today?: boolean\n}[]\n\nexport interface EventProvider {\n fetchEventsForCalendar(calendar: CALENDAR): Promise<Map<Date, BizEvent[]>>\n}\n\nexport const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']\n\nexport function isToday(date: Date) {\n const today = new Date()\n return (\n date.getDate() == today.getDate() &&\n date.getMonth() == today.getMonth() &&\n date.getFullYear() == today.getFullYear()\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import { TemplateResult } from 'lit'\nimport dayjs from 'dayjs/esm/index.js'\nimport weekday from 'dayjs/esm/plugin/weekday/index.js'\nimport weekOfYear from 'dayjs/esm/plugin/weekOfYear/index.js'\n\ndayjs.extend(weekday)\ndayjs.extend(weekOfYear)\n\nexport type BizEvent = {\n due?: string\n title: string\n color: string\n clickEvent?: any\n template?: TemplateResult\n}\n\nexport type CALENDAR = {\n /**\n * The number as it was formatted when the grid was built.\n *\n * ⚠ It goes stale. The grid is rebuilt on a month or week change, not on a language change,\n * so after the reader switches language this still holds the old language's text. The\n * templates format from `date` and `withMonth` instead; this stays for hosts that read it.\n */\n label: string\n /** First cell of a run, where the month is worth naming beside the number. */\n withMonth?: boolean\n date: Date\n thisMonth?: boolean\n thisWeek?: boolean\n today?: boolean\n}[]\n\n/**\n * Something true of the day itself, rather than something that happened in it.\n *\n * A non-working day is the clearest case. It is not one more thing on the calendar alongside the\n * production runs and the maintenance — it is the frame you read those through, and a reader who\n * misses it misreads everything else in the cell. Inspection days and cut-off dates are the same\n * shape.\n *\n * ── Why this exists ───────────────────────────────────────────────────────────\n * Until now a host had no way to say anything about a day. CALENDAR carries what this component\n * works out for itself — which month a date belongs to, whether it is today — and nothing a host\n * can add. So the plant calendar said \"this day is a holiday\" the only way that was open to it:\n * as an event, drawn as a 6px dot among the dots for the day's actual work.\n *\n * That was this component's shape, not that screen's choice.\n *\n * ── The host names it and paints it ───────────────────────────────────────────\n * `name` lands on the cell as a `day-state` attribute, so screens, tests and assistive tech can\n * reach it. `color` is the cell's surface.\n *\n * Both come from the host because **this component does not know what the word means.** A\n * calendar cannot decide what colour a holiday is; the factory can, and the next app will mark\n * something else entirely. Pass a `var(...)` so the value still follows the theme.\n *\n * `color` is required rather than optional on purpose. A state with no colour would set the\n * attribute and paint nothing, which looks exactly like a state that failed to arrive.\n *\n * ── One day, one surface ──────────────────────────────────────────────────────\n * A cell has one background, so it takes one state. A host that needs to express two conditions\n * at once should name the combination rather than send two — the component cannot stack them,\n * and silently drawing only the first would be worse than making the host choose.\n */\nexport type DayState = {\n /** The host's own word for it — `non-working`, `shutdown`, `cut-off`. Not shown to the reader. */\n name: string\n /** The cell's surface. A `var(--...)` is preferred so it follows the theme. */\n color: string\n}\n\nexport interface EventProvider {\n fetchEventsForCalendar(calendar: CALENDAR): Promise<Map<Date, BizEvent[]>>\n /**\n * Optional. What is true of the days themselves — see {@link DayState}.\n *\n * Asked alongside the events, with the same calendar and the same keys, so a host that already\n * knows both can answer from one round trip.\n */\n fetchDayStatesForCalendar?(calendar: CALENDAR): Promise<Map<Date, DayState>>\n}\n\n/*\n * Dates in the reader's language.\n *\n * The weekday row was a literal English array and the month names came from dayjs with no\n * locale loaded, so a Korean screen drew \"Sun Mon Tue\" over \"September 2026\" while every\n * other word on it was Korean.\n *\n * Intl is in the browser already and follows the language the document declares, so this\n * needs no dictionary of its own and no new dependency - which matters, because a leaf\n * component taking an i18n dependency is a boundary decision, not a wording fix.\n *\n * Formatters are cached: constructing one costs more than formatting with it, and the\n * weekday row builds seven on every render.\n */\nexport function documentLanguage(): string {\n return (typeof document !== 'undefined' && document.documentElement.lang) || 'en'\n}\n\nconst formatters = new Map<string, Intl.DateTimeFormat>()\n\nfunction formatter(lang: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = lang + JSON.stringify(options)\n let found = formatters.get(key)\n if (!found) {\n found = new Intl.DateTimeFormat(lang, options)\n formatters.set(key, found)\n }\n return found\n}\n\n/** Sun..Sat in the reader's language, starting on Sunday as the grid does. */\nexport function weekdayNames(lang: string = documentLanguage()): string[] {\n /* 2026-01-04 is a Sunday. Any Sunday works; this one keeps the intent readable. */\n const sunday = new Date(2026, 0, 4)\n return Array.from({ length: 7 }, (_, i) =>\n formatter(lang, { weekday: 'short' }).format(new Date(2026, 0, sunday.getDate() + i))\n )\n}\n\n/** \"2026년 9월\" · \"September 2026\" — the year sits where the language puts it. */\nexport function monthTitle(year: number, month: number, lang: string = documentLanguage()): string {\n return formatter(lang, { year: 'numeric', month: 'long' }).format(new Date(year, month, 1))\n}\n\n/** The number alone, or with its month on the first cell of a month. */\nexport function dayLabel(date: Date, withMonth: boolean, lang: string = documentLanguage()): string {\n return withMonth\n ? formatter(lang, { month: 'short', day: 'numeric' }).format(date)\n : formatter(lang, { day: 'numeric' }).format(date)\n}\n\n/*\n * \"오늘\" · \"지난달\" · \"다음 주\" — RelativeTimeFormat names these without a dictionary.\n *\n * The step buttons carry them as their accessible name and tooltip. They used to be the\n * characters \"<\" and \">\", which a screen reader reads as \"less than\" and \"greater than\".\n */\nexport function todayLabel(lang: string = documentLanguage()): string {\n return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(0, 'day')\n}\n\nexport function previousMonthLabel(lang: string = documentLanguage()): string {\n return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(-1, 'month')\n}\n\nexport function nextMonthLabel(lang: string = documentLanguage()): string {\n return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(1, 'month')\n}\n\nexport function previousWeekLabel(lang: string = documentLanguage()): string {\n return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(-1, 'week')\n}\n\nexport function nextWeekLabel(lang: string = documentLanguage()): string {\n return new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }).format(1, 'week')\n}\n\nexport function isToday(date: Date) {\n const today = new Date()\n return (\n date.getDate() == today.getDate() &&\n date.getMonth() == today.getMonth() &&\n date.getFullYear() == today.getFullYear()\n )\n}\n"]}
|
|
@@ -83,4 +83,106 @@ describe('the built-in view switch tells the host', () => {
|
|
|
83
83
|
expect((await heard).detail.mode).to.equal('monthly');
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
|
+
/**
|
|
87
|
+
* The calendar drew "Sun Mon Tue" over "September 2026" on a screen whose every other word was
|
|
88
|
+
* Korean: the weekday row was a literal English array and dayjs had no locale loaded.
|
|
89
|
+
*
|
|
90
|
+
* These read the rendered text rather than the helpers, because a helper that returns Korean
|
|
91
|
+
* proves nothing if the template still holds the old array.
|
|
92
|
+
*/
|
|
93
|
+
describe('the calendar speaks the language the document declares', () => {
|
|
94
|
+
const original = document.documentElement.lang;
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
document.documentElement.lang = original;
|
|
97
|
+
});
|
|
98
|
+
async function headerOf(lang, mode) {
|
|
99
|
+
document.documentElement.lang = lang;
|
|
100
|
+
const el = (await fixture(html `<ox-event-view mode=${mode}></ox-event-view>`));
|
|
101
|
+
const inner = await settle(el);
|
|
102
|
+
return inner.renderRoot;
|
|
103
|
+
}
|
|
104
|
+
for (const mode of ['monthly', 'weekly']) {
|
|
105
|
+
it(`${mode}: the weekday row is in Korean when the document says ko`, async () => {
|
|
106
|
+
const root = await headerOf('ko', mode);
|
|
107
|
+
const names = Array.from(root.querySelectorAll('li[day-of-week], [date-label]')).map(el => el.textContent.trim());
|
|
108
|
+
const row = names.join(' ');
|
|
109
|
+
expect(row, `weekday row came out "${row}"`).to.not.match(/Sun|Mon|Tue|Wed|Thu|Fri|Sat/);
|
|
110
|
+
expect(row, `weekday row came out "${row}"`).to.match(/[일월화수목금토]/);
|
|
111
|
+
});
|
|
112
|
+
it(`${mode}: the step buttons carry a name, not a "<" glyph`, async () => {
|
|
113
|
+
const root = await headerOf('ko', mode);
|
|
114
|
+
const buttons = Array.from(root.querySelectorAll('md-icon-button'));
|
|
115
|
+
expect(buttons.length, 'previous and next should both be icon buttons').to.equal(2);
|
|
116
|
+
for (const button of buttons) {
|
|
117
|
+
const name = button.getAttribute('aria-label') ?? '';
|
|
118
|
+
expect(name, 'an icon button with no accessible name is read as its glyph').to.not.equal('');
|
|
119
|
+
expect(name).to.not.match(/^[<>]$/);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
it('monthly: the month title follows the language', async () => {
|
|
124
|
+
const root = await headerOf('ko', 'monthly');
|
|
125
|
+
const title = root.querySelector('[this-month]').textContent.trim();
|
|
126
|
+
expect(title, `title came out "${title}"`).to.not.match(/January|February|March|September|December/);
|
|
127
|
+
expect(title, `title came out "${title}"`).to.include('월');
|
|
128
|
+
});
|
|
129
|
+
it('monthly: today is named in the language, not the word "Today"', async () => {
|
|
130
|
+
const root = await headerOf('ko', 'monthly');
|
|
131
|
+
const labels = Array.from(root.querySelectorAll('md-text-button')).map(el => el.textContent.trim());
|
|
132
|
+
expect(labels.join(' '), `buttons came out "${labels.join(' ')}"`).to.not.include('Today');
|
|
133
|
+
expect(labels.some(l => l.includes('오늘')), `buttons came out "${labels.join(' ')}"`).to.be.true;
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
/**
|
|
137
|
+
* The day numbers were formatted once, when the grid was built, and the grid is rebuilt on a
|
|
138
|
+
* month change - not on a language change. So switching language left "Sep 6 (일)": the
|
|
139
|
+
* weekday came from the template and followed, the date came from the stored string and did
|
|
140
|
+
* not. Found on the running plant, with the weekday already in Korean beside it.
|
|
141
|
+
*/
|
|
142
|
+
describe('the dates follow a language change, not just the weekday', () => {
|
|
143
|
+
const original = document.documentElement.lang;
|
|
144
|
+
afterEach(() => {
|
|
145
|
+
document.documentElement.lang = original;
|
|
146
|
+
});
|
|
147
|
+
for (const mode of ['monthly', 'weekly']) {
|
|
148
|
+
it(`${mode}: switching to ko rewrites the day labels, not only the weekday row`, async () => {
|
|
149
|
+
document.documentElement.lang = 'en';
|
|
150
|
+
const el = (await fixture(html `<ox-event-view mode=${mode}></ox-event-view>`));
|
|
151
|
+
const inner = await settle(el);
|
|
152
|
+
document.documentElement.lang = 'ko';
|
|
153
|
+
inner.requestUpdate();
|
|
154
|
+
await inner.updateComplete;
|
|
155
|
+
const labels = Array.from(inner.renderRoot.querySelectorAll('[date-label]'))
|
|
156
|
+
.map(el => el.textContent.trim())
|
|
157
|
+
.join(' ');
|
|
158
|
+
expect(labels, `day labels came out "${labels}"`).to.not.match(/Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/);
|
|
159
|
+
expect(labels, `day labels came out "${labels}"`).to.include('월');
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
/**
|
|
164
|
+
* The view switch was a bare md-icon: 24px, no padding, no hit area and no accessible name,
|
|
165
|
+
* sitting flush against a 40px button. On screen it read as four vertical bars.
|
|
166
|
+
*/
|
|
167
|
+
describe('the view switch is a named button', () => {
|
|
168
|
+
for (const [mode, other] of [
|
|
169
|
+
['monthly', 'week'],
|
|
170
|
+
['weekly', 'month']
|
|
171
|
+
]) {
|
|
172
|
+
it(`${mode}: the switch is an icon button that says where it goes`, async () => {
|
|
173
|
+
const el = (await fixture(html `<ox-event-view mode=${mode}></ox-event-view>`));
|
|
174
|
+
await settle(el);
|
|
175
|
+
const button = el.renderRoot.querySelector('md-icon-button');
|
|
176
|
+
expect(button, 'the switch should be a button, not a bare icon').to.exist;
|
|
177
|
+
const name = button.getAttribute('aria-label') ?? '';
|
|
178
|
+
expect(name, 'an icon with no name is read as its glyph').to.include(other);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
it('a host can pass its own words', async () => {
|
|
182
|
+
const el = (await fixture(html `<ox-event-view mode="monthly" weekly-label="주간 보기"></ox-event-view>`));
|
|
183
|
+
await settle(el);
|
|
184
|
+
const button = el.renderRoot.querySelector('md-icon-button');
|
|
185
|
+
expect(button.getAttribute('aria-label')).to.equal('주간 보기');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
86
188
|
//# sourceMappingURL=calendar-layout.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar-layout.test.js","sourceRoot":"","sources":["../../test/calendar-layout.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, fixture, expect, oneEvent } from '@open-wc/testing'\n\nimport '../src/ox-event-view.js'\nimport { OxEventView } from '../src/ox-event-view.js'\nimport { EventProvider, CALENDAR, BizEvent } from '../src/types.js'\n\n/**\n * Two things this calendar got wrong on a real screen, both of them structural.\n *\n * The package had no tests at all, which is why a layout that squeezed six of seven days to\n * 58px and a view switch that told nobody both shipped.\n */\n\n/** One long unbreakable title on one day, the way a work order id looks. */\nconst LONG = 'WO-CT-MAIN-003 · CT-FRAME-SET-ASSEMBLY-STATION'\n\nfunction providerWithLongTitleOn(index: number): EventProvider {\n return {\n fetchEventsForCalendar: async (calendar: CALENDAR) => {\n const days = (calendar ?? []).filter(one => one?.date instanceof Date)\n const map = new Map<Date, BizEvent[]>()\n\n days.forEach((day, i) => {\n map.set(day.date, [\n {\n title: i === index ? LONG : 'short',\n color: 'currentColor',\n /*\n * nowrap is what a host page asks for when it wants one line per row and an\n * ellipsis. It also makes the chip's min-content width the whole title, which is\n * the pressure this test is about.\n */\n template: html`<div style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis\">\n ${i === index ? LONG : 'short'}\n </div>`\n }\n ])\n })\n\n return map\n }\n }\n}\n\nasync function settle(el: OxEventView) {\n await el.updateComplete\n const inner = el.renderRoot.querySelector('calendar-monthly, calendar-weekly') as any\n await inner?.updateComplete\n /* the provider is awaited inside updated(), so give it a turn and let layout settle */\n await new Promise(resolve => setTimeout(resolve, 0))\n await inner?.updateComplete\n return inner\n}\n\ndescribe('the seven day columns stay the same width', () => {\n for (const mode of ['monthly', 'weekly'] as const) {\n it(`${mode}: one long title does not take the room of the other six days`, async () => {\n const el = (await fixture(\n html`<div style=\"width: 700px\">\n <ox-event-view mode=${mode} .eventProvider=${providerWithLongTitleOn(2)}></ox-event-view>\n </div>`\n )) as HTMLElement\n\n const view = el.querySelector('ox-event-view') as OxEventView\n const inner = await settle(view)\n\n const cells = [...inner.renderRoot.querySelectorAll('li[date]')] as HTMLElement[]\n expect(cells.length, 'no date cells were drawn, so this test measures nothing').to.be.greaterThan(6)\n\n const widths = cells.slice(0, 7).map(one => Math.round(one.getBoundingClientRect().width))\n const widest = Math.max(...widths)\n const narrowest = Math.min(...widths)\n\n /*\n * Measured before min-width: 0 was added — [58, 58, 353, 58, 58, 58, 58] at this width.\n * A pixel of rounding is fine; a column taking six times its share is not.\n */\n expect(widest - narrowest, `columns came out ${widths.join(', ')}`).to.be.lessThan(2)\n })\n }\n})\n\ndescribe('the built-in view switch tells the host', () => {\n it('emits mode-change, because the host keeps its own state next to this one', async () => {\n const el = (await fixture(html`<ox-event-view mode=\"monthly\"></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n const toggle = inner.querySelector('md-icon') as HTMLElement\n expect(toggle, 'the switch is slotted content — without it nothing can be clicked').to.exist\n\n const heard = oneEvent(el, 'mode-change')\n toggle.click()\n const event = await heard\n\n expect(event.detail.mode).to.equal('weekly')\n expect(el.mode, 'the element still switches on its own, for hosts that ignore the event').to.equal('weekly')\n })\n\n it('reports monthly on the way back', async () => {\n const el = (await fixture(html`<ox-event-view mode=\"weekly\"></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n const heard = oneEvent(el, 'mode-change')\n ;(inner.querySelector('md-icon') as HTMLElement).click()\n\n expect((await heard).detail.mode).to.equal('monthly')\n })\n})\n"]}
|
|
1
|
+
{"version":3,"file":"calendar-layout.test.js","sourceRoot":"","sources":["../../test/calendar-layout.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAElE,OAAO,yBAAyB,CAAA;AAIhC;;;;;GAKG;AAEH,4EAA4E;AAC5E,MAAM,IAAI,GAAG,gDAAgD,CAAA;AAE7D,SAAS,uBAAuB,CAAC,KAAa;IAC5C,OAAO;QACL,sBAAsB,EAAE,KAAK,EAAE,QAAkB,EAAE,EAAE;YACnD,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,YAAY,IAAI,CAAC,CAAA;YACtE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoB,CAAA;YAEvC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;gBACtB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE;oBAChB;wBACE,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;wBACnC,KAAK,EAAE,cAAc;wBACrB;;;;2BAIG;wBACH,QAAQ,EAAE,IAAI,CAAA;gBACV,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;mBACzB;qBACR;iBACF,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;YAEF,OAAO,GAAG,CAAA;QACZ,CAAC;KACF,CAAA;AACH,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,EAAe;IACnC,MAAM,EAAE,CAAC,cAAc,CAAA;IACvB,MAAM,KAAK,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,mCAAmC,CAAQ,CAAA;IACrF,MAAM,KAAK,EAAE,cAAc,CAAA;IAC3B,uFAAuF;IACvF,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;IACpD,MAAM,KAAK,EAAE,cAAc,CAAA;IAC3B,OAAO,KAAK,CAAA;AACd,CAAC;AAED,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACzD,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;QAClD,EAAE,CAAC,GAAG,IAAI,+DAA+D,EAAE,KAAK,IAAI,EAAE;YACpF,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CACvB,IAAI,CAAA;gCACoB,IAAI,mBAAmB,uBAAuB,CAAC,CAAC,CAAC;eAClE,CACR,CAAgB,CAAA;YAEjB,MAAM,IAAI,GAAG,EAAE,CAAC,aAAa,CAAC,eAAe,CAAgB,CAAA;YAC7D,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAA;YAEhC,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAkB,CAAA;YACjF,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,yDAAyD,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;YAEpG,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;YAC1F,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAA;YAClC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAA;YAErC;;;eAGG;YACH,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,oBAAoB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QACvF,CAAC,CAAC,CAAA;IACJ,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACvD,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,CAAA,gDAAgD,CAAC,CAAgB,CAAA;QAC/F,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,EAAE,CAAC,CAAA;QAE9B,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,SAAS,CAAgB,CAAA;QAC5D,MAAM,CAAC,MAAM,EAAE,mEAAmE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAA;QAE5F,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC,CAAA;QACzC,MAAM,CAAC,KAAK,EAAE,CAAA;QACd,MAAM,KAAK,GAAG,MAAM,KAAK,CAAA;QAEzB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC5C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,wEAAwE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAC9G,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,CAAA,+CAA+C,CAAC,CAAgB,CAAA;QAC9F,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,EAAE,CAAC,CAAA;QAE9B,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC,CACxC;QAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAiB,CAAC,KAAK,EAAE,CAAA;QAExD,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,QAAQ,CAAC,wDAAwD,EAAE,GAAG,EAAE;IACtE,MAAM,QAAQ,GAAG,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAA;IAE9C,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,eAAe,CAAC,IAAI,GAAG,QAAQ,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,IAA0B;QAC9D,QAAQ,CAAC,eAAe,CAAC,IAAI,GAAG,IAAI,CAAA;QACpC,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,CAAA,uBAAuB,IAAI,mBAAmB,CAAC,CAAgB,CAAA;QAC7F,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,EAAE,CAAC,CAAA;QAC9B,OAAO,KAAK,CAAC,UAAwB,CAAA;IACvC,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;QAClD,EAAE,CAAC,GAAG,IAAI,0DAA0D,EAAE,KAAK,IAAI,EAAE;YAC/E,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YACvC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CACxF,EAAE,CAAC,WAAY,CAAC,IAAI,EAAE,CACvB,CAAA;YACD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAE3B,MAAM,CAAC,GAAG,EAAE,yBAAyB,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAA;YACxF,MAAM,CAAC,GAAG,EAAE,yBAAyB,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QACpE,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,GAAG,IAAI,kDAAkD,EAAE,KAAK,IAAI,EAAE;YACvE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YACvC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAA;YAEnE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,+CAA+C,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACnF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;gBACpD,MAAM,CAAC,IAAI,EAAE,6DAA6D,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;gBAC5F,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;YACrC,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,CAAE,CAAC,WAAY,CAAC,IAAI,EAAE,CAAA;QAErE,MAAM,CAAC,KAAK,EAAE,mBAAmB,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAA;QACpG,MAAM,CAAC,KAAK,EAAE,mBAAmB,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,WAAY,CAAC,IAAI,EAAE,CAAC,CAAA;QAEpG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,qBAAqB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAC1F,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,qBAAqB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAA;IACjG,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF;;;;;GAKG;AACH,QAAQ,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACxE,MAAM,QAAQ,GAAG,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAA;IAE9C,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,eAAe,CAAC,IAAI,GAAG,QAAQ,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;QAClD,EAAE,CAAC,GAAG,IAAI,qEAAqE,EAAE,KAAK,IAAI,EAAE;YAC1F,QAAQ,CAAC,eAAe,CAAC,IAAI,GAAG,IAAI,CAAA;YACpC,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,CAAA,uBAAuB,IAAI,mBAAmB,CAAC,CAAgB,CAAA;YAC7F,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,EAAE,CAAC,CAAA;YAE9B,QAAQ,CAAC,eAAe,CAAC,IAAI,GAAG,IAAI,CAAA;YACpC,KAAK,CAAC,aAAa,EAAE,CAAA;YACrB,MAAM,KAAK,CAAC,cAAc,CAAA;YAE1B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAE,KAAK,CAAC,UAAyB,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;iBACzF,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,WAAY,CAAC,IAAI,EAAE,CAAC;iBACjC,IAAI,CAAC,GAAG,CAAC,CAAA;YAEZ,MAAM,CAAC,MAAM,EAAE,wBAAwB,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAC5D,iDAAiD,CAClD,CAAA;YACD,MAAM,CAAC,MAAM,EAAE,wBAAwB,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACnE,CAAC,CAAC,CAAA;IACJ,CAAC;AACH,CAAC,CAAC,CAAA;AAEF;;;GAGG;AACH,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IACjD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI;QAC1B,CAAC,SAAS,EAAE,MAAM,CAAC;QACnB,CAAC,QAAQ,EAAE,OAAO,CAAC;KACX,EAAE,CAAC;QACX,EAAE,CAAC,GAAG,IAAI,wDAAwD,EAAE,KAAK,IAAI,EAAE;YAC7E,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,CAAA,uBAAuB,IAAI,mBAAmB,CAAC,CAAgB,CAAA;YAC7F,MAAM,MAAM,CAAC,EAAE,CAAC,CAAA;YAEhB,MAAM,MAAM,GAAI,EAAE,CAAC,UAAyB,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAA;YAC5E,MAAM,CAAC,MAAM,EAAE,gDAAgD,CAAC,CAAC,EAAE,CAAC,KAAK,CAAA;YAEzE,MAAM,IAAI,GAAG,MAAO,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;YACrD,MAAM,CAAC,IAAI,EAAE,2CAA2C,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC7E,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CACvB,IAAI,CAAA,qEAAqE,CAC1E,CAAgB,CAAA;QACjB,MAAM,MAAM,CAAC,EAAE,CAAC,CAAA;QAEhB,MAAM,MAAM,GAAI,EAAE,CAAC,UAAyB,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAA;QAC5E,MAAM,CAAC,MAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { html, fixture, expect, oneEvent } from '@open-wc/testing'\n\nimport '../src/ox-event-view.js'\nimport { OxEventView } from '../src/ox-event-view.js'\nimport { EventProvider, CALENDAR, BizEvent } from '../src/types.js'\n\n/**\n * Two things this calendar got wrong on a real screen, both of them structural.\n *\n * The package had no tests at all, which is why a layout that squeezed six of seven days to\n * 58px and a view switch that told nobody both shipped.\n */\n\n/** One long unbreakable title on one day, the way a work order id looks. */\nconst LONG = 'WO-CT-MAIN-003 · CT-FRAME-SET-ASSEMBLY-STATION'\n\nfunction providerWithLongTitleOn(index: number): EventProvider {\n return {\n fetchEventsForCalendar: async (calendar: CALENDAR) => {\n const days = (calendar ?? []).filter(one => one?.date instanceof Date)\n const map = new Map<Date, BizEvent[]>()\n\n days.forEach((day, i) => {\n map.set(day.date, [\n {\n title: i === index ? LONG : 'short',\n color: 'currentColor',\n /*\n * nowrap is what a host page asks for when it wants one line per row and an\n * ellipsis. It also makes the chip's min-content width the whole title, which is\n * the pressure this test is about.\n */\n template: html`<div style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis\">\n ${i === index ? LONG : 'short'}\n </div>`\n }\n ])\n })\n\n return map\n }\n }\n}\n\nasync function settle(el: OxEventView) {\n await el.updateComplete\n const inner = el.renderRoot.querySelector('calendar-monthly, calendar-weekly') as any\n await inner?.updateComplete\n /* the provider is awaited inside updated(), so give it a turn and let layout settle */\n await new Promise(resolve => setTimeout(resolve, 0))\n await inner?.updateComplete\n return inner\n}\n\ndescribe('the seven day columns stay the same width', () => {\n for (const mode of ['monthly', 'weekly'] as const) {\n it(`${mode}: one long title does not take the room of the other six days`, async () => {\n const el = (await fixture(\n html`<div style=\"width: 700px\">\n <ox-event-view mode=${mode} .eventProvider=${providerWithLongTitleOn(2)}></ox-event-view>\n </div>`\n )) as HTMLElement\n\n const view = el.querySelector('ox-event-view') as OxEventView\n const inner = await settle(view)\n\n const cells = [...inner.renderRoot.querySelectorAll('li[date]')] as HTMLElement[]\n expect(cells.length, 'no date cells were drawn, so this test measures nothing').to.be.greaterThan(6)\n\n const widths = cells.slice(0, 7).map(one => Math.round(one.getBoundingClientRect().width))\n const widest = Math.max(...widths)\n const narrowest = Math.min(...widths)\n\n /*\n * Measured before min-width: 0 was added — [58, 58, 353, 58, 58, 58, 58] at this width.\n * A pixel of rounding is fine; a column taking six times its share is not.\n */\n expect(widest - narrowest, `columns came out ${widths.join(', ')}`).to.be.lessThan(2)\n })\n }\n})\n\ndescribe('the built-in view switch tells the host', () => {\n it('emits mode-change, because the host keeps its own state next to this one', async () => {\n const el = (await fixture(html`<ox-event-view mode=\"monthly\"></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n const toggle = inner.querySelector('md-icon') as HTMLElement\n expect(toggle, 'the switch is slotted content — without it nothing can be clicked').to.exist\n\n const heard = oneEvent(el, 'mode-change')\n toggle.click()\n const event = await heard\n\n expect(event.detail.mode).to.equal('weekly')\n expect(el.mode, 'the element still switches on its own, for hosts that ignore the event').to.equal('weekly')\n })\n\n it('reports monthly on the way back', async () => {\n const el = (await fixture(html`<ox-event-view mode=\"weekly\"></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n const heard = oneEvent(el, 'mode-change')\n ;(inner.querySelector('md-icon') as HTMLElement).click()\n\n expect((await heard).detail.mode).to.equal('monthly')\n })\n})\n\n/**\n * The calendar drew \"Sun Mon Tue\" over \"September 2026\" on a screen whose every other word was\n * Korean: the weekday row was a literal English array and dayjs had no locale loaded.\n *\n * These read the rendered text rather than the helpers, because a helper that returns Korean\n * proves nothing if the template still holds the old array.\n */\ndescribe('the calendar speaks the language the document declares', () => {\n const original = document.documentElement.lang\n\n afterEach(() => {\n document.documentElement.lang = original\n })\n\n async function headerOf(lang: string, mode: 'monthly' | 'weekly') {\n document.documentElement.lang = lang\n const el = (await fixture(html`<ox-event-view mode=${mode}></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n return inner.renderRoot as ShadowRoot\n }\n\n for (const mode of ['monthly', 'weekly'] as const) {\n it(`${mode}: the weekday row is in Korean when the document says ko`, async () => {\n const root = await headerOf('ko', mode)\n const names = Array.from(root.querySelectorAll('li[day-of-week], [date-label]')).map(el =>\n el.textContent!.trim()\n )\n const row = names.join(' ')\n\n expect(row, `weekday row came out \"${row}\"`).to.not.match(/Sun|Mon|Tue|Wed|Thu|Fri|Sat/)\n expect(row, `weekday row came out \"${row}\"`).to.match(/[일월화수목금토]/)\n })\n\n it(`${mode}: the step buttons carry a name, not a \"<\" glyph`, async () => {\n const root = await headerOf('ko', mode)\n const buttons = Array.from(root.querySelectorAll('md-icon-button'))\n\n expect(buttons.length, 'previous and next should both be icon buttons').to.equal(2)\n for (const button of buttons) {\n const name = button.getAttribute('aria-label') ?? ''\n expect(name, 'an icon button with no accessible name is read as its glyph').to.not.equal('')\n expect(name).to.not.match(/^[<>]$/)\n }\n })\n }\n\n it('monthly: the month title follows the language', async () => {\n const root = await headerOf('ko', 'monthly')\n const title = root.querySelector('[this-month]')!.textContent!.trim()\n\n expect(title, `title came out \"${title}\"`).to.not.match(/January|February|March|September|December/)\n expect(title, `title came out \"${title}\"`).to.include('월')\n })\n\n it('monthly: today is named in the language, not the word \"Today\"', async () => {\n const root = await headerOf('ko', 'monthly')\n const labels = Array.from(root.querySelectorAll('md-text-button')).map(el => el.textContent!.trim())\n\n expect(labels.join(' '), `buttons came out \"${labels.join(' ')}\"`).to.not.include('Today')\n expect(labels.some(l => l.includes('오늘')), `buttons came out \"${labels.join(' ')}\"`).to.be.true\n })\n})\n\n/**\n * The day numbers were formatted once, when the grid was built, and the grid is rebuilt on a\n * month change - not on a language change. So switching language left \"Sep 6 (일)\": the\n * weekday came from the template and followed, the date came from the stored string and did\n * not. Found on the running plant, with the weekday already in Korean beside it.\n */\ndescribe('the dates follow a language change, not just the weekday', () => {\n const original = document.documentElement.lang\n\n afterEach(() => {\n document.documentElement.lang = original\n })\n\n for (const mode of ['monthly', 'weekly'] as const) {\n it(`${mode}: switching to ko rewrites the day labels, not only the weekday row`, async () => {\n document.documentElement.lang = 'en'\n const el = (await fixture(html`<ox-event-view mode=${mode}></ox-event-view>`)) as OxEventView\n const inner = await settle(el)\n\n document.documentElement.lang = 'ko'\n inner.requestUpdate()\n await inner.updateComplete\n\n const labels = Array.from((inner.renderRoot as ShadowRoot).querySelectorAll('[date-label]'))\n .map(el => el.textContent!.trim())\n .join(' ')\n\n expect(labels, `day labels came out \"${labels}\"`).to.not.match(\n /Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/\n )\n expect(labels, `day labels came out \"${labels}\"`).to.include('월')\n })\n }\n})\n\n/**\n * The view switch was a bare md-icon: 24px, no padding, no hit area and no accessible name,\n * sitting flush against a 40px button. On screen it read as four vertical bars.\n */\ndescribe('the view switch is a named button', () => {\n for (const [mode, other] of [\n ['monthly', 'week'],\n ['weekly', 'month']\n ] as const) {\n it(`${mode}: the switch is an icon button that says where it goes`, async () => {\n const el = (await fixture(html`<ox-event-view mode=${mode}></ox-event-view>`)) as OxEventView\n await settle(el)\n\n const button = (el.renderRoot as ShadowRoot).querySelector('md-icon-button')\n expect(button, 'the switch should be a button, not a bare icon').to.exist\n\n const name = button!.getAttribute('aria-label') ?? ''\n expect(name, 'an icon with no name is read as its glyph').to.include(other)\n })\n }\n\n it('a host can pass its own words', async () => {\n const el = (await fixture(\n html`<ox-event-view mode=\"monthly\" weekly-label=\"주간 보기\"></ox-event-view>`\n )) as OxEventView\n await settle(el)\n\n const button = (el.renderRoot as ShadowRoot).querySelector('md-icon-button')\n expect(button!.getAttribute('aria-label')).to.equal('주간 보기')\n })\n})\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../src/ox-event-view.js';
|