@lekoala/slot-picker 0.1.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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +370 -0
  3. package/custom-elements.json +53 -0
  4. package/dist/slot-picker.css +597 -0
  5. package/dist/slot-picker.js +1382 -0
  6. package/dist/slot-picker.min.css +1 -0
  7. package/dist/slot-picker.min.js +36 -0
  8. package/dist/types/date.d.ts +23 -0
  9. package/dist/types/date.d.ts.map +1 -0
  10. package/dist/types/define.d.ts +2 -0
  11. package/dist/types/define.d.ts.map +1 -0
  12. package/dist/types/index.d.ts +6 -0
  13. package/dist/types/index.d.ts.map +1 -0
  14. package/dist/types/locales/ar.d.ts +20 -0
  15. package/dist/types/locales/ar.d.ts.map +1 -0
  16. package/dist/types/locales/de.d.ts +20 -0
  17. package/dist/types/locales/de.d.ts.map +1 -0
  18. package/dist/types/locales/en.d.ts +20 -0
  19. package/dist/types/locales/en.d.ts.map +1 -0
  20. package/dist/types/locales/es.d.ts +20 -0
  21. package/dist/types/locales/es.d.ts.map +1 -0
  22. package/dist/types/locales/fr.d.ts +20 -0
  23. package/dist/types/locales/fr.d.ts.map +1 -0
  24. package/dist/types/locales/hi.d.ts +20 -0
  25. package/dist/types/locales/hi.d.ts.map +1 -0
  26. package/dist/types/locales/id.d.ts +20 -0
  27. package/dist/types/locales/id.d.ts.map +1 -0
  28. package/dist/types/locales/it.d.ts +20 -0
  29. package/dist/types/locales/it.d.ts.map +1 -0
  30. package/dist/types/locales/ja.d.ts +20 -0
  31. package/dist/types/locales/ja.d.ts.map +1 -0
  32. package/dist/types/locales/ko.d.ts +20 -0
  33. package/dist/types/locales/ko.d.ts.map +1 -0
  34. package/dist/types/locales/nl.d.ts +20 -0
  35. package/dist/types/locales/nl.d.ts.map +1 -0
  36. package/dist/types/locales/pl.d.ts +20 -0
  37. package/dist/types/locales/pl.d.ts.map +1 -0
  38. package/dist/types/locales/pt-BR.d.ts +20 -0
  39. package/dist/types/locales/pt-BR.d.ts.map +1 -0
  40. package/dist/types/locales/pt-PT.d.ts +20 -0
  41. package/dist/types/locales/pt-PT.d.ts.map +1 -0
  42. package/dist/types/locales/ru.d.ts +20 -0
  43. package/dist/types/locales/ru.d.ts.map +1 -0
  44. package/dist/types/locales/tr.d.ts +20 -0
  45. package/dist/types/locales/tr.d.ts.map +1 -0
  46. package/dist/types/locales/zh-CN.d.ts +20 -0
  47. package/dist/types/locales/zh-CN.d.ts.map +1 -0
  48. package/dist/types/messages.d.ts +67 -0
  49. package/dist/types/messages.d.ts.map +1 -0
  50. package/dist/types/model.d.ts +213 -0
  51. package/dist/types/model.d.ts.map +1 -0
  52. package/dist/types/slot-picker.d.ts +182 -0
  53. package/dist/types/slot-picker.d.ts.map +1 -0
  54. package/dist/types/source.d.ts +45 -0
  55. package/dist/types/source.d.ts.map +1 -0
  56. package/dist/types/views/columns.d.ts +28 -0
  57. package/dist/types/views/columns.d.ts.map +1 -0
  58. package/dist/types/views/day.d.ts +27 -0
  59. package/dist/types/views/day.d.ts.map +1 -0
  60. package/dist/types/views/shared.d.ts +99 -0
  61. package/dist/types/views/shared.d.ts.map +1 -0
  62. package/docs/USE_CASES.md +197 -0
  63. package/package.json +148 -0
  64. package/src/date.js +66 -0
  65. package/src/define.js +5 -0
  66. package/src/index.js +25 -0
  67. package/src/locales/ar.js +18 -0
  68. package/src/locales/de.js +18 -0
  69. package/src/locales/en.js +18 -0
  70. package/src/locales/es.js +18 -0
  71. package/src/locales/fr.js +18 -0
  72. package/src/locales/hi.js +18 -0
  73. package/src/locales/id.js +18 -0
  74. package/src/locales/it.js +18 -0
  75. package/src/locales/ja.js +18 -0
  76. package/src/locales/ko.js +18 -0
  77. package/src/locales/nl.js +18 -0
  78. package/src/locales/pl.js +18 -0
  79. package/src/locales/pt-BR.js +18 -0
  80. package/src/locales/pt-PT.js +18 -0
  81. package/src/locales/ru.js +18 -0
  82. package/src/locales/tr.js +18 -0
  83. package/src/locales/zh-CN.js +18 -0
  84. package/src/messages.js +44 -0
  85. package/src/model.js +357 -0
  86. package/src/slot-picker.css +597 -0
  87. package/src/slot-picker.js +1094 -0
  88. package/src/source.js +87 -0
  89. package/src/views/columns.js +108 -0
  90. package/src/views/day.js +122 -0
  91. package/src/views/shared.js +157 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lekoala
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,370 @@
1
+ # @lekoala/slot-picker
2
+
3
+ A small, native-first Web Component for choosing appointment slots across a short civil date range.
4
+
5
+ It deliberately sits between `@lekoala/date-picker` and `@lekoala/calendar`:
6
+
7
+ - more structured than a date picker because it displays times;
8
+ - much smaller than a scheduler because it does not render events/resources;
9
+ - application-neutral: booking workflows stay outside.
10
+
11
+ ## Basic usage
12
+
13
+ ```html
14
+ <link rel="stylesheet" href="./dist/slot-picker.css">
15
+ <script src="./dist/slot-picker.js"></script>
16
+
17
+ <slot-picker id="slots" start="2026-11-17" day-count="5"></slot-picker>
18
+ <slot-picker id="day" start="2026-11-17" day-count="5" layout="day"></slot-picker>
19
+ ```
20
+
21
+ ```js
22
+ const picker = document.querySelector("#slots");
23
+
24
+ picker.days = [
25
+ {
26
+ date: "2026-11-17",
27
+ slots: [
28
+ { start: "13:35", end: "14:05" },
29
+ { start: "19:00", end: "19:30" }
30
+ ]
31
+ },
32
+ {
33
+ date: "2026-11-21",
34
+ slots: [],
35
+ notice: {
36
+ label: "Exceptionally unavailable",
37
+ description: "The practitioner is exceptionally unavailable that day."
38
+ }
39
+ }
40
+ ];
41
+
42
+ picker.addEventListener("slotactivate", ({ detail }) => {
43
+ console.log(detail.value, detail.slot);
44
+ });
45
+ ```
46
+
47
+ ## Remote source
48
+
49
+ ```js
50
+ picker.source = async ({ start, end, signal }) => {
51
+ const response = await fetch(`/availability?start=${start}&end=${end}`, { signal });
52
+ return response.json();
53
+ };
54
+ ```
55
+
56
+ A source may also be an object that additionally resolves the next known
57
+ availability beyond the visible range:
58
+
59
+ ```js
60
+ picker.source = {
61
+ load({ start, end, signal }) {
62
+ // availability for the visible window
63
+ },
64
+ async next({ after, signal }) {
65
+ // a date after `after`, or null
66
+ }
67
+ };
68
+
69
+ await picker.goToNextAvailability(); // -> source.next({ after: picker.range.end })
70
+ ```
71
+
72
+ Without `source.next`, `goToNextAvailability()` dispatches `nextrequest`
73
+ (`{ after }`) and returns `null`; the consumer then calls `picker.goTo(date)`.
74
+ When the source does answer, it resolves to the destination actually reached
75
+ (clamped to `min`/`max`), never the raw source proposal.
76
+ The source returns normalized day data. Backend query conventions and business
77
+ metadata stay outside core.
78
+
79
+ ## Navigation
80
+
81
+ ```html
82
+ <slot-picker
83
+ start="2026-11-17"
84
+ day-count="7"
85
+ responsive
86
+ min="2026-11-17"
87
+ max="2027-03-31"
88
+ home-date="2026-11-17"
89
+ ></slot-picker>
90
+ ```
91
+
92
+ - `day-count` is the maximum intention; `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
+ - `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.
94
+ - `home-date` is a reference date for `goHome()`, never confused with `min`.
95
+
96
+ ```js
97
+ picker.goTo("2026-12-08"); // bring a date into view and consult it
98
+ picker.goHome(); // reference window
99
+ picker.goToNextAvailability(); // next known availability (may skip windows)
100
+ picker.previous(); // adjacent window
101
+ picker.next();
102
+ picker.configure({ min: "2026-11-17", max: "2027-03-31", dayCount: 7, responsive: true, homeDate: "2026-11-17" });
103
+ ```
104
+
105
+ `configure()` is transactional: it validates every option before applying, so an
106
+ invalid option throws and leaves the component unchanged. A valid batch applies
107
+ as one pass: one reload and one final event pair, never one per setting. Event
108
+ order is stable: `rangechange`, then `daychange`, then any reload. Resizing and
109
+ navigating change the range but never `value`.
110
+
111
+ Navigation chrome is a symmetric `previous`/`next` pair framing the projection.
112
+ `home` is a persistent capability but not their mirror: it lives in an
113
+ auxiliary, start-aligned shortcut and only appears while `homeDate` is outside
114
+ the visible range, so it never clutters the initial window. There is no "go to
115
+ end". `goToNextAvailability()` is contextual and appears in the range empty
116
+ state when `next-availability` is set, because that is exactly where a real
117
+ business search makes sense. It is never merged with `next()`. The responsive
118
+ capacity is resolved from the projection's own width, inside the rail.
119
+
120
+ A window with no slot and no notice renders a range empty state
121
+ (`.sp-range-empty`) with a `next()` action, plus the availability action when
122
+ `next-availability` is set. A window with a notice but no slots keeps its
123
+ normal projection, so "exceptionally unavailable" stays distinct from
124
+ "no availability".
125
+
126
+ A day notice is never a fake slot. `notice-display` chooses how it projects:
127
+
128
+ - `action` (default) renders only a real control (the dot button) with an
129
+ accessible name; no inline text. It emits `noticeactivate` so the
130
+ application opens a modal, a popover or anything else.
131
+ - `inline` additionally renders the readable notice content: a compact label in
132
+ `columns` (description kept in the DOM, revealed by themes) and the full
133
+ label + description in `day`. The text itself is never interactive, and it
134
+ never overflows its column.
135
+ - `none` renders nothing; the application owns the notice elsewhere.
136
+
137
+ ```js
138
+ picker.addEventListener("noticeactivate", ({ detail }) => {
139
+ // detail = { day, notice, anchor }
140
+ openModal({ title: detail.notice.label, body: detail.notice.description });
141
+ // or: showPopover({ anchor: detail.anchor, ...detail.notice });
142
+ });
143
+ ```
144
+
145
+ `noticeactivate` bubbles, has no default behavior, and is only emitted from an
146
+ explicit control. The component never embeds a tooltip, popover or floating
147
+ engine.
148
+
149
+ ## Stable footprint and loading
150
+
151
+ Collapsed `columns` projection keeps a stable footprint derived from
152
+ `max-visible-rows`: a full, sparse or wholly empty range reserves the same
153
+ height, so `previous()`/`next()` never jerk the page. `expanded` content grows
154
+ past that floor. In `layout="day"`, only the empty state gets a baseline.
155
+
156
+ - `--sp-day-header-block-size` is a minimum, never a fixed height.
157
+ - `--sp-collapsed-rows` defaults to `max-visible-rows`.
158
+ - `--sp-collapsed-body-block-size`, `--sp-footer-block-size` and
159
+ `--sp-collapsed-block-size` describe the reserved area.
160
+ - `--sp-panel-min-block-size` is the day baseline.
161
+
162
+ When `home-date` is configured, the `.sp-shortcuts` wrapper stays in the DOM
163
+ even while `homeDate` is already visible, so the shortcut appearing never shifts
164
+ the page. Its reserved height is `--sp-shortcuts-block-size` (default `2.5rem`).
165
+
166
+ While collapsed, the "show more" control floats inside the reserved footer band
167
+ instead of adding a row: a gradient (`--sp-more-fade-size`, `--sp-more-bg`)
168
+ rises above it to suggest the availability that is not rendered. Hidden slots
169
+ stay out of the DOM. Expanding returns the control to normal flow, with no
170
+ overlay and no gradient.
171
+
172
+ Loading is signalled by `aria-busy="true"` on the host plus a CSS fade that only
173
+ starts after `--sp-loading-fade-delay` (default `400ms`), so fast responses
174
+ never flicker. `messages.loading` stays announced through a visually hidden
175
+ `role="status"`, so there is no layout shift. The fade honours
176
+ `prefers-reduced-motion` (delay kept, animation removed).
177
+
178
+ ## Locales
179
+
180
+ `Intl` already localizes weekday and month names from the component's `locale`
181
+ (or `lang`). Message packs only provide the UI strings:
182
+
183
+ ```js
184
+ import fr from "@lekoala/slot-picker/locales/fr";
185
+
186
+ picker.messages = fr;
187
+ ```
188
+
189
+ Or set a global default once:
190
+
191
+ ```js
192
+ import { setDefaultMessages } from "@lekoala/slot-picker";
193
+ import fr from "@lekoala/slot-picker/locales/fr";
194
+
195
+ setDefaultMessages(fr);
196
+ ```
197
+
198
+ Resolution order: `DEFAULT_MESSAGES`, then global defaults, then the instance
199
+ `messages`. Global defaults are resolved at render time, so
200
+ `setDefaultMessages()` also reaches instances that already exist on their next
201
+ render. Packs are never auto-imported, which keeps them tree-shakable.
202
+
203
+ Included packs: `ar`, `de`, `en`, `es`, `fr`, `hi`, `id`, `it`, `ja`, `ko`,
204
+ `nl`, `pl`, `pt-BR`, `pt-PT`, `ru`, `tr`, `zh-CN`.
205
+
206
+ These are provided message packs to review, not fully localized copy:
207
+ pluralization is intentionally simple (`oneSlot` / `manySlots` with `{n}`) and
208
+ does not use `Intl.PluralRules`.
209
+
210
+ Locale priority for formatting: `locale` attribute, then `lang`, then
211
+ `document.documentElement.lang`, then `navigator.language`. Only `locale`/`lang`
212
+ set on the component is observed; changing the document language does not
213
+ re-render existing pickers, and no global `MutationObserver` is used.
214
+
215
+ ## Data shape
216
+
217
+ ```js
218
+ {
219
+ date: "2026-11-17",
220
+ slots: [
221
+ {
222
+ start: "13:35",
223
+ end: "14:05",
224
+ disabled: false,
225
+ description: "Optional accessible detail",
226
+ tone: "video",
227
+ meta: {}
228
+ }
229
+ ],
230
+ closed: false,
231
+ notice: {
232
+ label: "Unavailable",
233
+ description: "Optional longer explanation",
234
+ meta: {}
235
+ }
236
+ }
237
+ ```
238
+
239
+ Dates are civil `YYYY-MM-DD`, times are `HH:mm`, and the selected value is
240
+ `YYYY-MM-DDTHH:mm`. No timezone conversion is performed. The `value` property
241
+ setter rejects an impossible civil date such as `2026-02-31T10:00`; a malformed
242
+ `value` attribute is ignored and reads as empty, so imperfect markup never
243
+ breaks the element upgrade.
244
+
245
+ ### Open, empty and closed days
246
+
247
+ Three cases stay distinct:
248
+
249
+ - slots present → normal availability;
250
+ - `slots: []`, open → `messages.empty` ("No availability");
251
+ - `slots: []`, `closed: true` → `messages.closed` ("Closed").
252
+
253
+ `closed` is a normal day state set by the application (weekend, weekly
254
+ closure); the component computes no opening hours. Closed days stay in the
255
+ civil window and are stylable through `[data-closed]`:
256
+
257
+ ```js
258
+ picker.days = [{ date: "2026-11-22", slots: [], closed: true }];
259
+ ```
260
+
261
+ ```css
262
+ slot-picker .sp-day[data-closed] {
263
+ --sp-closed-opacity: 0.4;
264
+ }
265
+ ```
266
+
267
+ `notice` remains reserved for exceptional information and may coexist with
268
+ `closed`. A closed day carrying slots is invalid input and is rejected by
269
+ normalization.
270
+
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
+ To jump between windows by real availability, let the source expose `next()`
292
+ and call `goToNextAvailability()`; the civil shape of a window never changes.
293
+
294
+ ## Per-slot presentation
295
+
296
+ `slot.tone` is a neutral presentation token. The component never interprets it:
297
+ it is only surfaced as `data-tone`, and the core ships no palette.
298
+
299
+ ```js
300
+ picker.days = [{ date: "2026-11-17", slots: [{ start: "13:35", tone: "video" }] }];
301
+ ```
302
+
303
+ ```html
304
+ <!-- rendered -->
305
+ <button type="button" role="option" class="sp-slot" data-tone="video" ...>13:35</button>
306
+ ```
307
+
308
+ Map tones to color with the dedicated slot tokens, so a tone never touches the
309
+ focus ring (`--sp-focus`) or the rest of the picker:
310
+
311
+ ```css
312
+ slot-picker .sp-slot[data-tone="video"] {
313
+ --sp-slot-bg: #e0f2fe;
314
+ --sp-slot-fg: #075985;
315
+ }
316
+ ```
317
+
318
+ The slot surface is `--sp-slot-bg`, `--sp-slot-fg`, `--sp-slot-border`,
319
+ `--sp-slot-hover-bg`, `--sp-slot-selected-bg`, `--sp-slot-selected-fg`.
320
+
321
+ An icon next to the time is theme-owned too. `::after` needs
322
+ `grid-auto-flow: column`, because `.sp-slot` centers its content with a grid:
323
+
324
+ ```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 {
331
+ content: "⚡";
332
+ font-size: 0.85em;
333
+ line-height: 1;
334
+ }
335
+ ```
336
+
337
+ A generated glyph can enter the accessible name ("13:35 ⚡"): keep the meaning
338
+ in `slot.description`, or use `content: ""` plus a background/mask when the
339
+ name must stay purely the time.
340
+
341
+ Accessible detail stays on `slot.description` (`aria-description`). The
342
+ component deliberately ships no tooltip or popover engine (see U4 in
343
+ `docs/USE_CASES.md`): a hover/focus tooltip is application-owned. Because the
344
+ DOM is light, an application can delegate on `.sp-slot` and read
345
+ `data-value`/`data-tone` for advanced enrichments, but the primary workflow
346
+ only needs `slotactivate`.
347
+
348
+ ## Events
349
+
350
+ - `rangechange` — the visible civil range changed.
351
+ - `daychange` — the consulted day (`activeDate`) changed; never selects a slot.
352
+ - `slotactivate` — the user explicitly chose a slot (sets `activeDate` first); bubbles.
353
+ - `noticeactivate` — the user activated a day notice control (bubbles, detail `{ day, notice, anchor }`, no default behavior).
354
+ - `nextrequest` — no `source.next` was available; detail is `{ after }`.
355
+ - `loadstart` / `loadend` / `loaderror` — optional remote-source lifecycle.
356
+
357
+ See `docs/USE_CASES.md` for the product contract.
358
+
359
+ ## Demo
360
+
361
+ The demo page points at the built artifacts, so no web server is required:
362
+
363
+ ```bash
364
+ bun run build
365
+ ```
366
+
367
+ Then open `demo/index.html` directly, or serve it with `bun run dev`.
368
+ It shows both `columns` and `day` projections, wide and in narrow
369
+ phone-like containers, plus bounded/responsive navigation and the range empty
370
+ state.
@@ -0,0 +1,53 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "README.md",
4
+ "modules": [
5
+ {
6
+ "kind": "javascript-module",
7
+ "path": "src/slot-picker.js",
8
+ "declarations": [
9
+ {
10
+ "kind": "class",
11
+ "name": "SlotPickerElement",
12
+ "tagName": "slot-picker",
13
+ "description": "Inline appointment slot chooser.",
14
+ "attributes": [
15
+ { "name": "start", "type": { "text": "string" } },
16
+ { "name": "day-count", "type": { "text": "number" } },
17
+ { "name": "min", "type": { "text": "string" } },
18
+ { "name": "max", "type": { "text": "string" } },
19
+ { "name": "value", "type": { "text": "string" } },
20
+ { "name": "active-date", "type": { "text": "string" } },
21
+ { "name": "layout", "type": { "text": "string" } },
22
+ { "name": "max-visible-rows", "type": { "text": "number" } },
23
+ { "name": "expanded", "type": { "text": "boolean" } },
24
+ { "name": "responsive", "type": { "text": "boolean" } },
25
+ { "name": "home-date", "type": { "text": "string" } },
26
+ { "name": "next-availability", "type": { "text": "boolean" } },
27
+ {
28
+ "name": "notice-display",
29
+ "type": { "text": "\"inline\" | \"action\" | \"none\"" },
30
+ "default": "action"
31
+ },
32
+ {
33
+ "name": "closed-days",
34
+ "type": { "text": "\"show\" | \"hide\"" },
35
+ "default": "show"
36
+ },
37
+ { "name": "locale", "type": { "text": "string" } }
38
+ ],
39
+ "events": [
40
+ { "name": "rangechange" },
41
+ { "name": "daychange" },
42
+ { "name": "slotactivate" },
43
+ { "name": "noticeactivate" },
44
+ { "name": "nextrequest" },
45
+ { "name": "loadstart" },
46
+ { "name": "loadend" },
47
+ { "name": "loaderror" }
48
+ ]
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }