@lekoala/slot-picker 0.1.0 → 0.1.2
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/README.md +85 -39
- package/custom-elements.json +0 -5
- package/dist/slot-picker.css +22 -0
- package/dist/slot-picker.js +160 -134
- package/dist/slot-picker.min.css +1 -1
- package/dist/slot-picker.min.js +24 -24
- package/dist/types/date.d.ts +8 -1
- package/dist/types/date.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/locales/ar.d.ts +0 -1
- package/dist/types/locales/ar.d.ts.map +1 -1
- package/dist/types/locales/de.d.ts +0 -1
- package/dist/types/locales/de.d.ts.map +1 -1
- package/dist/types/locales/en.d.ts +0 -1
- package/dist/types/locales/en.d.ts.map +1 -1
- package/dist/types/locales/es.d.ts +0 -1
- package/dist/types/locales/es.d.ts.map +1 -1
- package/dist/types/locales/fr.d.ts +0 -1
- package/dist/types/locales/fr.d.ts.map +1 -1
- package/dist/types/locales/hi.d.ts +0 -1
- package/dist/types/locales/hi.d.ts.map +1 -1
- package/dist/types/locales/id.d.ts +0 -1
- package/dist/types/locales/id.d.ts.map +1 -1
- package/dist/types/locales/it.d.ts +0 -1
- package/dist/types/locales/it.d.ts.map +1 -1
- package/dist/types/locales/ja.d.ts +0 -1
- package/dist/types/locales/ja.d.ts.map +1 -1
- package/dist/types/locales/ko.d.ts +0 -1
- package/dist/types/locales/ko.d.ts.map +1 -1
- package/dist/types/locales/nl.d.ts +0 -1
- package/dist/types/locales/nl.d.ts.map +1 -1
- package/dist/types/locales/pl.d.ts +0 -1
- package/dist/types/locales/pl.d.ts.map +1 -1
- package/dist/types/locales/pt-BR.d.ts +0 -1
- package/dist/types/locales/pt-BR.d.ts.map +1 -1
- package/dist/types/locales/pt-PT.d.ts +0 -1
- package/dist/types/locales/pt-PT.d.ts.map +1 -1
- package/dist/types/locales/ru.d.ts +0 -1
- package/dist/types/locales/ru.d.ts.map +1 -1
- package/dist/types/locales/tr.d.ts +0 -1
- package/dist/types/locales/tr.d.ts.map +1 -1
- package/dist/types/locales/zh-CN.d.ts +0 -1
- package/dist/types/locales/zh-CN.d.ts.map +1 -1
- package/dist/types/messages.d.ts +0 -3
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/model.d.ts +58 -23
- package/dist/types/model.d.ts.map +1 -1
- package/dist/types/slot-picker.d.ts +25 -15
- package/dist/types/slot-picker.d.ts.map +1 -1
- package/dist/types/views/shared.d.ts +5 -4
- package/dist/types/views/shared.d.ts.map +1 -1
- package/docs/USE_CASES.md +24 -18
- package/package.json +1 -1
- package/src/date.js +11 -1
- package/src/index.js +13 -2
- package/src/locales/ar.js +0 -1
- package/src/locales/de.js +0 -1
- package/src/locales/en.js +0 -1
- package/src/locales/es.js +0 -1
- package/src/locales/fr.js +0 -1
- package/src/locales/hi.js +0 -1
- package/src/locales/id.js +0 -1
- package/src/locales/it.js +0 -1
- package/src/locales/ja.js +0 -1
- package/src/locales/ko.js +0 -1
- package/src/locales/nl.js +0 -1
- package/src/locales/pl.js +0 -1
- package/src/locales/pt-BR.js +0 -1
- package/src/locales/pt-PT.js +0 -1
- package/src/locales/ru.js +0 -1
- package/src/locales/tr.js +0 -1
- package/src/locales/zh-CN.js +0 -1
- package/src/messages.js +0 -1
- package/src/model.js +149 -55
- package/src/slot-picker.css +22 -0
- package/src/slot-picker.js +123 -100
- package/src/views/shared.js +10 -7
package/README.md
CHANGED
|
@@ -53,6 +53,11 @@ picker.source = async ({ start, end, signal }) => {
|
|
|
53
53
|
};
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
`start` and `end` are the inclusive civil envelope of the visible window. With
|
|
57
|
+
`hiddenDays` set it can be wider than `dayCount`, so load the whole envelope and
|
|
58
|
+
let the component pick the columns; days outside it are ignored. No adaptive
|
|
59
|
+
fetching is ever required: the envelope is known before the request.
|
|
60
|
+
|
|
56
61
|
A source may also be an object that additionally resolves the next known
|
|
57
62
|
availability beyond the visible range:
|
|
58
63
|
|
|
@@ -89,10 +94,16 @@ metadata stay outside core.
|
|
|
89
94
|
></slot-picker>
|
|
90
95
|
```
|
|
91
96
|
|
|
92
|
-
- `day-count` is the maximum intention
|
|
97
|
+
- `day-count` is the maximum intention, counted in **columns**; `visibleDayCount` is the capacity actually resolved from `min`/`max` and the component's own width (opt-in via `responsive`, overridable with `responsiveBreakpoints`, observed with `ResizeObserver`).
|
|
93
98
|
- `min`/`max` are hard bounds. `min > max` is an invalid configuration: `range` becomes `{ start: "", end: "", dayCount: 0 }`, `data-invalid-range` is set, and the source is never called.
|
|
99
|
+
- `hiddenDays` removes weekdays from the calendar structure; it never costs a column.
|
|
94
100
|
- `home-date` is a reference date for `goHome()`, never confused with `min`.
|
|
95
101
|
|
|
102
|
+
At equal width and bounds the picker keeps its column capacity: only `min`/`max`
|
|
103
|
+
can really reduce it, and only when the interval itself holds too few
|
|
104
|
+
projectable days. `previous()`/`next()` move by projected days, so the count of
|
|
105
|
+
columns never changes as you navigate.
|
|
106
|
+
|
|
96
107
|
```js
|
|
97
108
|
picker.goTo("2026-12-08"); // bring a date into view and consult it
|
|
98
109
|
picker.goHome(); // reference window
|
|
@@ -242,17 +253,60 @@ setter rejects an impossible civil date such as `2026-02-31T10:00`; a malformed
|
|
|
242
253
|
`value` attribute is ignored and reads as empty, so imperfect markup never
|
|
243
254
|
breaks the element upgrade.
|
|
244
255
|
|
|
245
|
-
###
|
|
256
|
+
### Hidden days, closed days, empty days
|
|
257
|
+
|
|
258
|
+
Three separate ideas, deliberately not merged:
|
|
259
|
+
|
|
260
|
+
| | what it is | column? |
|
|
261
|
+
| --- | --- | --- |
|
|
262
|
+
| `hiddenDays` | calendar structure, known before loading | never rendered |
|
|
263
|
+
| `closed: true` | business state of a real date | rendered, says `Closed` |
|
|
264
|
+
| `slots: []` | open date with nothing bookable | rendered, says `No availability` |
|
|
246
265
|
|
|
247
|
-
|
|
266
|
+
#### hiddenDays
|
|
248
267
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
268
|
+
```js
|
|
269
|
+
picker.hiddenDays = [0, 6]; // Sunday and Saturday are never a column
|
|
270
|
+
```
|
|
252
271
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
civil
|
|
272
|
+
Weekday indexes match `Date#getDay` (0 = Sunday). Because the rule is known
|
|
273
|
+
before any request, `dayCount` keeps meaning "columns": a hidden weekday widens
|
|
274
|
+
the civil envelope instead of eating a column.
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
picker.configure({ start: "2026-11-19", dayCount: 5, hiddenDays: [0, 6] });
|
|
278
|
+
picker.range; // { start: "2026-11-19", end: "2026-11-25", dayCount: 5 }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Five columns (Thu, Fri, Mon, Tue, Wed) over a seven-day envelope; `source.load()`
|
|
282
|
+
receives `2026-11-19` to `2026-11-25`. `previous()`/`next()` step by five
|
|
283
|
+
projected days, so the grid never changes shape. A `start` landing on a hidden
|
|
284
|
+
weekday resolves forward to the first projected day, and `activeDate` resolves
|
|
285
|
+
to the next projected column. It is a property, like `days` and `source`, with no
|
|
286
|
+
matching attribute; `configure({ hiddenDays })` applies it inside a transaction.
|
|
287
|
+
Anything but integers `0`–`6` throws, and hiding all seven weekdays is rejected.
|
|
288
|
+
|
|
289
|
+
This is what makes a sparse weekly schedule usable. A practitioner working only
|
|
290
|
+
Tuesdays and Thursdays is calendar structure, not availability:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
picker.configure({ dayCount: 5, hiddenDays: [0, 1, 3, 5, 6] });
|
|
294
|
+
picker.range.dayCount; // 5 columns: Tue, Thu, Tue, Thu, Tue
|
|
295
|
+
picker.range; // spanning about three weeks of civil time
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Five columns are five real opening days, instead of a grid mostly made of
|
|
299
|
+
closed ones, and `next()` moves to the five following opening days. Without
|
|
300
|
+
`hiddenDays` the same consumer would have to page through weeks to find two
|
|
301
|
+
bookable columns.
|
|
302
|
+
|
|
303
|
+
Combined with `responsive`, this is what makes a narrow screen usable.
|
|
304
|
+
`day-count` is a ceiling, and the width resolves it: a phone may only fit two
|
|
305
|
+
columns, but they are two real opening days rather than two arbitrary civil
|
|
306
|
+
days. Width, bounds and `hiddenDays` are all resolved before the request, so the
|
|
307
|
+
capacity is known before any data arrives and a response can never change it.
|
|
308
|
+
|
|
309
|
+
#### closed
|
|
256
310
|
|
|
257
311
|
```js
|
|
258
312
|
picker.days = [{ date: "2026-11-22", slots: [], closed: true }];
|
|
@@ -264,32 +318,18 @@ slot-picker .sp-day[data-closed] {
|
|
|
264
318
|
}
|
|
265
319
|
```
|
|
266
320
|
|
|
321
|
+
`closed` is set by the application (a holiday, an exceptional closure); the
|
|
322
|
+
component computes no opening hours and never derives a recurrence from the
|
|
323
|
+
data. It is a rendered state, exactly like `notice`: loaded data can never add
|
|
324
|
+
or remove a column, so a closed day keeps its place and explains why there is
|
|
325
|
+
nothing that day. A window made only of closed days still renders its columns.
|
|
326
|
+
|
|
267
327
|
`notice` remains reserved for exceptional information and may coexist with
|
|
268
328
|
`closed`. A closed day carrying slots is invalid input and is rejected by
|
|
269
329
|
normalization.
|
|
270
330
|
|
|
271
|
-
`closed-days="hide"` drops closed days from the projection without changing the
|
|
272
|
-
civil range:
|
|
273
|
-
|
|
274
|
-
```html
|
|
275
|
-
<slot-picker closed-days="hide" start="2026-11-21" day-count="5"></slot-picker>
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
```js
|
|
279
|
-
picker.range; // still { start: "2026-11-21", end: "2026-11-25", dayCount: 5 }
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
Hiding a closed day does not extend the civil range to compensate for the
|
|
283
|
-
hidden column: `day-count="5"` with two closed days renders three columns.
|
|
284
|
-
`range`, `visibleDayCount`, `previous()`/`next()`, `goTo()` and `source.load()`
|
|
285
|
-
stay civil, and toggling `closed-days` never reloads the source. In
|
|
286
|
-
`layout="day"`, a closed day that is the consulted `activeDate` stays visible
|
|
287
|
-
until another day is activated, so data arrival never moves `activeDate`. A
|
|
288
|
-
window that is closed in its entirety renders the range empty state with
|
|
289
|
-
`messages.closedRange`.
|
|
290
|
-
|
|
291
331
|
To jump between windows by real availability, let the source expose `next()`
|
|
292
|
-
and call `goToNextAvailability()`; the
|
|
332
|
+
and call `goToNextAvailability()`; the shape of a window never changes.
|
|
293
333
|
|
|
294
334
|
## Per-slot presentation
|
|
295
335
|
|
|
@@ -302,7 +342,7 @@ picker.days = [{ date: "2026-11-17", slots: [{ start: "13:35", tone: "video" }]
|
|
|
302
342
|
|
|
303
343
|
```html
|
|
304
344
|
<!-- rendered -->
|
|
305
|
-
<button type="button" role="option" class="sp-slot" data-tone="video"
|
|
345
|
+
<button type="button" role="option" class="sp-slot" data-tone="video" ...><span class="sp-slot-time">13:35</span></button>
|
|
306
346
|
```
|
|
307
347
|
|
|
308
348
|
Map tones to color with the dedicated slot tokens, so a tone never touches the
|
|
@@ -318,17 +358,16 @@ slot-picker .sp-slot[data-tone="video"] {
|
|
|
318
358
|
The slot surface is `--sp-slot-bg`, `--sp-slot-fg`, `--sp-slot-border`,
|
|
319
359
|
`--sp-slot-hover-bg`, `--sp-slot-selected-bg`, `--sp-slot-selected-fg`.
|
|
320
360
|
|
|
321
|
-
An icon next to the time is theme-owned too.
|
|
322
|
-
|
|
361
|
+
An icon next to the time is theme-owned too. The time lives in a
|
|
362
|
+
`.sp-slot-time` inline-flex wrapper, so append the glyph to that wrapper with a
|
|
363
|
+
`::after` and a logical margin: it stays in flow, thus really adjacent to the
|
|
364
|
+
time, and readjusts in RTL. The slot is width-clamped (`min-inline-size: 0;
|
|
365
|
+
max-inline-size: 100%`), so the icon never widens the day column.
|
|
323
366
|
|
|
324
367
|
```css
|
|
325
|
-
slot-picker .sp-slot[data-tone="instant"] {
|
|
326
|
-
grid-auto-flow: column;
|
|
327
|
-
justify-content: center;
|
|
328
|
-
gap: 0.35rem;
|
|
329
|
-
}
|
|
330
|
-
slot-picker .sp-slot[data-tone="instant"]::after {
|
|
368
|
+
slot-picker .sp-slot[data-tone="instant"] .sp-slot-time::after {
|
|
331
369
|
content: "⚡";
|
|
370
|
+
margin-inline-start: 0.35rem;
|
|
332
371
|
font-size: 0.85em;
|
|
333
372
|
line-height: 1;
|
|
334
373
|
}
|
|
@@ -368,3 +407,10 @@ Then open `demo/index.html` directly, or serve it with `bun run dev`.
|
|
|
368
407
|
It shows both `columns` and `day` projections, wide and in narrow
|
|
369
408
|
phone-like containers, plus bounded/responsive navigation and the range empty
|
|
370
409
|
state.
|
|
410
|
+
|
|
411
|
+
The same page is published from `master` at
|
|
412
|
+
<https://lekoala.github.io/slot-picker/> (GitHub Pages serves the repository
|
|
413
|
+
root and redirects to `demo/`). Pages publishes the committed `dist/` bundles,
|
|
414
|
+
so run `bun run build:bundle` and commit `dist/slot-picker.js` /
|
|
415
|
+
`dist/slot-picker.css` whenever `src/` changes; CI rejects a stale bundle.
|
|
416
|
+
|
package/custom-elements.json
CHANGED
|
@@ -29,11 +29,6 @@
|
|
|
29
29
|
"type": { "text": "\"inline\" | \"action\" | \"none\"" },
|
|
30
30
|
"default": "action"
|
|
31
31
|
},
|
|
32
|
-
{
|
|
33
|
-
"name": "closed-days",
|
|
34
|
-
"type": { "text": "\"show\" | \"hide\"" },
|
|
35
|
-
"default": "show"
|
|
36
|
-
},
|
|
37
32
|
{ "name": "locale", "type": { "text": "string" } }
|
|
38
33
|
],
|
|
39
34
|
"events": [
|
package/dist/slot-picker.css
CHANGED
|
@@ -56,6 +56,11 @@
|
|
|
56
56
|
font: inherit;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/* The component renders in light DOM, so a page stylesheet can reach these
|
|
60
|
+
declarations. `!important` is what keeps assistive-technology-only text (the
|
|
61
|
+
loading status, the day strip's spoken labels) out of sight: losing it is an
|
|
62
|
+
accessibility regression, not a cosmetic one. */
|
|
63
|
+
/* biome-ignore-start lint/complexity/noImportantStyles: visually-hidden must win over page styles */
|
|
59
64
|
.sp-visually-hidden {
|
|
60
65
|
position: absolute !important;
|
|
61
66
|
inline-size: 1px !important;
|
|
@@ -67,6 +72,7 @@
|
|
|
67
72
|
white-space: nowrap !important;
|
|
68
73
|
border: 0 !important;
|
|
69
74
|
}
|
|
75
|
+
/* biome-ignore-end lint/complexity/noImportantStyles: visually-hidden must win over page styles */
|
|
70
76
|
|
|
71
77
|
.sp-shell {
|
|
72
78
|
display: block;
|
|
@@ -214,6 +220,9 @@ slot-picker[home-date] .sp-shortcuts {
|
|
|
214
220
|
|
|
215
221
|
.sp-slots {
|
|
216
222
|
display: grid;
|
|
223
|
+
/* Bound the single column to the day column: an `auto` track follows the
|
|
224
|
+
widest slot's min-content and would push every slot past the day card. */
|
|
225
|
+
grid-template-columns: minmax(0, 1fr);
|
|
217
226
|
gap: var(--sp-gap);
|
|
218
227
|
}
|
|
219
228
|
|
|
@@ -225,6 +234,19 @@ slot-picker[home-date] .sp-shortcuts {
|
|
|
225
234
|
box-sizing: border-box;
|
|
226
235
|
border-radius: var(--sp-radius);
|
|
227
236
|
font: inherit;
|
|
237
|
+
/* A grid item defaults to `min-width: auto`, i.e. its min-content; that would
|
|
238
|
+
let a wide slot (a theme icon next to the time) push the bounded day column.
|
|
239
|
+
Clamp the button to its track instead; the content stays centered. */
|
|
240
|
+
min-inline-size: 0;
|
|
241
|
+
max-inline-size: 100%;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Time wrapper: inline-flex so a theme can append an in-flow icon (`::after`)
|
|
245
|
+
that stays adjacent to the time and vertically centered. */
|
|
246
|
+
.sp-slot-time {
|
|
247
|
+
display: inline-flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
justify-content: center;
|
|
228
250
|
}
|
|
229
251
|
|
|
230
252
|
.sp-slot {
|