@c2n/date-selector 0.0.8
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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/date-selector.js +299 -0
- package/package.json +73 -0
- package/react.d.ts +24 -0
- package/react.js +3 -0
- package/types/src/date-selector.d.ts +124 -0
- package/types/src/date-selector.d.ts.map +1 -0
- package/vue.d.ts +37 -0
- package/vue.js +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nguyen Thai Vinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Date Selector
|
|
2
|
+
|
|
3
|
+
Accessible one- or two-month date-range picker for Lit applications and plain HTML.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @c2n/date-selector
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script type="module" src="@c2n/date-selector"></script>
|
|
11
|
+
|
|
12
|
+
<c2-date-selector from="2026-09-10" to="2026-09-15" min="2026-09-01" name="trip"></c2-date-selector>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Dates use local-calendar ISO strings (`YYYY-MM-DD`). The first selection starts a range, the second completes it, and
|
|
16
|
+
the next starts over. The component supports arrow-key navigation, month paging, locale and week-start options,
|
|
17
|
+
min/max constraints, disabled and required states, and form submission.
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { LitElement, html, nothing, unsafeCSS } from "lit";
|
|
2
|
+
import { property, queryAll, state } from "lit/decorators.js";
|
|
3
|
+
import { customElement } from "@c2n/core/element-helper.js";
|
|
4
|
+
//#region src/date-selector.scss?inline
|
|
5
|
+
var date_selector_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n vertical-align: middle;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.c2-date-selector {\n display: grid;\n grid-template-columns: repeat(var(--date-selector-months, 2), minmax(0, 1fr));\n gap: var(--c2-date-selector--month-gap,40px);\n min-width: var(--c2-date-selector--min-width,312px);\n box-sizing: border-box;\n padding: var(--c2-date-selector--padding,16px);\n border: var(--c2-date-selector--border,1px solid #e4e4e7);\n border-radius: var(--c2-date-selector--border-radius,14px);\n box-shadow: var(--c2-date-selector--box-shadow,0 12px 32px rgba(24, 24, 27, 0.08));\n background: var(--c2-date-selector--background,#ffffff);\n color: var(--c2-date-selector--color,#18181b);\n font-family: var(--c2-date-selector--font-family,inherit);\n font-size: var(--c2-date-selector--font-size,14px);\n}\n\n:host([months=\"1\"]) .c2-date-selector {\n --date-selector-months: 1;\n}\n\n.month {\n min-width: calc(7 * var(--c2-date-selector__day--size,40px));\n}\n\n.month-header {\n position: relative;\n display: flex;\n min-height: var(--c2-date-selector__navigation--size,32px);\n align-items: center;\n justify-content: center;\n margin-bottom: 8px;\n}\n\nh2 {\n margin: 0;\n font: inherit;\n font-weight: var(--c2-date-selector__month-title--font-weight,600);\n}\n\n.navigation {\n position: absolute;\n top: 0;\n display: inline-flex;\n width: var(--c2-date-selector__navigation--size,32px);\n height: var(--c2-date-selector__navigation--size,32px);\n align-items: center;\n justify-content: center;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: 999px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n font: inherit;\n font-size: 24px;\n line-height: 1;\n transition: background 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n.navigation.previous {\n left: 0;\n}\n.navigation.next {\n right: 0;\n}\n.navigation:hover:not(:disabled) {\n background: var(--c2-date-selector__navigation__hover--background,#f4f4f5);\n}\n.navigation:focus-visible {\n outline: var(--c2-date-selector__focus--outline,2px solid rgba(2, 101, 220, 0.4));\n outline-offset: var(--c2-date-selector__focus--outline-offset,2px);\n}\n.navigation:disabled {\n cursor: default;\n opacity: var(--c2-date-selector__disabled--opacity,0.38);\n}\n\n.weekdays,\n.days {\n display: grid;\n grid-template-columns: repeat(7, var(--c2-date-selector__day--size,40px));\n}\n\n.week {\n display: contents;\n}\n\n.weekdays span {\n display: flex;\n height: var(--c2-date-selector__day--size,40px);\n align-items: center;\n justify-content: center;\n color: var(--c2-date-selector__weekday--color,#71717a);\n font-size: var(--c2-date-selector__weekday--font-size,12px);\n}\n\n.day {\n position: relative;\n display: inline-flex;\n width: var(--c2-date-selector__day--size,40px);\n height: var(--c2-date-selector__day--size,40px);\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: inherit;\n cursor: pointer;\n font: inherit;\n}\n.day::before {\n position: absolute;\n inset: 0;\n content: \"\";\n background: transparent;\n}\n.day span {\n position: relative;\n z-index: 1;\n display: inline-flex;\n width: 100%;\n height: 100%;\n align-items: center;\n justify-content: center;\n border-radius: var(--c2-date-selector__day--border-radius,999px);\n transition: background 250ms cubic-bezier(0.2, 0, 0, 1), color 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n.day:hover:not(:disabled) span {\n background: var(--c2-date-selector__day__hover--background,#f4f4f5);\n}\n.day[aria-current=date]:not([aria-selected=true]) span {\n color: var(--c2-date-selector__day__today--color,#0265dc);\n font-weight: var(--c2-date-selector__day__today--font-weight,600);\n}\n.day.in-range::before, .day.range-start:not(.range-end)::before, .day.range-end:not(.range-start)::before {\n background: var(--c2-date-selector__range--background,#edf1fe);\n}\n.day.range-start::before {\n border-radius: var(--c2-date-selector__day--border-radius,999px) 0 0 var(--c2-date-selector__day--border-radius,999px);\n}\n.day.range-end::before {\n border-radius: 0 var(--c2-date-selector__day--border-radius,999px) var(--c2-date-selector__day--border-radius,999px) 0;\n}\n.day.in-range {\n color: var(--c2-date-selector__range--color,#18181b);\n}\n.day[aria-selected=true] span {\n background: var(--c2-date-selector__day__selected--background,#0265dc);\n color: var(--c2-date-selector__day__selected--color,#ffffff);\n}\n.day:focus-visible {\n z-index: 2;\n outline: var(--c2-date-selector__focus--outline,2px solid rgba(2, 101, 220, 0.4));\n outline-offset: var(--c2-date-selector__focus--outline-offset,2px);\n border-radius: var(--c2-date-selector__day--border-radius,999px);\n}\n.day:disabled {\n cursor: default;\n opacity: var(--c2-date-selector__day__disabled--opacity,0.38);\n}\n\n:host([disabled]) .c2-date-selector {\n opacity: var(--c2-date-selector__disabled--opacity,0.38);\n}\n\n@media (max-width: 680px) {\n .c2-date-selector {\n grid-template-columns: 1fr;\n }\n}";
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
|
|
8
|
+
function __decorate(decorators, target, key, desc) {
|
|
9
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
10
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
11
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
12
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/date-selector.ts
|
|
16
|
+
var isoPattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
17
|
+
function toIso(date) {
|
|
18
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
19
|
+
}
|
|
20
|
+
function fromIso(value) {
|
|
21
|
+
if (!isoPattern.test(value)) return null;
|
|
22
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
23
|
+
const date = new Date(year, month - 1, day, 12);
|
|
24
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day ? date : null;
|
|
25
|
+
}
|
|
26
|
+
function beginningOfMonth(date) {
|
|
27
|
+
return new Date(date.getFullYear(), date.getMonth(), 1, 12);
|
|
28
|
+
}
|
|
29
|
+
function addMonths(date, amount) {
|
|
30
|
+
return new Date(date.getFullYear(), date.getMonth() + amount, 1, 12);
|
|
31
|
+
}
|
|
32
|
+
function addDays(date, amount) {
|
|
33
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount, 12);
|
|
34
|
+
}
|
|
35
|
+
var DateSelector = class DateSelector extends LitElement {
|
|
36
|
+
constructor(..._args) {
|
|
37
|
+
super(..._args);
|
|
38
|
+
this.internals = this.attachInternals();
|
|
39
|
+
this.defaultsCaptured = false;
|
|
40
|
+
this.defaultFrom = "";
|
|
41
|
+
this.defaultTo = "";
|
|
42
|
+
this.disabledByForm = false;
|
|
43
|
+
this.visibleMonth = beginningOfMonth(/* @__PURE__ */ new Date());
|
|
44
|
+
this.focusDate = "";
|
|
45
|
+
this.from = "";
|
|
46
|
+
this.to = "";
|
|
47
|
+
this.min = "";
|
|
48
|
+
this.max = "";
|
|
49
|
+
this.locale = "";
|
|
50
|
+
this.weekStart = "monday";
|
|
51
|
+
this.months = 2;
|
|
52
|
+
this.name = "";
|
|
53
|
+
this.endName = "";
|
|
54
|
+
this.required = false;
|
|
55
|
+
this.disabled = false;
|
|
56
|
+
this.ariaLabel = "Choose a date range";
|
|
57
|
+
}
|
|
58
|
+
static {
|
|
59
|
+
this.formAssociated = true;
|
|
60
|
+
}
|
|
61
|
+
static {
|
|
62
|
+
this.styles = unsafeCSS(date_selector_default);
|
|
63
|
+
}
|
|
64
|
+
get form() {
|
|
65
|
+
return this.internals.form;
|
|
66
|
+
}
|
|
67
|
+
get labels() {
|
|
68
|
+
return this.internals.labels;
|
|
69
|
+
}
|
|
70
|
+
get validity() {
|
|
71
|
+
return this.internals.validity;
|
|
72
|
+
}
|
|
73
|
+
get validationMessage() {
|
|
74
|
+
return this.internals.validationMessage;
|
|
75
|
+
}
|
|
76
|
+
get willValidate() {
|
|
77
|
+
return this.internals.willValidate;
|
|
78
|
+
}
|
|
79
|
+
checkValidity() {
|
|
80
|
+
return this.internals.checkValidity();
|
|
81
|
+
}
|
|
82
|
+
reportValidity() {
|
|
83
|
+
return this.internals.reportValidity();
|
|
84
|
+
}
|
|
85
|
+
formResetCallback() {
|
|
86
|
+
this.from = this.defaultFrom;
|
|
87
|
+
this.to = this.defaultTo;
|
|
88
|
+
this.visibleMonth = beginningOfMonth(fromIso(this.from) ?? /* @__PURE__ */ new Date());
|
|
89
|
+
}
|
|
90
|
+
formDisabledCallback(disabled) {
|
|
91
|
+
this.disabledByForm = disabled && !this.hasAttribute("disabled");
|
|
92
|
+
}
|
|
93
|
+
formStateRestoreCallback(state) {
|
|
94
|
+
if (typeof state !== "string") return;
|
|
95
|
+
const [from, to = ""] = state.split("/");
|
|
96
|
+
this.from = from;
|
|
97
|
+
this.to = to;
|
|
98
|
+
}
|
|
99
|
+
willUpdate(changed) {
|
|
100
|
+
if (!this.defaultsCaptured) {
|
|
101
|
+
this.defaultFrom = this.from;
|
|
102
|
+
this.defaultTo = this.to;
|
|
103
|
+
this.defaultsCaptured = true;
|
|
104
|
+
const selected = fromIso(this.from);
|
|
105
|
+
if (selected) this.visibleMonth = beginningOfMonth(selected);
|
|
106
|
+
}
|
|
107
|
+
if (changed.has("from") || changed.has("to")) this.normalizeRange();
|
|
108
|
+
}
|
|
109
|
+
updated(changed) {
|
|
110
|
+
if (changed.has("from") || changed.has("to") || changed.has("name") || changed.has("endName") || changed.has("required") || changed.has("disabled")) this.syncFormState();
|
|
111
|
+
}
|
|
112
|
+
get effectiveDisabled() {
|
|
113
|
+
return this.disabled || this.disabledByForm;
|
|
114
|
+
}
|
|
115
|
+
get effectiveLocale() {
|
|
116
|
+
return this.locale || this.ownerDocument?.documentElement.lang || "en-US";
|
|
117
|
+
}
|
|
118
|
+
get displayedMonths() {
|
|
119
|
+
return Math.min(2, Math.max(1, Number.isFinite(this.months) ? Math.round(this.months) : 2));
|
|
120
|
+
}
|
|
121
|
+
normalizeRange() {
|
|
122
|
+
if (this.from && !fromIso(this.from)) this.from = "";
|
|
123
|
+
if (this.to && !fromIso(this.to)) this.to = "";
|
|
124
|
+
if (this.to && (!this.from || this.to < this.from)) this.to = "";
|
|
125
|
+
}
|
|
126
|
+
syncFormState() {
|
|
127
|
+
if (this.effectiveDisabled || !this.name || !this.from) this.internals.setFormValue(null);
|
|
128
|
+
else if (this.to) {
|
|
129
|
+
const data = new FormData();
|
|
130
|
+
data.append(this.name, this.from);
|
|
131
|
+
data.append(this.endName || `${this.name}-end`, this.to);
|
|
132
|
+
this.internals.setFormValue(data, `${this.from}/${this.to}`);
|
|
133
|
+
} else this.internals.setFormValue(this.from, `${this.from}/`);
|
|
134
|
+
if (this.required && (!this.from || !this.to)) this.internals.setValidity({ valueMissing: true }, "Choose a start and end date");
|
|
135
|
+
else this.internals.setValidity({});
|
|
136
|
+
}
|
|
137
|
+
isUnavailable(value) {
|
|
138
|
+
return this.effectiveDisabled || !!this.min && value < this.min || !!this.max && value > this.max;
|
|
139
|
+
}
|
|
140
|
+
select(value) {
|
|
141
|
+
if (this.isUnavailable(value)) return;
|
|
142
|
+
if (!this.from || this.to) {
|
|
143
|
+
this.from = value;
|
|
144
|
+
this.to = "";
|
|
145
|
+
} else if (value > this.from) this.to = value;
|
|
146
|
+
else {
|
|
147
|
+
this.from = value;
|
|
148
|
+
this.to = "";
|
|
149
|
+
}
|
|
150
|
+
this.focusDate = value;
|
|
151
|
+
this.dispatchEvent(new Event("input", {
|
|
152
|
+
bubbles: true,
|
|
153
|
+
composed: true
|
|
154
|
+
}));
|
|
155
|
+
this.dispatchEvent(new CustomEvent("change", {
|
|
156
|
+
detail: {
|
|
157
|
+
from: this.from,
|
|
158
|
+
to: this.to
|
|
159
|
+
},
|
|
160
|
+
bubbles: true,
|
|
161
|
+
composed: true
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
navigate(amount) {
|
|
165
|
+
if (this.effectiveDisabled) return;
|
|
166
|
+
const target = addMonths(this.visibleMonth, amount);
|
|
167
|
+
if (amount < 0 && this.min && toIso(new Date(target.getFullYear(), target.getMonth() + 1, 0, 12)) < this.min) return;
|
|
168
|
+
if (amount > 0 && this.max && toIso(target) > this.max) return;
|
|
169
|
+
this.visibleMonth = target;
|
|
170
|
+
}
|
|
171
|
+
async moveFocus(current, offset, byMonth = false) {
|
|
172
|
+
const date = fromIso(current);
|
|
173
|
+
if (!date) return;
|
|
174
|
+
const target = byMonth ? new Date(date.getFullYear(), date.getMonth() + offset, Math.min(date.getDate(), new Date(date.getFullYear(), date.getMonth() + offset + 1, 0).getDate()), 12) : addDays(date, offset);
|
|
175
|
+
const value = toIso(target);
|
|
176
|
+
if (this.isUnavailable(value)) return;
|
|
177
|
+
const firstVisible = toIso(this.visibleMonth);
|
|
178
|
+
const afterVisible = toIso(addMonths(this.visibleMonth, this.displayedMonths));
|
|
179
|
+
if (value < firstVisible || value >= afterVisible) this.visibleMonth = beginningOfMonth(target);
|
|
180
|
+
this.focusDate = value;
|
|
181
|
+
await this.updateComplete;
|
|
182
|
+
Array.from(this.dayButtons).find((button) => button.dataset.date === value)?.focus();
|
|
183
|
+
}
|
|
184
|
+
handleDayKeydown(event, value, column) {
|
|
185
|
+
let action;
|
|
186
|
+
if (event.key === "ArrowLeft") action = this.moveFocus(value, -1);
|
|
187
|
+
else if (event.key === "ArrowRight") action = this.moveFocus(value, 1);
|
|
188
|
+
else if (event.key === "ArrowUp") action = this.moveFocus(value, -7);
|
|
189
|
+
else if (event.key === "ArrowDown") action = this.moveFocus(value, 7);
|
|
190
|
+
else if (event.key === "Home") action = this.moveFocus(value, -column);
|
|
191
|
+
else if (event.key === "End") action = this.moveFocus(value, 6 - column);
|
|
192
|
+
else if (event.key === "PageUp") action = this.moveFocus(value, event.shiftKey ? -12 : -1, true);
|
|
193
|
+
else if (event.key === "PageDown") action = this.moveFocus(value, event.shiftKey ? 12 : 1, true);
|
|
194
|
+
if (action) event.preventDefault();
|
|
195
|
+
}
|
|
196
|
+
weekdayLabels() {
|
|
197
|
+
const formatter = new Intl.DateTimeFormat(this.effectiveLocale, { weekday: "narrow" });
|
|
198
|
+
const monday = new Date(2024, 0, 1, 12);
|
|
199
|
+
const start = this.weekStart === "monday" ? monday : addDays(monday, -1);
|
|
200
|
+
return Array.from({ length: 7 }, (_, index) => formatter.format(addDays(start, index)));
|
|
201
|
+
}
|
|
202
|
+
renderDay(date, column, dateLabel, today) {
|
|
203
|
+
const value = toIso(date);
|
|
204
|
+
const disabled = this.isUnavailable(value);
|
|
205
|
+
const selected = value === this.from || value === this.to;
|
|
206
|
+
const inRange = !!this.to && value > this.from && value < this.to;
|
|
207
|
+
const focusable = !disabled && (this.focusDate ? value === this.focusDate : this.from ? value === this.from : value === today || !this.min || value >= this.min);
|
|
208
|
+
return html`<button
|
|
209
|
+
class="day ${value === this.from ? "range-start" : ""} ${value === this.to ? "range-end" : ""} ${inRange ? "in-range" : ""}"
|
|
210
|
+
type="button"
|
|
211
|
+
role="gridcell"
|
|
212
|
+
data-date=${value}
|
|
213
|
+
aria-label=${dateLabel.format(date)}
|
|
214
|
+
aria-selected=${selected ? "true" : "false"}
|
|
215
|
+
aria-current=${value === today ? "date" : nothing}
|
|
216
|
+
?disabled=${disabled}
|
|
217
|
+
tabindex=${focusable ? "0" : "-1"}
|
|
218
|
+
@click=${() => this.select(value)}
|
|
219
|
+
@focus=${() => this.focusDate = value}
|
|
220
|
+
@keydown=${(event) => this.handleDayKeydown(event, value, column)}
|
|
221
|
+
>
|
|
222
|
+
<span>${date.getDate()}</span>
|
|
223
|
+
</button>`;
|
|
224
|
+
}
|
|
225
|
+
renderMonth(month, index) {
|
|
226
|
+
const year = month.getFullYear();
|
|
227
|
+
const monthIndex = month.getMonth();
|
|
228
|
+
const offset = this.weekStart === "monday" ? (month.getDay() + 6) % 7 : month.getDay();
|
|
229
|
+
const days = new Date(year, monthIndex + 1, 0).getDate();
|
|
230
|
+
const monthLabel = new Intl.DateTimeFormat(this.effectiveLocale, {
|
|
231
|
+
month: "long",
|
|
232
|
+
year: "numeric"
|
|
233
|
+
}).format(month);
|
|
234
|
+
const dateLabel = new Intl.DateTimeFormat(this.effectiveLocale, {
|
|
235
|
+
weekday: "long",
|
|
236
|
+
month: "long",
|
|
237
|
+
day: "numeric",
|
|
238
|
+
year: "numeric"
|
|
239
|
+
});
|
|
240
|
+
const today = toIso(/* @__PURE__ */ new Date());
|
|
241
|
+
const cells = Array.from({ length: 42 }, (_, cellIndex) => {
|
|
242
|
+
const day = cellIndex - offset + 1;
|
|
243
|
+
return day < 1 || day > days ? html`<span class="empty" role="gridcell" aria-disabled="true"></span>` : this.renderDay(new Date(year, monthIndex, day, 12), cellIndex % 7, dateLabel, today);
|
|
244
|
+
});
|
|
245
|
+
return html`<section class="month" aria-label=${monthLabel}>
|
|
246
|
+
<header class="month-header">
|
|
247
|
+
${index === 0 ? html`<button class="navigation previous" type="button" aria-label="Previous month" ?disabled=${this.effectiveDisabled} @click=${() => this.navigate(-1)}><span aria-hidden="true">‹</span></button>` : nothing}
|
|
248
|
+
<h2>${monthLabel}</h2>
|
|
249
|
+
${index === this.displayedMonths - 1 ? html`<button class="navigation next" type="button" aria-label="Next month" ?disabled=${this.effectiveDisabled} @click=${() => this.navigate(1)}><span aria-hidden="true">›</span></button>` : nothing}
|
|
250
|
+
</header>
|
|
251
|
+
<div class="weekdays" aria-hidden="true">${this.weekdayLabels().map((label) => html`<span>${label}</span>`)}</div>
|
|
252
|
+
<div class="days" role="grid" aria-label=${monthLabel}>
|
|
253
|
+
${Array.from({ length: 6 }, (_, week) => html`<div class="week" role="row">${cells.slice(week * 7, week * 7 + 7)}</div>`)}
|
|
254
|
+
</div>
|
|
255
|
+
</section>`;
|
|
256
|
+
}
|
|
257
|
+
render() {
|
|
258
|
+
return html`<div class="c2-date-selector" role="group" aria-label=${this.ariaLabel ?? nothing} aria-disabled=${this.effectiveDisabled ? "true" : "false"}>
|
|
259
|
+
${Array.from({ length: this.displayedMonths }, (_, index) => this.renderMonth(addMonths(this.visibleMonth, index), index))}
|
|
260
|
+
</div>`;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
__decorate([state()], DateSelector.prototype, "disabledByForm", void 0);
|
|
264
|
+
__decorate([state()], DateSelector.prototype, "visibleMonth", void 0);
|
|
265
|
+
__decorate([state()], DateSelector.prototype, "focusDate", void 0);
|
|
266
|
+
__decorate([queryAll(".day:not(:disabled)")], DateSelector.prototype, "dayButtons", void 0);
|
|
267
|
+
__decorate([property({
|
|
268
|
+
type: String,
|
|
269
|
+
reflect: true
|
|
270
|
+
})], DateSelector.prototype, "from", void 0);
|
|
271
|
+
__decorate([property({
|
|
272
|
+
type: String,
|
|
273
|
+
reflect: true
|
|
274
|
+
})], DateSelector.prototype, "to", void 0);
|
|
275
|
+
__decorate([property({ type: String })], DateSelector.prototype, "min", void 0);
|
|
276
|
+
__decorate([property({ type: String })], DateSelector.prototype, "max", void 0);
|
|
277
|
+
__decorate([property({ type: String })], DateSelector.prototype, "locale", void 0);
|
|
278
|
+
__decorate([property({
|
|
279
|
+
attribute: "week-start",
|
|
280
|
+
reflect: true
|
|
281
|
+
})], DateSelector.prototype, "weekStart", void 0);
|
|
282
|
+
__decorate([property({
|
|
283
|
+
type: Number,
|
|
284
|
+
reflect: true
|
|
285
|
+
})], DateSelector.prototype, "months", void 0);
|
|
286
|
+
__decorate([property({ type: String })], DateSelector.prototype, "name", void 0);
|
|
287
|
+
__decorate([property({ attribute: "end-name" })], DateSelector.prototype, "endName", void 0);
|
|
288
|
+
__decorate([property({
|
|
289
|
+
type: Boolean,
|
|
290
|
+
reflect: true
|
|
291
|
+
})], DateSelector.prototype, "required", void 0);
|
|
292
|
+
__decorate([property({
|
|
293
|
+
type: Boolean,
|
|
294
|
+
reflect: true
|
|
295
|
+
})], DateSelector.prototype, "disabled", void 0);
|
|
296
|
+
__decorate([property({ attribute: "aria-label" })], DateSelector.prototype, "ariaLabel", void 0);
|
|
297
|
+
DateSelector = __decorate([customElement("c2-date-selector")], DateSelector);
|
|
298
|
+
//#endregion
|
|
299
|
+
export { DateSelector };
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c2n/date-selector",
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/date-selector.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./types/src/date-selector.d.ts",
|
|
9
|
+
"default": "./dist/date-selector.js"
|
|
10
|
+
},
|
|
11
|
+
"./react": {
|
|
12
|
+
"types": "./react.d.ts",
|
|
13
|
+
"default": "./react.js"
|
|
14
|
+
},
|
|
15
|
+
"./vue": {
|
|
16
|
+
"types": "./vue.d.ts",
|
|
17
|
+
"default": "./vue.js"
|
|
18
|
+
},
|
|
19
|
+
"./custom-elements.json": "./custom-elements.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"types",
|
|
24
|
+
"react.d.ts",
|
|
25
|
+
"react.js",
|
|
26
|
+
"vue.d.ts",
|
|
27
|
+
"vue.js"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"date-selector",
|
|
31
|
+
"web component",
|
|
32
|
+
"lit"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"author": "code2nguyen@gmail.com",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"registry": "https://registry.npmjs.org",
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/code2nguyen/web-components.git"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"dev": "vite",
|
|
46
|
+
"build": "wireit",
|
|
47
|
+
"build:only": "vite build",
|
|
48
|
+
"type-check": "wireit"
|
|
49
|
+
},
|
|
50
|
+
"wireit": {
|
|
51
|
+
"type-check": {
|
|
52
|
+
"dependencies": [
|
|
53
|
+
"../../core:build"
|
|
54
|
+
],
|
|
55
|
+
"command": "tsc -p tsconfig.lib.json --composite false"
|
|
56
|
+
},
|
|
57
|
+
"build": {
|
|
58
|
+
"dependencies": [
|
|
59
|
+
"type-check"
|
|
60
|
+
],
|
|
61
|
+
"command": "vite build"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@c2n/core": "0.0.8",
|
|
66
|
+
"lit": "3.3.3"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@c2n/config": "*"
|
|
70
|
+
},
|
|
71
|
+
"customElements": "custom-elements.json",
|
|
72
|
+
"gitHead": "be59cbccee1d33ca248e39f69b05c0b595ec6c6d"
|
|
73
|
+
}
|
package/react.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// GENERATED by @c2n/framework-types. Do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// JSX types for the custom elements of @c2n/date-selector. Import once, anywhere in the program:
|
|
4
|
+
//
|
|
5
|
+
// import '@c2n/date-selector/react'
|
|
6
|
+
//
|
|
7
|
+
// Register the elements at module scope before React renders, or React writes an object prop as an
|
|
8
|
+
// attribute and it stringifies.
|
|
9
|
+
|
|
10
|
+
import type { DetailedHTMLProps, HTMLAttributes } from 'react'
|
|
11
|
+
import type { DateSelector } from '@c2n/date-selector'
|
|
12
|
+
|
|
13
|
+
/** Standard React host-element attributes plus the element's own public properties. */
|
|
14
|
+
type C2Props<T> = DetailedHTMLProps<HTMLAttributes<T>, T> & Partial<Omit<T, keyof HTMLElement>>
|
|
15
|
+
|
|
16
|
+
declare module 'react' {
|
|
17
|
+
namespace JSX {
|
|
18
|
+
interface IntrinsicElements {
|
|
19
|
+
'c2-date-selector': C2Props<DateSelector>
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {}
|
package/react.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { LitElement, type PropertyValues } from 'lit';
|
|
2
|
+
import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
|
|
3
|
+
export type DateSelectorWeekStart = 'monday' | 'sunday';
|
|
4
|
+
export interface DateRangeChangeDetail {
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DateSelectorEventMap {
|
|
9
|
+
input: Event;
|
|
10
|
+
change: CustomEvent<DateRangeChangeDetail>;
|
|
11
|
+
}
|
|
12
|
+
export interface DateSelector {
|
|
13
|
+
addEventListener: TypedAddEventListener<DateSelector, DateSelectorEventMap>;
|
|
14
|
+
removeEventListener: TypedRemoveEventListener<DateSelector, DateSelectorEventMap>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A keyboard-accessible date-range calendar inspired by travel booking pickers. It shows one or two consecutive
|
|
18
|
+
* months, starts a range on the first activation, completes it on the second, and starts over on the third. Dates are
|
|
19
|
+
* local-calendar ISO strings (`YYYY-MM-DD`), so they do not shift across time zones. Arrow keys move by day or week,
|
|
20
|
+
* Home/End move within the week, and Page Up/Page Down change month.
|
|
21
|
+
*
|
|
22
|
+
* When `name` is present, the start date is submitted under that name and the end date under `end-name` (or
|
|
23
|
+
* `${name}-end`). `required` requires a complete range. Use `min` and `max` to constrain availability.
|
|
24
|
+
*
|
|
25
|
+
* @tag c2-date-selector
|
|
26
|
+
* @event {Event} input - Fired after either endpoint changes, with `from` and `to` already updated.
|
|
27
|
+
* @event {CustomEvent<DateRangeChangeDetail>} change - Fired with `input`; `detail` contains the ISO `from` and `to` values.
|
|
28
|
+
* @cssproperty {color} [--c2-date-selector--background=#ffffff]
|
|
29
|
+
* @cssproperty {color} [--c2-date-selector--color=#18181b]
|
|
30
|
+
* @cssproperty {border} [--c2-date-selector--border=1px solid #e4e4e7]
|
|
31
|
+
* @cssproperty {border-radius} [--c2-date-selector--border-radius=14px]
|
|
32
|
+
* @cssproperty {box-shadow} [--c2-date-selector--box-shadow=0 12px 32px rgba(24, 24, 27, 0.08)]
|
|
33
|
+
* @cssproperty {pixel} [--c2-date-selector--min-width=312px] - Prevents the seven day columns and container padding from collapsing.
|
|
34
|
+
* @cssproperty {padding} [--c2-date-selector--padding=16px]
|
|
35
|
+
* @cssproperty {pixel} [--c2-date-selector--month-gap=40px]
|
|
36
|
+
* @cssproperty {font-family} [--c2-date-selector--font-family=inherit]
|
|
37
|
+
* @cssproperty {font-size} [--c2-date-selector--font-size=14px]
|
|
38
|
+
* @cssproperty {pixel} [--c2-date-selector__day--size=40px]
|
|
39
|
+
* @cssproperty {border-radius} [--c2-date-selector__day--border-radius=999px]
|
|
40
|
+
* @cssproperty {color} [--c2-date-selector__day__hover--background=#f4f4f5]
|
|
41
|
+
* @cssproperty {color} [--c2-date-selector__day__today--color=#0265dc]
|
|
42
|
+
* @cssproperty {font-weight} [--c2-date-selector__day__today--font-weight=600]
|
|
43
|
+
* @cssproperty {color} [--c2-date-selector__day__selected--background=#0265dc]
|
|
44
|
+
* @cssproperty {color} [--c2-date-selector__day__selected--color=#ffffff]
|
|
45
|
+
* @cssproperty {color} [--c2-date-selector__range--background=#edf1fe]
|
|
46
|
+
* @cssproperty {color} [--c2-date-selector__range--color=#18181b]
|
|
47
|
+
* @cssproperty {opacity} [--c2-date-selector__day__disabled--opacity=0.38]
|
|
48
|
+
* @cssproperty {color} [--c2-date-selector__weekday--color=#71717a]
|
|
49
|
+
* @cssproperty {font-size} [--c2-date-selector__weekday--font-size=12px]
|
|
50
|
+
* @cssproperty {font-weight} [--c2-date-selector__month-title--font-weight=600]
|
|
51
|
+
* @cssproperty {pixel} [--c2-date-selector__navigation--size=32px]
|
|
52
|
+
* @cssproperty {color} [--c2-date-selector__navigation__hover--background=#f4f4f5]
|
|
53
|
+
* @cssproperty {outline} [--c2-date-selector__focus--outline=2px solid rgba(2, 101, 220, 0.4)]
|
|
54
|
+
* @cssproperty {pixel} [--c2-date-selector__focus--outline-offset=2px]
|
|
55
|
+
* @cssproperty {opacity} [--c2-date-selector__disabled--opacity=0.38]
|
|
56
|
+
*/
|
|
57
|
+
export declare class DateSelector extends LitElement {
|
|
58
|
+
static formAssociated: boolean;
|
|
59
|
+
static styles: import("lit").CSSResult;
|
|
60
|
+
private readonly internals;
|
|
61
|
+
private defaultsCaptured;
|
|
62
|
+
private defaultFrom;
|
|
63
|
+
private defaultTo;
|
|
64
|
+
private disabledByForm;
|
|
65
|
+
private visibleMonth;
|
|
66
|
+
private focusDate;
|
|
67
|
+
private readonly dayButtons;
|
|
68
|
+
/** Start of the selected range as `YYYY-MM-DD`. */
|
|
69
|
+
from: string;
|
|
70
|
+
/** End of the selected range as `YYYY-MM-DD`; empty while the range is incomplete. */
|
|
71
|
+
to: string;
|
|
72
|
+
/** Earliest selectable date as `YYYY-MM-DD`. */
|
|
73
|
+
min: string;
|
|
74
|
+
/** Latest selectable date as `YYYY-MM-DD`. */
|
|
75
|
+
max: string;
|
|
76
|
+
/** Locale used for month, weekday and accessible date labels. Defaults to the document language. */
|
|
77
|
+
locale: string;
|
|
78
|
+
/** First day of each calendar week. */
|
|
79
|
+
weekStart: DateSelectorWeekStart;
|
|
80
|
+
/** Number of consecutive months shown; clamped to one or two. */
|
|
81
|
+
months: number;
|
|
82
|
+
/** Form field name for the start date. */
|
|
83
|
+
name: string;
|
|
84
|
+
/** Form field name for the end date. Defaults to `${name}-end`. */
|
|
85
|
+
endName: string;
|
|
86
|
+
/** Requires both endpoints when the selector participates in a form. */
|
|
87
|
+
required: boolean;
|
|
88
|
+
/** Prevents navigation and selection and excludes the control from form submission. */
|
|
89
|
+
disabled: boolean;
|
|
90
|
+
/** Accessible name for the complete calendar. */
|
|
91
|
+
ariaLabel: string | null;
|
|
92
|
+
get form(): HTMLFormElement | null;
|
|
93
|
+
get labels(): NodeList;
|
|
94
|
+
get validity(): ValidityState;
|
|
95
|
+
get validationMessage(): string;
|
|
96
|
+
get willValidate(): boolean;
|
|
97
|
+
checkValidity(): boolean;
|
|
98
|
+
reportValidity(): boolean;
|
|
99
|
+
formResetCallback(): void;
|
|
100
|
+
formDisabledCallback(disabled: boolean): void;
|
|
101
|
+
formStateRestoreCallback(state: string | File | FormData | null): void;
|
|
102
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
103
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
104
|
+
private get effectiveDisabled();
|
|
105
|
+
private get effectiveLocale();
|
|
106
|
+
private get displayedMonths();
|
|
107
|
+
private normalizeRange;
|
|
108
|
+
private syncFormState;
|
|
109
|
+
private isUnavailable;
|
|
110
|
+
private select;
|
|
111
|
+
private navigate;
|
|
112
|
+
private moveFocus;
|
|
113
|
+
private handleDayKeydown;
|
|
114
|
+
private weekdayLabels;
|
|
115
|
+
private renderDay;
|
|
116
|
+
private renderMonth;
|
|
117
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
118
|
+
}
|
|
119
|
+
declare global {
|
|
120
|
+
interface HTMLElementTagNameMap {
|
|
121
|
+
'c2-date-selector': DateSelector;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=date-selector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-selector.d.ts","sourceRoot":"","sources":["../../src/date-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAG/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAGhG,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEvD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,WAAW,CAAC,qBAAqB,CAAC,CAAA;CAC3C;AAED,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,qBAAqB,CAAC,YAAY,EAAE,oBAAoB,CAAC,CAAA;IAC3E,mBAAmB,EAAE,wBAAwB,CAAC,YAAY,EAAE,oBAAoB,CAAC,CAAA;CAClF;AA2BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,MAAM,CAAC,cAAc,UAAO;IAC5B,OAAgB,MAAM,0BAAoB;IAE1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,SAAS,CAAK;IACb,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,SAAS,CAAK;IACE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;IAE5F,mDAAmD;IACR,IAAI,SAAK;IACpD,sFAAsF;IAC3C,EAAE,SAAK;IAClD,gDAAgD;IACpB,GAAG,SAAK;IACpC,8CAA8C;IAClB,GAAG,SAAK;IACpC,oGAAoG;IACxE,MAAM,SAAK;IACvC,uCAAuC;IACe,SAAS,EAAE,qBAAqB,CAAW;IACjG,iEAAiE;IACtB,MAAM,SAAI;IACrD,0CAA0C;IACd,IAAI,SAAK;IACrC,mEAAmE;IAC9B,OAAO,SAAK;IACjD,wEAAwE;IAC5B,QAAQ,UAAQ;IAC5D,uFAAuF;IAC3C,QAAQ,UAAQ;IAC5D,iDAAiD;IACD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAwB;IAEhG,IAAI,IAAI,2BAEP;IACD,IAAI,MAAM,aAET;IACD,IAAI,QAAQ,kBAEX;IACD,IAAI,iBAAiB,WAEpB;IACD,IAAI,YAAY,YAEf;IACD,aAAa;IAGb,cAAc;IAId,iBAAiB;IAMjB,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IAItC,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI;cAO5C,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;cAWxC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAKxD,OAAO,KAAK,iBAAiB,GAE5B;IACD,OAAO,KAAK,eAAe,GAE1B;IACD,OAAO,KAAK,eAAe,GAE1B;IAED,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,MAAM;IAed,OAAO,CAAC,QAAQ;YAQF,SAAS;IAuBvB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,SAAS;IAyBjB,OAAO,CAAC,WAAW;IA2BV,MAAM;CAKhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,YAAY,CAAA;KACjC;CACF"}
|
package/vue.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// GENERATED by @c2n/framework-types. Do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// Vue template types for the custom elements of @c2n/date-selector. Import once, anywhere in the program:
|
|
4
|
+
//
|
|
5
|
+
// import '@c2n/date-selector/vue'
|
|
6
|
+
//
|
|
7
|
+
// Tell the compiler about the tags as well, or every c2-* tag is resolved as a Vue component and renders
|
|
8
|
+
// nothing: template.compilerOptions.isCustomElement = (tag) => tag.startsWith('c2-') in vite.config.ts.
|
|
9
|
+
|
|
10
|
+
import type { DefineComponent, HTMLAttributes } from 'vue'
|
|
11
|
+
import type { DateSelector, DateSelectorEventMap } from '@c2n/date-selector'
|
|
12
|
+
|
|
13
|
+
/** The element's own public properties, plus every attribute Vue understands on a host element. */
|
|
14
|
+
type C2Props<T> = Partial<Omit<T, keyof HTMLElement>> & HTMLAttributes
|
|
15
|
+
|
|
16
|
+
declare module 'vue' {
|
|
17
|
+
interface GlobalComponents {
|
|
18
|
+
'c2-date-selector': DefineComponent<
|
|
19
|
+
C2Props<DateSelector> & {
|
|
20
|
+
'week-start'?: unknown
|
|
21
|
+
'end-name'?: unknown
|
|
22
|
+
'aria-label'?: unknown
|
|
23
|
+
onInput?: (event: DateSelectorEventMap['input']) => void
|
|
24
|
+
onChange?: (event: DateSelectorEventMap['change']) => void
|
|
25
|
+
}
|
|
26
|
+
>
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare module '@vue/runtime-dom' {
|
|
31
|
+
interface HTMLAttributes {
|
|
32
|
+
/** Vue's own definition omits it, and slotting a plain element into a component needs it. */
|
|
33
|
+
slot?: string
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {}
|
package/vue.js
ADDED