@surdeddd/bottom-sheet 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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/dist/BottomSheetEngine-CvSVbo6c.d.cts +704 -0
  4. package/dist/BottomSheetEngine-DbO8rW_3.d.ts +704 -0
  5. package/dist/element.cjs +3442 -0
  6. package/dist/element.d.cts +88 -0
  7. package/dist/element.d.ts +88 -0
  8. package/dist/element.iife.global.js +3 -0
  9. package/dist/element.js +3442 -0
  10. package/dist/index.cjs +3693 -0
  11. package/dist/index.d.cts +184 -0
  12. package/dist/index.d.ts +184 -0
  13. package/dist/index.js +3669 -0
  14. package/dist/integrations/formik.cjs +27 -0
  15. package/dist/integrations/formik.d.cts +27 -0
  16. package/dist/integrations/formik.d.ts +27 -0
  17. package/dist/integrations/formik.js +25 -0
  18. package/dist/integrations/react-hook-form.cjs +28 -0
  19. package/dist/integrations/react-hook-form.d.cts +30 -0
  20. package/dist/integrations/react-hook-form.d.ts +30 -0
  21. package/dist/integrations/react-hook-form.js +26 -0
  22. package/dist/overlay.cjs +1058 -0
  23. package/dist/overlay.d.cts +282 -0
  24. package/dist/overlay.d.ts +282 -0
  25. package/dist/overlay.js +1053 -0
  26. package/dist/preact.cjs +2 -0
  27. package/dist/preact.d.cts +5 -0
  28. package/dist/preact.d.ts +5 -0
  29. package/dist/preact.js +2 -0
  30. package/dist/qwik.cjs +3046 -0
  31. package/dist/qwik.d.cts +55 -0
  32. package/dist/qwik.d.ts +55 -0
  33. package/dist/qwik.js +3044 -0
  34. package/dist/react.cjs +4162 -0
  35. package/dist/react.d.cts +301 -0
  36. package/dist/react.d.ts +301 -0
  37. package/dist/react.js +4156 -0
  38. package/dist/solid.cjs +3063 -0
  39. package/dist/solid.d.cts +66 -0
  40. package/dist/solid.d.ts +66 -0
  41. package/dist/solid.js +3061 -0
  42. package/dist/styles.css +508 -0
  43. package/dist/svelte.cjs +3200 -0
  44. package/dist/svelte.d.ts +89 -0
  45. package/dist/svelte.js +3177 -0
  46. package/dist/themes/ios.css +24 -0
  47. package/dist/themes/material.css +24 -0
  48. package/dist/themes/vercel.css +24 -0
  49. package/dist/transform-Brkf-wzo.d.cts +4 -0
  50. package/dist/transform-Brkf-wzo.d.ts +4 -0
  51. package/dist/vue.cjs +3361 -0
  52. package/dist/vue.d.ts +68 -0
  53. package/dist/vue.js +3358 -0
  54. package/package.json +288 -0
@@ -0,0 +1,508 @@
1
+ /* =====================================================================
2
+ * @surdeddd/bottom-sheet — base styles
3
+ * Override any token below to retheme. All sizes use logical defaults
4
+ * so the same stylesheet works for bottom / top / left / right modes.
5
+ * ===================================================================== */
6
+
7
+ :where(.bs-root) {
8
+ /* surface */
9
+ --bs-surface: #ffffff;
10
+ --bs-surface-border: rgba(225, 227, 237, 1);
11
+ --bs-shadow: 0 -3px 12px rgba(0, 0, 0, 0.12);
12
+ --bs-radius: 20px;
13
+
14
+ /* handle — WCAG 1.4.11 contrast against light surface ≥ 3:1 */
15
+ --bs-handle-color: #b5b3a8;
16
+ --bs-handle-width: 40px;
17
+ --bs-handle-height: 5px;
18
+
19
+ /* layout */
20
+ --bs-z-backdrop: 90;
21
+ --bs-z-sheet: 100;
22
+ --bs-header-min-height: 80px;
23
+
24
+ /* backdrop */
25
+ --bs-backdrop-color: rgba(0, 0, 0, 0.5);
26
+
27
+ /* scrim — dimming layer for the area NOT occupied by the sheet
28
+ (used with scrimMode: "above-sheet" or the .bs-scrim* recipes below).
29
+ Override these to retheme without rewriting the recipe. */
30
+ --bs-scrim-color: rgba(0, 0, 0, 0.4);
31
+ --bs-scrim-blur: 0px;
32
+ --bs-z-scrim: calc(var(--bs-z-sheet) - 1);
33
+
34
+ /* engine writes these — read them in your own CSS to react to drag */
35
+ --bs-size: 0px;
36
+ --bs-progress: 0;
37
+ }
38
+
39
+ .bs-backdrop {
40
+ position: fixed;
41
+ inset: 0;
42
+ background: var(--bs-backdrop-color);
43
+ opacity: 0;
44
+ pointer-events: none;
45
+ z-index: var(--bs-z-backdrop);
46
+ /* Transition only when NOT actively dragging/animating — engine writes
47
+ opacity per-frame and the transition would delay it by 150ms. */
48
+ transition: opacity 0.15s linear;
49
+ will-change: opacity;
50
+ }
51
+ .bs-root[data-dragging="true"] .bs-backdrop,
52
+ .bs-root[data-animating="true"] .bs-backdrop {
53
+ transition: none;
54
+ }
55
+
56
+ /* ----- sheet ----- */
57
+ .bs-sheet {
58
+ position: fixed;
59
+ background: var(--bs-surface);
60
+ box-shadow: var(--bs-shadow);
61
+ z-index: var(--bs-z-sheet);
62
+ overflow: hidden;
63
+ display: flex;
64
+ flex-direction: column;
65
+ /* engine sets touch-action on the handle directly; this is for clicks. */
66
+ -webkit-tap-highlight-color: transparent;
67
+ user-select: none;
68
+ overscroll-behavior: contain;
69
+ /* engine drives the size via transform translate3d. */
70
+ }
71
+
72
+ .bs-sheet[data-mode="bottom"] {
73
+ left: 0;
74
+ right: 0;
75
+ bottom: 0;
76
+ width: 100%;
77
+ border-top-left-radius: var(--bs-radius);
78
+ border-top-right-radius: var(--bs-radius);
79
+ border-top: 1px solid var(--bs-surface-border);
80
+ /* iPhone home-indicator safe area. */
81
+ padding-bottom: env(safe-area-inset-bottom);
82
+ /* Progress-driven shadow depth — deeper as the sheet expands. */
83
+ box-shadow: 0
84
+ calc(var(--bs-progress, 0) * -2px - 3px)
85
+ calc(var(--bs-progress, 0) * 18px + 12px)
86
+ rgba(0, 0, 0, calc(var(--bs-progress, 0) * 0.15 + 0.12));
87
+ }
88
+ .bs-sheet[data-mode="top"] {
89
+ left: 0;
90
+ right: 0;
91
+ top: 0;
92
+ width: 100%;
93
+ border-bottom-left-radius: var(--bs-radius);
94
+ border-bottom-right-radius: var(--bs-radius);
95
+ border-bottom: 1px solid var(--bs-surface-border);
96
+ flex-direction: column-reverse;
97
+ box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
98
+ padding-top: env(safe-area-inset-top);
99
+ }
100
+ .bs-sheet[data-mode="left"] {
101
+ top: 0;
102
+ bottom: 0;
103
+ left: 0;
104
+ height: 100%;
105
+ border-top-right-radius: var(--bs-radius);
106
+ border-bottom-right-radius: var(--bs-radius);
107
+ border-right: 1px solid var(--bs-surface-border);
108
+ /* default flex layout (column) — handle is positioned-absolute at the
109
+ open edge (right) so column flow doesn't matter for it */
110
+ box-shadow: 3px 0 12px rgba(0, 0, 0, 0.12);
111
+ }
112
+ .bs-sheet[data-mode="right"] {
113
+ top: 0;
114
+ bottom: 0;
115
+ right: 0;
116
+ height: 100%;
117
+ border-top-left-radius: var(--bs-radius);
118
+ border-bottom-left-radius: var(--bs-radius);
119
+ border-left: 1px solid var(--bs-surface-border);
120
+ box-shadow: -3px 0 12px rgba(0, 0, 0, 0.12);
121
+ }
122
+ /* In side modes, content sits inside the sheet but with padding to clear
123
+ the absolutely-positioned handle strip on the open edge. */
124
+ .bs-sheet[data-mode="left"] .bs-content {
125
+ padding-right: 36px;
126
+ }
127
+ .bs-sheet[data-mode="right"] .bs-content {
128
+ padding-left: 36px;
129
+ }
130
+
131
+ /* ----- handle ----- */
132
+ .bs-handle {
133
+ flex-shrink: 0;
134
+ /* WCAG 2.5.5 / Apple HIG / Material — 44px minimum hit target. */
135
+ min-height: 44px;
136
+ display: flex;
137
+ flex-direction: column;
138
+ justify-content: center;
139
+ padding: 8px 16px 4px;
140
+ cursor: grab;
141
+ touch-action: none;
142
+ user-select: none;
143
+ background: var(--bs-surface);
144
+ -webkit-tap-highlight-color: transparent;
145
+ outline: none;
146
+ /* anchor point for the optional .bs-handle--ticks ::after dots */
147
+ position: relative;
148
+ }
149
+ .bs-handle:active {
150
+ cursor: grabbing;
151
+ }
152
+ .bs-handle:focus-visible {
153
+ outline: 2px solid currentColor;
154
+ outline-offset: -2px;
155
+ border-radius: var(--bs-radius);
156
+ }
157
+
158
+ .bs-handle::before {
159
+ content: "";
160
+ display: block;
161
+ width: var(--bs-handle-width);
162
+ height: var(--bs-handle-height);
163
+ background: var(--bs-handle-color);
164
+ border-radius: calc(var(--bs-handle-height) / 2);
165
+ margin: 0 auto 8px;
166
+ transition: width 0.15s ease, background 0.15s ease, transform 0.15s ease;
167
+ pointer-events: none; /* always hit the padding-zoned parent */
168
+ }
169
+
170
+ /* Side-mode handle: a thin grab-strip docked to the sheet's open edge,
171
+ absolutely positioned so it sits on the visible boundary regardless of
172
+ the sheet's flex-direction. The header content inside it is hidden in
173
+ side modes — for vertical sheets the handle is just a grip. The hidden
174
+ header content stays in DOM (and out of the a11y tree thanks to
175
+ `display: none`) so screen readers don't read a stale header here —
176
+ consumers should put descriptive content inside the scrollable area
177
+ instead in side modes. */
178
+ .bs-sheet[data-mode="left"] .bs-handle,
179
+ .bs-sheet[data-mode="right"] .bs-handle {
180
+ position: absolute;
181
+ top: 0;
182
+ bottom: 0;
183
+ width: 28px;
184
+ min-width: 0;
185
+ min-height: 0;
186
+ display: flex;
187
+ flex-direction: column;
188
+ align-items: center;
189
+ justify-content: center;
190
+ padding: 0;
191
+ background: var(--bs-surface);
192
+ z-index: 1;
193
+ overflow: hidden;
194
+ }
195
+ .bs-sheet[data-mode="left"] .bs-handle > *,
196
+ .bs-sheet[data-mode="right"] .bs-handle > * {
197
+ display: none;
198
+ }
199
+ .bs-sheet[data-mode="left"] .bs-handle {
200
+ right: 0;
201
+ border-left: 1px solid var(--bs-surface-border);
202
+ }
203
+ .bs-sheet[data-mode="right"] .bs-handle {
204
+ left: 0;
205
+ border-right: 1px solid var(--bs-surface-border);
206
+ }
207
+
208
+ .bs-sheet[data-mode="left"] .bs-handle::before,
209
+ .bs-sheet[data-mode="right"] .bs-handle::before {
210
+ width: var(--bs-handle-height);
211
+ height: var(--bs-handle-width);
212
+ margin: 0;
213
+ }
214
+
215
+ .bs-sheet:hover .bs-handle::before {
216
+ background: color-mix(in oklab, var(--bs-handle-color), #000 15%);
217
+ }
218
+ .bs-handle:active::before {
219
+ background: color-mix(in oklab, var(--bs-handle-color), #000 35%);
220
+ transform: scaleX(1.15);
221
+ }
222
+
223
+ /* ----- snap-target ticks (opt-in via .bs-handle--ticks) -----
224
+ Material You / Apple Music inspired: while dragging, render a faint row
225
+ of dots near the grip to suggest "this thing has multiple stops".
226
+ Decorative only — positions are NOT computed from real snap points.
227
+ Hidden in idle state, fades in during drag. Opt-in via the modifier
228
+ class so consumers who don't want the hint don't get it. */
229
+ .bs-handle.bs-handle--ticks::after {
230
+ content: "";
231
+ display: block;
232
+ position: absolute;
233
+ left: 50%;
234
+ /* Sit just under the grip in vertical (bottom/top) modes. Translate -50%
235
+ so the dot row is centered horizontally regardless of width. */
236
+ bottom: 6px;
237
+ transform: translateX(-50%);
238
+ width: calc(var(--bs-handle-width) * 2);
239
+ height: 3px;
240
+ /* 3px dots spaced every 8px — purely visual hint. */
241
+ background-image: radial-gradient(
242
+ circle,
243
+ var(--bs-handle-color) 0 1.5px,
244
+ transparent 1.6px
245
+ );
246
+ background-size: 8px 3px;
247
+ background-repeat: repeat-x;
248
+ background-position: center;
249
+ opacity: 0;
250
+ transition: opacity 0.18s ease-out;
251
+ pointer-events: none;
252
+ }
253
+ /* Only visible while the user is actively dragging the sheet. */
254
+ .bs-root[data-dragging="true"] .bs-handle.bs-handle--ticks::after {
255
+ opacity: 0.4;
256
+ }
257
+ /* Top mode: dots above the grip instead of below. */
258
+ .bs-sheet[data-mode="top"] .bs-handle.bs-handle--ticks::after {
259
+ bottom: auto;
260
+ top: 6px;
261
+ }
262
+ /* Side modes: dots run vertically alongside the (vertical) grip. */
263
+ .bs-sheet[data-mode="left"] .bs-handle.bs-handle--ticks::after,
264
+ .bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after {
265
+ left: 50%;
266
+ top: 50%;
267
+ bottom: auto;
268
+ transform: translate(-50%, -50%);
269
+ width: 3px;
270
+ height: calc(var(--bs-handle-width) * 2);
271
+ background-size: 3px 8px;
272
+ background-repeat: repeat-y;
273
+ /* Offset the column so it sits beside the grip, not on top of it. */
274
+ margin-left: calc(var(--bs-handle-height) + 6px);
275
+ }
276
+ .bs-sheet[data-mode="right"] .bs-handle.bs-handle--ticks::after {
277
+ margin-left: calc(-1 * (var(--bs-handle-height) + 6px));
278
+ }
279
+
280
+ /* ----- content ----- */
281
+ .bs-content {
282
+ flex: 1;
283
+ min-height: 0;
284
+ overflow-y: auto;
285
+ -webkit-overflow-scrolling: touch;
286
+ overscroll-behavior: contain;
287
+ padding: 0 16px 16px;
288
+ }
289
+
290
+ /* ----- screen-component (fades in behind sheet, opacity = --bs-progress) -----
291
+ *
292
+ * Two ready-made scrim recipes for the engine's `screenComponent`:
293
+ *
294
+ * 1. `.bs-screen` — base. Engine writes `opacity` and `display`; consumer
295
+ * controls layout. Use directly when scrimMode: "full" (default).
296
+ *
297
+ * 2. `.bs-scrim` — opinionated scrim with token-driven color + optional
298
+ * blur. Drop on the same element as `.bs-screen` (or alone) for an
299
+ * instant "monitoring-style" dim with no extra CSS.
300
+ *
301
+ * 3. `.bs-scrim--above` — pairs with `scrimMode: "above-sheet"`. The
302
+ * engine writes the `inset` so the scrim covers only the area NOT
303
+ * occupied by the sheet; this class only adds the visual treatment.
304
+ *
305
+ * Tokens (override at any cascade level):
306
+ * --bs-scrim-color default rgba(0, 0, 0, 0.4)
307
+ * --bs-scrim-blur default 0px (set to e.g. 8px for backdrop blur)
308
+ * --bs-z-scrim default calc(--bs-z-sheet - 1)
309
+ */
310
+ .bs-screen {
311
+ position: absolute;
312
+ inset: 0;
313
+ pointer-events: none;
314
+ display: none;
315
+ z-index: var(--bs-z-scrim);
316
+ }
317
+
318
+ .bs-scrim {
319
+ background: var(--bs-scrim-color);
320
+ /* Engine writes opacity per-frame; transition only when not actively
321
+ dragging/animating to avoid the 150ms lag on fast snaps. */
322
+ transition: opacity 0.15s linear;
323
+ will-change: opacity;
324
+ }
325
+ .bs-root[data-dragging="true"] .bs-scrim,
326
+ .bs-root[data-animating="true"] .bs-scrim {
327
+ transition: none;
328
+ }
329
+
330
+ /* Optional blur — opt-in by setting --bs-scrim-blur to a non-zero length.
331
+ `backdrop-filter: blur(0)` is a no-op visually but still triggers a
332
+ compositing layer; the calc() guards against accidental layer creation
333
+ when the consumer hasn't customised the token. */
334
+ .bs-scrim {
335
+ backdrop-filter: blur(var(--bs-scrim-blur));
336
+ -webkit-backdrop-filter: blur(var(--bs-scrim-blur));
337
+ }
338
+
339
+ /* `.bs-scrim--above` is a documented marker class for `scrimMode:
340
+ "above-sheet"` — the engine writes the `inset` + `position: fixed`
341
+ inline. Add your own declarations under `.bs-scrim--above { ... }` to
342
+ override the visual treatment per-context. */
343
+
344
+ /* ----- buttons docked above the sheet ----- */
345
+ .bs-button-slot {
346
+ position: absolute;
347
+ z-index: calc(var(--bs-z-sheet) + 1);
348
+ pointer-events: auto;
349
+ }
350
+ .bs-button-slot[data-side="left"] {
351
+ left: 16px;
352
+ bottom: calc(var(--bs-size, 0px) + 12px);
353
+ transition: bottom 0.15s ease;
354
+ }
355
+ .bs-button-slot[data-side="right"] {
356
+ right: 16px;
357
+ bottom: calc(var(--bs-size, 0px) + 12px);
358
+ transition: bottom 0.15s ease;
359
+ }
360
+
361
+ /* ----- accessibility ----- */
362
+ @media (prefers-reduced-motion: reduce) {
363
+ .bs-backdrop,
364
+ .bs-handle::before,
365
+ .bs-button-slot[data-side="left"],
366
+ .bs-button-slot[data-side="right"] {
367
+ transition: none !important;
368
+ }
369
+ }
370
+
371
+ /* ----- dark mode token defaults ----- */
372
+ @media (prefers-color-scheme: dark) {
373
+ :where(.bs-root) {
374
+ --bs-surface: #1a1a1d;
375
+ --bs-surface-border: rgba(255, 255, 255, 0.08);
376
+ /* WCAG 1.4.11 — non-text contrast ≥ 3:1 against the surface. */
377
+ --bs-handle-color: rgba(255, 255, 255, 0.45);
378
+ --bs-shadow: 0 -3px 16px rgba(0, 0, 0, 0.5);
379
+ }
380
+ }
381
+
382
+ /* visually-hidden helper for aria-live announcements */
383
+ .bs-sr-only {
384
+ position: absolute;
385
+ width: 1px;
386
+ height: 1px;
387
+ padding: 0;
388
+ margin: -1px;
389
+ overflow: hidden;
390
+ clip: rect(0, 0, 0, 0);
391
+ white-space: nowrap;
392
+ border: 0;
393
+ }
394
+
395
+ /* =====================================================================
396
+ * OVERLAY — bottom-anchored slide-up panel
397
+ * ===================================================================== */
398
+
399
+ :where(.ovl-root) {
400
+ --ovl-surface: var(--bs-surface, #ffffff);
401
+ --ovl-surface-border: var(--bs-surface-border, rgba(0, 0, 0, 0.08));
402
+ --ovl-shadow: 0 -8px 40px rgba(0, 0, 0, 0.35);
403
+ --ovl-radius: var(--bs-radius, 20px);
404
+ --ovl-backdrop-color: rgba(0, 0, 0, 0.5);
405
+ --ovl-z-backdrop: 200;
406
+ --ovl-z-panel: 201;
407
+ }
408
+
409
+ .ovl-backdrop {
410
+ position: fixed;
411
+ inset: 0;
412
+ background: var(--ovl-backdrop-color);
413
+ opacity: 0;
414
+ pointer-events: none;
415
+ z-index: var(--ovl-z-backdrop);
416
+ }
417
+
418
+ .ovl-panel {
419
+ position: fixed;
420
+ background: var(--ovl-surface);
421
+ box-shadow: var(--ovl-shadow);
422
+ z-index: var(--ovl-z-panel);
423
+ -webkit-tap-highlight-color: transparent;
424
+ outline: none;
425
+ /* Modern open transition — works alongside the JS double-rAF fallback
426
+ in core/overlay.ts. In browsers that support @starting-style
427
+ (Chrome 117+, Safari 17.5+, Firefox 129+), the engine just flips
428
+ [data-state] from "entering" to "open" and the browser interpolates
429
+ from the @starting-style block below. The JS still writes inline
430
+ transition/transform for older browsers; setting the same transition
431
+ here is harmless because the inline style wins via specificity. */
432
+ transition: transform 0.32s cubic-bezier(0.29, 1.24, 0.84, 0.99),
433
+ opacity 0.32s ease-out;
434
+ }
435
+ .ovl-panel[data-state="open"] {
436
+ transform: translate3d(0, 0, 0);
437
+ opacity: 1;
438
+ }
439
+ /* @starting-style: define the "from" frame for the entry interpolation
440
+ per edge. Browsers without support fall back gracefully — the
441
+ double-rAF in overlay.ts still drives the same animation. */
442
+ @starting-style {
443
+ .ovl-panel[data-edge="bottom"][data-state="open"] {
444
+ transform: translate3d(0, 100%, 0);
445
+ opacity: 0;
446
+ }
447
+ .ovl-panel[data-edge="top"][data-state="open"] {
448
+ transform: translate3d(0, -100%, 0);
449
+ opacity: 0;
450
+ }
451
+ .ovl-panel[data-edge="left"][data-state="open"] {
452
+ transform: translate3d(-100%, 0, 0);
453
+ opacity: 0;
454
+ }
455
+ .ovl-panel[data-edge="right"][data-state="open"] {
456
+ transform: translate3d(100%, 0, 0);
457
+ opacity: 0;
458
+ }
459
+ }
460
+
461
+ .ovl-panel[data-edge="bottom"] {
462
+ left: 0;
463
+ right: 0;
464
+ bottom: 0;
465
+ width: 100%;
466
+ border-top-left-radius: var(--ovl-radius);
467
+ border-top-right-radius: var(--ovl-radius);
468
+ border-top: 1px solid var(--ovl-surface-border);
469
+ padding-bottom: env(safe-area-inset-bottom);
470
+ }
471
+
472
+ .ovl-panel[data-edge="top"] {
473
+ left: 0;
474
+ right: 0;
475
+ top: 0;
476
+ width: 100%;
477
+ border-bottom-left-radius: var(--ovl-radius);
478
+ border-bottom-right-radius: var(--ovl-radius);
479
+ border-bottom: 1px solid var(--ovl-surface-border);
480
+ padding-top: env(safe-area-inset-top);
481
+ }
482
+
483
+ .ovl-panel[data-edge="left"] {
484
+ top: 0;
485
+ bottom: 0;
486
+ left: 0;
487
+ height: 100%;
488
+ border-top-right-radius: var(--ovl-radius);
489
+ border-bottom-right-radius: var(--ovl-radius);
490
+ border-right: 1px solid var(--ovl-surface-border);
491
+ }
492
+
493
+ .ovl-panel[data-edge="right"] {
494
+ top: 0;
495
+ bottom: 0;
496
+ right: 0;
497
+ height: 100%;
498
+ border-top-left-radius: var(--ovl-radius);
499
+ border-bottom-left-radius: var(--ovl-radius);
500
+ border-left: 1px solid var(--ovl-surface-border);
501
+ }
502
+
503
+ @media (prefers-reduced-motion: reduce) {
504
+ .ovl-panel,
505
+ .ovl-backdrop {
506
+ transition: none !important;
507
+ }
508
+ }