@oslokommune/punkt-elements 11.12.3
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/dist/converters-01e4a1b1.cjs +5 -0
- package/dist/converters-92631e0d.js +105 -0
- package/dist/directive-6900e150.cjs +23 -0
- package/dist/directive-720b7cc8.js +606 -0
- package/dist/index-34184de4.js +173 -0
- package/dist/index-51027300.cjs +13 -0
- package/dist/pkt-el-calendar.cjs +60 -0
- package/dist/pkt-el-calendar.js +1504 -0
- package/dist/pkt-el-component-template.cjs +29 -0
- package/dist/pkt-el-component-template.js +100 -0
- package/dist/pkt-el-element.cjs +1 -0
- package/dist/pkt-el-element.js +5 -0
- package/dist/pkt-el-icon.cjs +9 -0
- package/dist/pkt-el-icon.js +103 -0
- package/dist/pkt-el-index.cjs +1 -0
- package/dist/pkt-el-index.js +12 -0
- package/dist/property-0378afc0.js +47 -0
- package/dist/property-e170cbca.cjs +9 -0
- package/dist/src/components/calendar/index.d.ts +60 -0
- package/dist/src/components/component-template/index.d.ts +35 -0
- package/dist/src/components/element/index.d.ts +25 -0
- package/dist/src/components/icon/index.d.ts +30 -0
- package/dist/src/components/index.d.ts +3 -0
- package/dist/src/controllers/pkt-slot-controller.d.ts +12 -0
- package/dist/src/helpers/converters.d.ts +3 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/translations/no.json.d.ts +43 -0
- package/dist/vite.config.d.ts +2 -0
- package/package.json +61 -0
- package/src/components/calendar/index.ts +375 -0
- package/src/components/component-template/index.ts +113 -0
- package/src/components/element/index.ts +37 -0
- package/src/components/icon/index.ts +100 -0
- package/src/components/index.ts +3 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js'
|
|
4
|
+
import strings from '../../translations/no.json'
|
|
5
|
+
import { getWeek, eachDayOfInterval, getISODay, format as dateFormat } from 'date-fns'
|
|
6
|
+
import { csvToArray, stringToDate, stringsToDate } from '../../helpers/converters'
|
|
7
|
+
import { PktElement } from '../element'
|
|
8
|
+
// TODO: Change this to @oslokommune/punkt-elements as soon as it exists
|
|
9
|
+
import '../icon'
|
|
10
|
+
|
|
11
|
+
@customElement('pkt-el-calendar')
|
|
12
|
+
export class PktCalendar extends PktElement {
|
|
13
|
+
/**
|
|
14
|
+
* Element attributes
|
|
15
|
+
*/
|
|
16
|
+
@property({ converter: stringToDate }) date: Date = new Date()
|
|
17
|
+
@property({ converter: csvToArray, reflect: true }) selected: string[] = []
|
|
18
|
+
@property({ converter: csvToArray }) excludeweekdays: string[] = []
|
|
19
|
+
@property({ converter: stringsToDate }) excludedates: Date[] = []
|
|
20
|
+
@property({ converter: stringToDate }) earliest: Date | null = null
|
|
21
|
+
@property({ converter: stringToDate }) latest: Date | null = null
|
|
22
|
+
|
|
23
|
+
@property({ type: Boolean, reflect: true }) weeknumbers: boolean = false
|
|
24
|
+
@property({ type: Boolean, reflect: true }) withcontrols: boolean = false
|
|
25
|
+
@property({ type: Boolean, reflect: true }) multiple: boolean = false
|
|
26
|
+
@property({ type: Boolean, reflect: true }) range: boolean = false
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Strings
|
|
30
|
+
*/
|
|
31
|
+
@property({ type: Array }) dayStrings: string[] = strings.dates.daysShort
|
|
32
|
+
@property({ type: Array }) monthStrings: string[] = strings.dates.months
|
|
33
|
+
@property({ type: String }) weekString: string = strings.dates.week
|
|
34
|
+
@property({ type: String }) prevMonthString: string = strings.dates.prevMonth
|
|
35
|
+
@property({ type: String }) nextMonthString: string = strings.dates.nextMonth
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Private properties
|
|
39
|
+
*/
|
|
40
|
+
@property({ type: Array }) private _selected: Date[] = []
|
|
41
|
+
@property({ type: Number }) private year: number = 0
|
|
42
|
+
@property({ type: Number }) private month: number = 0
|
|
43
|
+
@property({ type: Number }) private week: number = 0
|
|
44
|
+
@property({ type: Date }) private rangeHovered: Date | null = null
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Runs on mount, used to set up various values and whatever you need to run
|
|
48
|
+
*/
|
|
49
|
+
connectedCallback() {
|
|
50
|
+
const today = this.date
|
|
51
|
+
this.year = today.getFullYear()
|
|
52
|
+
this.month = today.getMonth()
|
|
53
|
+
this.week = getWeek(new Date(this.year, this.month, 1))
|
|
54
|
+
this.selected.length &&
|
|
55
|
+
this.selected.forEach((d) => {
|
|
56
|
+
this._selected.push(new Date(d))
|
|
57
|
+
})
|
|
58
|
+
super.connectedCallback()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Component functionality and render
|
|
63
|
+
*/
|
|
64
|
+
render() {
|
|
65
|
+
return html`
|
|
66
|
+
<div class="pkt-calendar ${this.weeknumbers && 'pkt-cal-weeknumbers'}">
|
|
67
|
+
<nav class="pkt-cal-month-nav">
|
|
68
|
+
<button
|
|
69
|
+
@click=${this.isPrevMonthAllowed() && this.prevMonth}
|
|
70
|
+
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only"
|
|
71
|
+
?disabled=${!this.isPrevMonthAllowed()}
|
|
72
|
+
>
|
|
73
|
+
<pkt-el-icon class="pkt-btn__icon" name="chevron-thin-left"></pkt-el-icon>
|
|
74
|
+
<span class="pkt-btn__text">${this.prevMonthString}</span>
|
|
75
|
+
</button>
|
|
76
|
+
${this.renderMonthNav()}
|
|
77
|
+
<button
|
|
78
|
+
@click=${this.isNextMonthAllowed() && this.nextMonth}
|
|
79
|
+
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--icon-only"
|
|
80
|
+
?disabled=${!this.isNextMonthAllowed()}
|
|
81
|
+
>
|
|
82
|
+
<pkt-el-icon class="pkt-btn__icon" name="chevron-thin-right"></pkt-el-icon>
|
|
83
|
+
<span class="pkt-btn__text">${this.nextMonthString}</span>
|
|
84
|
+
</button>
|
|
85
|
+
</nav>
|
|
86
|
+
<ul class="pkt-cal-days pkt-txt-12-medium" role="grid">
|
|
87
|
+
${this.renderDayNames()} ${this.renderCalendarBody()}
|
|
88
|
+
</ul>
|
|
89
|
+
</div>
|
|
90
|
+
`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private renderDayNames() {
|
|
94
|
+
const days = []
|
|
95
|
+
if (this.weeknumbers) {
|
|
96
|
+
days.push(html`<li>${this.weekString}</li>`)
|
|
97
|
+
}
|
|
98
|
+
for (let i = 0; i < this.dayStrings.length; i++) {
|
|
99
|
+
days.push(html`<li>${this.dayStrings[i]}</li>`)
|
|
100
|
+
}
|
|
101
|
+
return days
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private renderMonthNav() {
|
|
105
|
+
let monthView = []
|
|
106
|
+
if (this.withcontrols) {
|
|
107
|
+
monthView.push(html`<div class="pkt-cal-month-picker">
|
|
108
|
+
<select
|
|
109
|
+
class="pkt-input pkt-input-compact"
|
|
110
|
+
@change=${(e: any) => {
|
|
111
|
+
this.changeMonth(this.year, e.target.value)
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
${this.monthStrings.map(
|
|
115
|
+
(month, index) =>
|
|
116
|
+
html`<option value=${index} ?selected=${this.month === index}>${month}</option>`,
|
|
117
|
+
)}
|
|
118
|
+
</select>
|
|
119
|
+
<input
|
|
120
|
+
class="pkt-input pkt-cal-input-year pkt-input-compact"
|
|
121
|
+
type="number"
|
|
122
|
+
size="4"
|
|
123
|
+
placeholder="0000"
|
|
124
|
+
@change=${(e: any) => {
|
|
125
|
+
this.changeMonth(e.target.value, this.month)
|
|
126
|
+
}}
|
|
127
|
+
value="${this.year}"
|
|
128
|
+
/>
|
|
129
|
+
</div> `)
|
|
130
|
+
} else {
|
|
131
|
+
monthView.push(
|
|
132
|
+
html`<div class="pkt-txt-16-medium" aria-live="polite">
|
|
133
|
+
${this.monthStrings[this.month]} ${this.year}
|
|
134
|
+
</div>`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
return monthView
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private renderCalendarBody() {
|
|
141
|
+
const today = new Date()
|
|
142
|
+
const firstDayOfMonth = new Date(this.year, this.month, 1)
|
|
143
|
+
const lastDayOfMonth = new Date(this.year, this.month + 1, 0)
|
|
144
|
+
const startingDay = (firstDayOfMonth.getDay() + 6) % 7
|
|
145
|
+
const numDays = lastDayOfMonth.getDate()
|
|
146
|
+
const numRows = Math.ceil((numDays + startingDay) / 7)
|
|
147
|
+
const lastDayOfPrevMonth = new Date(this.year, this.month, 0)
|
|
148
|
+
const numDaysPrevMonth = lastDayOfPrevMonth.getDate()
|
|
149
|
+
|
|
150
|
+
let dayCounter = 1
|
|
151
|
+
this.week = getWeek(new Date(this.year, this.month, 1))
|
|
152
|
+
|
|
153
|
+
const rows = []
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < numRows; i++) {
|
|
156
|
+
const cells = []
|
|
157
|
+
|
|
158
|
+
this.weeknumbers && cells.push(html`<li class="pkt-cal-week">${this.week}</li>`)
|
|
159
|
+
this.week++
|
|
160
|
+
|
|
161
|
+
for (let j = 1; j < 8; j++) {
|
|
162
|
+
if (i === 0 && j < startingDay) {
|
|
163
|
+
const dayFromPrevMonth = numDaysPrevMonth - (startingDay - j - 1)
|
|
164
|
+
cells.push(html`<li class="pkt-cal-other">
|
|
165
|
+
<button class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only" disabled>
|
|
166
|
+
<span class="pkt-btn__text pkt-txt-14-light">${dayFromPrevMonth}</span>
|
|
167
|
+
</button>
|
|
168
|
+
</li>`)
|
|
169
|
+
} else if (dayCounter > numDays) {
|
|
170
|
+
cells.push(html`<li class="pkt-cal-other">
|
|
171
|
+
<button class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only" disabled>
|
|
172
|
+
<span class="pkt-btn__text pkt-txt-14-light">${dayCounter - numDays}</span>
|
|
173
|
+
</button>
|
|
174
|
+
</li>`)
|
|
175
|
+
dayCounter++
|
|
176
|
+
} else {
|
|
177
|
+
const currentDate = new Date(this.year, this.month, dayCounter)
|
|
178
|
+
const currentDateISO = this.formatISODate(currentDate)
|
|
179
|
+
const isToday = currentDateISO === this.formatISODate(today)
|
|
180
|
+
const isSelected = this.selected.includes(currentDateISO)
|
|
181
|
+
const classes = {
|
|
182
|
+
'pkt-cal-today': isToday,
|
|
183
|
+
'pkt-cal-selected': isSelected,
|
|
184
|
+
'pkt-cal-in-range': this.isInRange(currentDate),
|
|
185
|
+
'pkt-cal-excluded': this.isExcluded(j, currentDate),
|
|
186
|
+
'pkt-cal-in-range-first':
|
|
187
|
+
this.range &&
|
|
188
|
+
(this.selected.length === 2 || this.rangeHovered !== null) &&
|
|
189
|
+
currentDateISO === this.selected[0],
|
|
190
|
+
'pkt-cal-in-range-last':
|
|
191
|
+
this.range && this.selected.length === 2 && currentDateISO === this.selected[1],
|
|
192
|
+
'pkt-cal-range-hover':
|
|
193
|
+
this.rangeHovered !== null &&
|
|
194
|
+
currentDateISO === this.formatISODate(this.rangeHovered),
|
|
195
|
+
}
|
|
196
|
+
cells.push(
|
|
197
|
+
html`<li class=${classMap(classes)}>
|
|
198
|
+
<button
|
|
199
|
+
class="pkt-btn pkt-btn--tertiary pkt-btn--small pkt-btn--label-only"
|
|
200
|
+
@click=${() => this.handleDateSelect(currentDate)}
|
|
201
|
+
@mouseover=${() =>
|
|
202
|
+
this.range &&
|
|
203
|
+
!this.isExcluded(j, currentDate) &&
|
|
204
|
+
this.handleRangeHover(currentDate)}
|
|
205
|
+
?disabled=${this.isExcluded(j, currentDate)}
|
|
206
|
+
>
|
|
207
|
+
<span class="pkt-btn__text pkt-txt-14-light">${dayCounter}</span>
|
|
208
|
+
</button>
|
|
209
|
+
</li>`,
|
|
210
|
+
)
|
|
211
|
+
dayCounter++
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
rows.push(html`${cells}`)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return rows
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private isExcluded(weekday: number, date: Date) {
|
|
222
|
+
if (this.excludeweekdays.includes(weekday.toString())) return true
|
|
223
|
+
if (this.earliest && date < this.earliest) return true
|
|
224
|
+
if (this.latest && date > this.latest) return true
|
|
225
|
+
return this.excludedates.some((x) => {
|
|
226
|
+
return x.toDateString() === date.toDateString()
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private formatISODate(date: Date) {
|
|
231
|
+
return dateFormat(date, 'yyyy-MM-dd')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
isPrevMonthAllowed() {
|
|
235
|
+
const prevMonth = new Date(this.year, this.month, 0)
|
|
236
|
+
if (this.earliest && this.earliest > prevMonth) return false
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private prevMonth() {
|
|
241
|
+
const newMonth = this.month === 0 ? 11 : this.month - 1
|
|
242
|
+
const newYear = this.month === 0 ? this.year - 1 : this.year
|
|
243
|
+
this.changeMonth(newYear, newMonth)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
isNextMonthAllowed() {
|
|
247
|
+
const nextMonth = new Date(this.year, this.month === 11 ? 0 : this.month + 1, 1)
|
|
248
|
+
if (this.latest && this.latest < nextMonth) return false
|
|
249
|
+
return true
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private nextMonth() {
|
|
253
|
+
const newMonth = this.month === 11 ? 0 : this.month + 1
|
|
254
|
+
const newYear = this.month === 11 ? this.year + 1 : this.year
|
|
255
|
+
this.changeMonth(newYear, newMonth)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private changeMonth(year: number, month: number) {
|
|
259
|
+
this.year = year
|
|
260
|
+
this.month = month
|
|
261
|
+
this.requestUpdate()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private isInRange(date: Date) {
|
|
265
|
+
if (this.range && this._selected.length === 2) {
|
|
266
|
+
if (date > this._selected[0] && date < this._selected[1]) return true
|
|
267
|
+
} else if (this.range && this._selected.length === 1 && this.rangeHovered) {
|
|
268
|
+
if (date > this._selected[0] && date < this.rangeHovered) return true
|
|
269
|
+
}
|
|
270
|
+
return false
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private isRangeAllowed(date: Date) {
|
|
274
|
+
let allowed = true
|
|
275
|
+
if (this._selected.length === 1) {
|
|
276
|
+
const days = eachDayOfInterval({
|
|
277
|
+
start: this._selected[0],
|
|
278
|
+
end: date,
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
if (Array.isArray(days) && days.length) {
|
|
282
|
+
for (let i = 0; i < days.length; i++) {
|
|
283
|
+
this.excludedates.forEach((d) => {
|
|
284
|
+
if (d > this._selected[0] && d < date) {
|
|
285
|
+
allowed = false
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
if (this.excludeweekdays.includes(getISODay(days[i]).toString())) {
|
|
289
|
+
allowed = false
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return allowed
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private emptySelected() {
|
|
298
|
+
this.selected.splice(0, this.selected.length)
|
|
299
|
+
this._selected.splice(0, this._selected.length)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private addToSelected(selectedDate: Date) {
|
|
303
|
+
this.selected.push(this.formatISODate(selectedDate))
|
|
304
|
+
this._selected.push(selectedDate)
|
|
305
|
+
this.requestUpdate()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private removeFromSelected(selectedDate: Date) {
|
|
309
|
+
const selectedDateISO = this.formatISODate(selectedDate)
|
|
310
|
+
this.selected.splice(this.selected.indexOf(selectedDateISO), 1)
|
|
311
|
+
this._selected.splice(this.selected.indexOf(selectedDateISO), 1)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private toggleSelected(selectedDate: Date) {
|
|
315
|
+
const selectedDateISO = this.formatISODate(selectedDate)
|
|
316
|
+
this.selected.includes(selectedDateISO)
|
|
317
|
+
? this.removeFromSelected(selectedDate)
|
|
318
|
+
: this.addToSelected(selectedDate)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private handleRangeSelect(selectedDate: Date) {
|
|
322
|
+
const selectedDateISO = this.formatISODate(selectedDate)
|
|
323
|
+
if (this.selected.includes(selectedDateISO)) {
|
|
324
|
+
if (this.selected.indexOf(selectedDateISO) === 0) {
|
|
325
|
+
this.emptySelected()
|
|
326
|
+
} else {
|
|
327
|
+
this.removeFromSelected(selectedDate)
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
if (this.selected.length > 1) {
|
|
331
|
+
this.emptySelected()
|
|
332
|
+
this.addToSelected(selectedDate)
|
|
333
|
+
} else {
|
|
334
|
+
if (this.selected.length === 1 && !this.isRangeAllowed(selectedDate)) {
|
|
335
|
+
this.emptySelected()
|
|
336
|
+
}
|
|
337
|
+
if (this.selected.length === 1 && this._selected[0] > selectedDate) {
|
|
338
|
+
this.emptySelected()
|
|
339
|
+
}
|
|
340
|
+
this.addToSelected(selectedDate)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private handleRangeHover(date: Date) {
|
|
346
|
+
if (
|
|
347
|
+
this.range &&
|
|
348
|
+
this._selected.length === 1 &&
|
|
349
|
+
this.isRangeAllowed(date) &&
|
|
350
|
+
this._selected[0] < date
|
|
351
|
+
) {
|
|
352
|
+
this.rangeHovered = date
|
|
353
|
+
} else {
|
|
354
|
+
this.rangeHovered = null
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private handleDateSelect(selectedDate: Date) {
|
|
359
|
+
if (this.range) {
|
|
360
|
+
this.handleRangeSelect(selectedDate)
|
|
361
|
+
} else if (this.multiple) {
|
|
362
|
+
this.toggleSelected(selectedDate)
|
|
363
|
+
} else {
|
|
364
|
+
this.emptySelected()
|
|
365
|
+
this.addToSelected(selectedDate)
|
|
366
|
+
}
|
|
367
|
+
this.dispatchEvent(
|
|
368
|
+
new CustomEvent('date-selected', {
|
|
369
|
+
detail: this.selected,
|
|
370
|
+
bubbles: true,
|
|
371
|
+
composed: true,
|
|
372
|
+
}),
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { ref } from 'lit/directives/ref.js'
|
|
4
|
+
import { PktElement } from '../element'
|
|
5
|
+
|
|
6
|
+
// If you need to convert an object to a list of classes, use classMap
|
|
7
|
+
import { classMap } from 'lit/directives/class-map.js'
|
|
8
|
+
|
|
9
|
+
// If you need text strings add them in this file and include it
|
|
10
|
+
import translations from '../../translations/no.json'
|
|
11
|
+
|
|
12
|
+
// If you need to convert attribute strings to JavaScript objects,
|
|
13
|
+
// use helpers from converters.ts
|
|
14
|
+
import { csvToArray } from '../../helpers/converters'
|
|
15
|
+
|
|
16
|
+
@customElement('pkt-el-component')
|
|
17
|
+
export class PktComponent extends PktElement {
|
|
18
|
+
/**
|
|
19
|
+
* Element attributes => props
|
|
20
|
+
* Example:
|
|
21
|
+
* <pkt-el-component string="hei" strings="hei,og,hallo" darkmode>
|
|
22
|
+
* Hei!
|
|
23
|
+
* </pkt-el-component>
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
@property({ type: String }) string: string = ''
|
|
27
|
+
@property({ converter: csvToArray }) strings: string[] = []
|
|
28
|
+
@property({ type: Boolean }) darkmode: boolean = false
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Private properties, for internal use only
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
@property({ type: Array }) private _list: string[] = []
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Runs on mount, used to set up various values and whatever you need to run
|
|
38
|
+
*/
|
|
39
|
+
connectedCallback() {
|
|
40
|
+
this.strings.length &&
|
|
41
|
+
this.strings.forEach((string) => {
|
|
42
|
+
this._list.push(string.toUpperCase())
|
|
43
|
+
})
|
|
44
|
+
super.connectedCallback()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Render functions
|
|
49
|
+
*/
|
|
50
|
+
render() {
|
|
51
|
+
const classes = {
|
|
52
|
+
'pkt-component': true,
|
|
53
|
+
'pkt-component--has-list': this.strings.length > 0,
|
|
54
|
+
'pkt-darkmode': this.darkmode,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return html`
|
|
58
|
+
<div class="${classMap(classes)}">
|
|
59
|
+
<h1 class="pkt-txt-28">${this.string}</h1>
|
|
60
|
+
|
|
61
|
+
<h2 class="pkt-txt-22">Innhold fra attributter og funksjoner</h2>
|
|
62
|
+
<div>${this.renderList(this.strings)}</div>
|
|
63
|
+
<div>${this.renderList(this.doStuff(this._list))}</div>
|
|
64
|
+
|
|
65
|
+
<h2 class="pkt-txt-22">Slot</h2>
|
|
66
|
+
<div ${ref(this.defaultSlot)}>defaultSlotRef</div>
|
|
67
|
+
<h2 class="pkt-txt-22">Named slot</h2>
|
|
68
|
+
<select
|
|
69
|
+
name="named-slot"
|
|
70
|
+
${ref(this.namedSlot)}
|
|
71
|
+
@change=${(e: Event) => alert((e.target as HTMLSelectElement).value)}
|
|
72
|
+
>
|
|
73
|
+
namedSlotRef
|
|
74
|
+
</select>
|
|
75
|
+
|
|
76
|
+
<h2 class="pkt-txt-22">Knapp som emitter en event</h2>
|
|
77
|
+
<button type="button" @click=${() => this.handleGreeting()}>
|
|
78
|
+
Si ${translations.example.hi}
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private renderList(list: string[]) {
|
|
85
|
+
return html`
|
|
86
|
+
<ul>
|
|
87
|
+
${list.map((i) => html`<li>${i}</li>`)}
|
|
88
|
+
</ul>
|
|
89
|
+
`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Add other functionality under here
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
private doStuff(val: string[]) {
|
|
97
|
+
return val.reverse()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Handlers for returning data from the component
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
private handleGreeting() {
|
|
105
|
+
this.dispatchEvent(
|
|
106
|
+
new CustomEvent('pkt-greeting', {
|
|
107
|
+
detail: 'Hei på deg!',
|
|
108
|
+
bubbles: true,
|
|
109
|
+
composed: true,
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LitElement } from 'lit'
|
|
2
|
+
import { Ref, createRef } from 'lit/directives/ref.js'
|
|
3
|
+
import { PktSlotController } from '@/controllers/pkt-slot-controller'
|
|
4
|
+
|
|
5
|
+
export class PktElement extends LitElement {
|
|
6
|
+
/**
|
|
7
|
+
* Runs on mount, used to set up various values and whatever you need to run
|
|
8
|
+
*/
|
|
9
|
+
connectedCallback() {
|
|
10
|
+
super.connectedCallback()
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Make sure the component renders in light DOM instead of shadow DOM
|
|
14
|
+
*/
|
|
15
|
+
createRenderRoot() {
|
|
16
|
+
return this
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Add support for Hot Module Reloading in dev mode
|
|
21
|
+
*/
|
|
22
|
+
hotReplacedCallback() {
|
|
23
|
+
this.requestUpdate()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Set up slot support for Light DOM
|
|
28
|
+
*/
|
|
29
|
+
slotController: PktSlotController
|
|
30
|
+
defaultSlot: Ref<HTMLElement> = createRef()
|
|
31
|
+
namedSlot: Ref<HTMLElement> = createRef()
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
super()
|
|
35
|
+
this.slotController = new PktSlotController(this, this.defaultSlot, this.namedSlot)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { LitElement, html } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js'
|
|
4
|
+
|
|
5
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
6
|
+
const errorSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"></svg>'
|
|
7
|
+
const dlStatus: { [key: string]: string } = {}
|
|
8
|
+
|
|
9
|
+
const downloadIconOrGetFromCache = async (name: string, path: string): Promise<string | null> => {
|
|
10
|
+
let i = 0
|
|
11
|
+
while (dlStatus[path + name + '.svg'] === 'fetching') {
|
|
12
|
+
i++
|
|
13
|
+
if (i > 50) break
|
|
14
|
+
await sleep(50)
|
|
15
|
+
}
|
|
16
|
+
if (localStorage.getItem(path + name + '.svg')) {
|
|
17
|
+
return Promise.resolve(localStorage.getItem(path + name + '.svg'))
|
|
18
|
+
} else if (typeof window.fetch === 'function') {
|
|
19
|
+
dlStatus[path + name + '.svg'] = 'fetching'
|
|
20
|
+
return Promise.resolve(
|
|
21
|
+
fetch(path + name + '.svg')
|
|
22
|
+
.then((response) => {
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.error('Missing icon: ' + path + name + '.svg')
|
|
26
|
+
return errorSvg
|
|
27
|
+
} else {
|
|
28
|
+
return response.text()
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
.then((text) => {
|
|
32
|
+
if (text !== errorSvg) {
|
|
33
|
+
localStorage.setItem(path + name + '.svg', text)
|
|
34
|
+
}
|
|
35
|
+
dlStatus[path + name + '.svg'] = 'fetched'
|
|
36
|
+
return text
|
|
37
|
+
}),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
return Promise.resolve(errorSvg)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@customElement('pkt-el-icon')
|
|
44
|
+
export class PktIcon extends LitElement {
|
|
45
|
+
/**
|
|
46
|
+
* Element attributes
|
|
47
|
+
*/
|
|
48
|
+
@property({ type: String, reflect: true }) name: string = ''
|
|
49
|
+
@property({ type: String, reflect: true }) path: string =
|
|
50
|
+
'https://punkt-cdn.oslo.kommune.no/latest/icons/'
|
|
51
|
+
|
|
52
|
+
@property({ type: SVGElement }) private icon: any = unsafeSVG(errorSvg)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Make sure the component renders in light DOM instead of shadow DOM
|
|
56
|
+
*/
|
|
57
|
+
createRenderRoot() {
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Add support for Hot Module Reloading in dev mode
|
|
63
|
+
*/
|
|
64
|
+
hotReplacedCallback() {
|
|
65
|
+
this.requestUpdate()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Runs on mount, used to set up various values and whatever you need to run
|
|
70
|
+
*/
|
|
71
|
+
async connectedCallback() {
|
|
72
|
+
if (this.name && this.path) {
|
|
73
|
+
this.icon = unsafeSVG(
|
|
74
|
+
await downloadIconOrGetFromCache(this.name, this.path).then((res) => res),
|
|
75
|
+
) as SVGElement
|
|
76
|
+
}
|
|
77
|
+
super.connectedCallback()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Runs every time a property changes
|
|
82
|
+
*/
|
|
83
|
+
async update(changedProperties: Map<string, unknown>) {
|
|
84
|
+
if (changedProperties.has('name')) {
|
|
85
|
+
if (this.name && this.path) {
|
|
86
|
+
this.icon = unsafeSVG(
|
|
87
|
+
await downloadIconOrGetFromCache(this.name, this.path).then((res) => res),
|
|
88
|
+
) as SVGElement
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
super.update(changedProperties)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Component functionality and render
|
|
96
|
+
*/
|
|
97
|
+
render() {
|
|
98
|
+
return html`${this.icon}`
|
|
99
|
+
}
|
|
100
|
+
}
|