@swiftbrowser/web 0.18.3 → 0.19.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.
- package/package.json +1 -1
- package/src/audioEngine.ts +1443 -0
- package/src/chartDom.ts +1 -1
- package/src/hostRequests.ts +10 -0
- package/src/languageModel.ts +309 -0
- package/src/layout/engine.ts +126 -48
- package/src/layoutDom.ts +43 -3
- package/src/protocol.ts +29 -4
- package/src/renderer.ts +167 -43
- package/src/styles.css +207 -35
- package/src/styles.ts +4 -9
- package/src/symbols.ts +6 -0
package/src/renderer.ts
CHANGED
|
@@ -116,6 +116,7 @@ import {
|
|
|
116
116
|
type ScrollViewProps,
|
|
117
117
|
type SearchFieldProps,
|
|
118
118
|
type SectionProps,
|
|
119
|
+
type DisclosureGroupProps,
|
|
119
120
|
type ShapeProps,
|
|
120
121
|
type SheetProps,
|
|
121
122
|
type SpacerProps,
|
|
@@ -304,6 +305,8 @@ const ALL_KINDS: ReadonlySet<string> = new Set<ElementKind>([
|
|
|
304
305
|
// A `Section`'s view header / footer
|
|
305
306
|
'sectionheader',
|
|
306
307
|
'sectionfooter',
|
|
308
|
+
// `DisclosureGroup`: its label with a chevron, its content below once expanded
|
|
309
|
+
'disclosuregroup',
|
|
307
310
|
// An `AVPlayer` shown by a view (src/video.ts)
|
|
308
311
|
'video',
|
|
309
312
|
'lazyspacer',
|
|
@@ -320,10 +323,14 @@ const SLIDER_THUMB = 38;
|
|
|
320
323
|
export const PICKER_MENU_WIDTH = 250;
|
|
321
324
|
/** Row height inside the picker menu. */
|
|
322
325
|
export const PICKER_MENU_ROW = 44;
|
|
326
|
+
/** A row with a subtitle under its title (styles.css, `[data-sb-subtitled]`). */
|
|
327
|
+
const MENU_SUBTITLED_ROW = 58;
|
|
323
328
|
/** Height of a section header caption row in the popover (Phase 6). */
|
|
324
|
-
const MENU_CAPTION_HEIGHT =
|
|
325
|
-
/**
|
|
326
|
-
const MENU_SEPARATOR_HEIGHT =
|
|
329
|
+
const MENU_CAPTION_HEIGHT = 30;
|
|
330
|
+
/** A group separator in the popover: a hairline with 9px above and below (iOS 26). */
|
|
331
|
+
const MENU_SEPARATOR_HEIGHT = 19;
|
|
332
|
+
/** The popover's padding above its first row and below its last (iOS 26's inset menu); keep in sync with styles.css. */
|
|
333
|
+
const MENU_PADDING = 10;
|
|
327
334
|
/** Gap between the control and its menu. */
|
|
328
335
|
const PICKER_MENU_GAP = 4;
|
|
329
336
|
/** Minimum distance between the menu and the screen edge. */
|
|
@@ -515,16 +522,24 @@ export class Renderer {
|
|
|
515
522
|
// `tapgesture` (or a menu label) handles the click alone. Elsewhere in
|
|
516
523
|
// a list row, the row's one button (`rowAction`).
|
|
517
524
|
const hit = event.target as HTMLElement | null;
|
|
518
|
-
|
|
525
|
+
let target = hit?.closest<HTMLElement>(
|
|
519
526
|
'.sb-button, .sb-navlink, .sb-navbar-back, .sb-navbar-more, .sb-navsplit-toggle, .sb-switch, .sb-sheet-scrim, ' +
|
|
520
527
|
'.sb-toggle[data-sb-toggle-style="button"], .sb-toolbar .sb-toggle:not([data-sb-toggle-style]), ' +
|
|
521
528
|
'.sb-tabbar-item, .sb-segment, .sb-picker-option, .sb-picker-menu-item, .sb-picker[data-sb-style="menu"], ' +
|
|
522
529
|
'.sb-menu, .sb-tapgesture, .sb-stepper-button, .sb-alert-scrim, .sb-alert-ok, ' +
|
|
523
530
|
// Phase 8: swipe action buttons, the edit-mode row control and selectable rows (the row sends `tap` itself)
|
|
524
531
|
'.sb-swipe-action, .sb-listrow-control, .sb-listrow[data-sb-selectable], ' +
|
|
532
|
+
// A disclosure group's label, a collapsible section's header: a `tap` on the element toggles it
|
|
533
|
+
'.sb-disclosure-toggle, .sb-section[data-sb-collapsible] > .sb-section-header, .sb-section[data-sb-collapsible] .sb-sectionheader, ' +
|
|
525
534
|
// A link in a rich text: innermost, so it wins over a tappable row around the text
|
|
526
535
|
'.sb-link',
|
|
527
536
|
) ?? (hit ? rowAction(hit, this.root) : null);
|
|
537
|
+
// A link or button row of a list in edit mode is selected, not followed
|
|
538
|
+
// or pressed: the tap is the row's (its `tag`), as iOS selects the row then.
|
|
539
|
+
if (target?.classList.contains('sb-navlink') || target?.classList.contains('sb-button')) {
|
|
540
|
+
const row = target.closest<HTMLElement>('.sb-listrow[data-sb-selectable]');
|
|
541
|
+
if (row && row.closest('.sb-list')?.hasAttribute('data-sb-editing')) target = row;
|
|
542
|
+
}
|
|
528
543
|
if (!target || !this.root.contains(target)) return;
|
|
529
544
|
if (target.classList.contains('sb-link')) {
|
|
530
545
|
const text = target.closest<HTMLElement>('.sb-text');
|
|
@@ -535,6 +550,19 @@ export class Renderer {
|
|
|
535
550
|
this.onEvent({ type: 'link', id, value: url });
|
|
536
551
|
return;
|
|
537
552
|
}
|
|
553
|
+
const sectionHeader = target.classList.contains('sb-section-header') || target.classList.contains('sb-sectionheader');
|
|
554
|
+
if (target.classList.contains('sb-disclosure-toggle') || sectionHeader) {
|
|
555
|
+
// `DisclosureGroup` and `Section(isExpanded:)`: the tap toggles the
|
|
556
|
+
// element (a section's only in a sidebar-style list, where it collapses).
|
|
557
|
+
const section = sectionHeader;
|
|
558
|
+
const owner = target.closest<HTMLElement>(section ? '.sb-section' : '.sb-disclosuregroup');
|
|
559
|
+
const id = Number(owner?.dataset[ID_ATTR]);
|
|
560
|
+
if (!owner || !Number.isInteger(id)) return;
|
|
561
|
+
if (section && owner.closest<HTMLElement>('.sb-list')?.dataset['sbListStyle'] !== 'sidebar') return;
|
|
562
|
+
event.preventDefault();
|
|
563
|
+
this.onEvent({ type: 'tap', id });
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
538
566
|
if (this.interaction.handleClick(target)) {
|
|
539
567
|
event.preventDefault();
|
|
540
568
|
return;
|
|
@@ -588,7 +616,7 @@ export class Renderer {
|
|
|
588
616
|
return;
|
|
589
617
|
}
|
|
590
618
|
if (target.classList.contains('sb-picker-menu-item')) {
|
|
591
|
-
// A popover row: a picker option (`select`) or a button / navlink (`tap`).
|
|
619
|
+
// A popover row: a picker option (`select`), a toggle, or a button / navlink (`tap`).
|
|
592
620
|
if (!this.menu) return;
|
|
593
621
|
const action = target.dataset['sbAction'];
|
|
594
622
|
const id = Number(target.dataset['sbTarget']);
|
|
@@ -597,6 +625,7 @@ export class Renderer {
|
|
|
597
625
|
event.preventDefault();
|
|
598
626
|
this.closeMenu();
|
|
599
627
|
if (action === 'tap') this.onEvent({ type: 'tap', id });
|
|
628
|
+
else if (action === 'toggle') this.onEvent({ type: 'toggle', id, value: target.getAttribute('aria-checked') !== 'true' });
|
|
600
629
|
else if (Number.isInteger(index)) this.onEvent({ type: 'select', id, value: index });
|
|
601
630
|
return;
|
|
602
631
|
}
|
|
@@ -1691,6 +1720,11 @@ export class Renderer {
|
|
|
1691
1720
|
this.collectLayouts(result);
|
|
1692
1721
|
if (this.applying === 0) this.flushGeometry();
|
|
1693
1722
|
this.queueVisibilityCheck();
|
|
1723
|
+
// `.statusBarHidden` on a screen that shows: the frame's drawn status bar fades (styles.css).
|
|
1724
|
+
const statusHidden = this.root.querySelector('.sb-navscreen[data-sb-status-hidden]:not([hidden])') !== null;
|
|
1725
|
+
if (document.documentElement.hasAttribute('data-sb-status-hidden') !== statusHidden) {
|
|
1726
|
+
document.documentElement.toggleAttribute('data-sb-status-hidden', statusHidden);
|
|
1727
|
+
}
|
|
1694
1728
|
this.afterLayout?.();
|
|
1695
1729
|
return changes;
|
|
1696
1730
|
}
|
|
@@ -1818,10 +1852,23 @@ export class Renderer {
|
|
|
1818
1852
|
const menu = div('sb-picker-menu');
|
|
1819
1853
|
menu.setAttribute('role', 'menu');
|
|
1820
1854
|
menu.dataset['sbFor'] = String(id);
|
|
1821
|
-
|
|
1855
|
+
// No hairline at either end (a section first or last).
|
|
1856
|
+
while (rows[0]?.kind === 'separator') rows = rows.slice(1);
|
|
1857
|
+
while (rows[rows.length - 1]?.kind === 'separator') rows = rows.slice(0, -1);
|
|
1858
|
+
const checks = rows.some((row) => row.kind === 'select' || row.kind === 'toggle');
|
|
1859
|
+
// iOS indents the whole menu around its check column: section titles start with the plain titles.
|
|
1860
|
+
if (checks) menu.dataset['sbChecks'] = '';
|
|
1861
|
+
let height = 2 * MENU_PADDING;
|
|
1822
1862
|
for (const row of rows) {
|
|
1823
|
-
menu.append(menuRowElement(row));
|
|
1824
|
-
height +=
|
|
1863
|
+
menu.append(menuRowElement(row, checks));
|
|
1864
|
+
height +=
|
|
1865
|
+
row.kind === 'caption'
|
|
1866
|
+
? MENU_CAPTION_HEIGHT
|
|
1867
|
+
: row.kind === 'separator'
|
|
1868
|
+
? MENU_SEPARATOR_HEIGHT
|
|
1869
|
+
: (row.kind === 'tap' || row.kind === 'toggle') && row.subtitle
|
|
1870
|
+
? MENU_SUBTITLED_ROW
|
|
1871
|
+
: PICKER_MENU_ROW;
|
|
1825
1872
|
}
|
|
1826
1873
|
// Anchor to the control: trailing edges aligned, below it when there is
|
|
1827
1874
|
// room, else above; clamped inside the screen. A menu taller than the
|
|
@@ -1832,7 +1879,10 @@ export class Renderer {
|
|
|
1832
1879
|
const width = Math.min(PICKER_MENU_WIDTH, Math.max(0, rootRect.width - 2 * PICKER_MENU_MARGIN));
|
|
1833
1880
|
let left = anchorRect.right - rootRect.left - width;
|
|
1834
1881
|
left = Math.max(PICKER_MENU_MARGIN, Math.min(left, rootRect.width - width - PICKER_MENU_MARGIN));
|
|
1835
|
-
|
|
1882
|
+
// A toolbar button's menu grows out of the button and covers it (iOS 26);
|
|
1883
|
+
// any other control's opens just under it.
|
|
1884
|
+
const inToolbar = anchor.closest('.sb-toolbar') !== null;
|
|
1885
|
+
const belowTop = inToolbar ? anchorRect.top - rootRect.top : anchorRect.bottom - rootRect.top + PICKER_MENU_GAP;
|
|
1836
1886
|
const roomBelow = rootRect.height - PICKER_MENU_MARGIN - belowTop;
|
|
1837
1887
|
const roomAbove = anchorRect.top - rootRect.top - PICKER_MENU_GAP - PICKER_MENU_MARGIN;
|
|
1838
1888
|
const below = height <= roomBelow || (height > roomAbove && roomBelow >= roomAbove);
|
|
@@ -2684,7 +2734,8 @@ interface OpenMenu {
|
|
|
2684
2734
|
|
|
2685
2735
|
/** One row of a popover menu. */
|
|
2686
2736
|
type MenuRow =
|
|
2687
|
-
| { kind: 'tap'; id: ElementId; title: string; symbol: string | null; destructive: boolean; disabled: boolean }
|
|
2737
|
+
| { kind: 'tap'; id: ElementId; title: string; subtitle: string | null; symbol: string | null; destructive: boolean; disabled: boolean }
|
|
2738
|
+
| { kind: 'toggle'; id: ElementId; title: string; subtitle: string | null; symbol: string | null; isOn: boolean; disabled: boolean }
|
|
2688
2739
|
| { kind: 'select'; id: ElementId; index: number; title: string; checked: boolean }
|
|
2689
2740
|
| { kind: 'caption'; text: string }
|
|
2690
2741
|
| { kind: 'text'; text: string }
|
|
@@ -2704,6 +2755,20 @@ function disabledOf(node: LayoutNode): boolean {
|
|
|
2704
2755
|
return false;
|
|
2705
2756
|
}
|
|
2706
2757
|
|
|
2758
|
+
/**
|
|
2759
|
+
* A menu item's title and subtitle, as SwiftUI reads a menu label: its
|
|
2760
|
+
* first text is the title, any after it the subtitle under it.
|
|
2761
|
+
*/
|
|
2762
|
+
function titlesOf(node: LayoutNode): { title: string; subtitle: string | null } {
|
|
2763
|
+
const texts: string[] = [];
|
|
2764
|
+
const visit = (n: LayoutNode): void => {
|
|
2765
|
+
if (n.kind === 'text' && typeof n.props['text'] === 'string' && n.props['text'].trim().length > 0) texts.push(n.props['text'].trim());
|
|
2766
|
+
n.children.forEach(visit);
|
|
2767
|
+
};
|
|
2768
|
+
visit(node);
|
|
2769
|
+
return { title: texts[0] ?? '', subtitle: texts.length > 1 ? texts.slice(1).join(' ') : null };
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2707
2772
|
/**
|
|
2708
2773
|
* Turn a `menu`'s items into popover rows. A picker's options form their
|
|
2709
2774
|
* own group, separated from neighbouring rows like iOS does; explicit
|
|
@@ -2726,12 +2791,16 @@ function collectMenuRows(items: readonly LayoutNode[], rows: MenuRow[] = []): Me
|
|
|
2726
2791
|
rows.push({
|
|
2727
2792
|
kind: 'tap',
|
|
2728
2793
|
id: base.id,
|
|
2729
|
-
|
|
2794
|
+
...titlesOf(base),
|
|
2730
2795
|
symbol: symbolOf(base),
|
|
2731
2796
|
destructive: base.props['role'] === 'destructive',
|
|
2732
2797
|
disabled: disabledOf(item),
|
|
2733
2798
|
});
|
|
2734
2799
|
break;
|
|
2800
|
+
case 'toggle':
|
|
2801
|
+
// A toggle in a menu is a checkable row (SwiftUI's), its label the title.
|
|
2802
|
+
rows.push({ kind: 'toggle', id: base.id, ...titlesOf(base), symbol: symbolOf(base), isOn: base.props['isOn'] === true, disabled: disabledOf(item) });
|
|
2803
|
+
break;
|
|
2735
2804
|
case 'picker': {
|
|
2736
2805
|
const raw = base.props['options'];
|
|
2737
2806
|
const options = Array.isArray(raw) ? raw.map(String) : [];
|
|
@@ -2745,9 +2814,12 @@ function collectMenuRows(items: readonly LayoutNode[], rows: MenuRow[] = []): Me
|
|
|
2745
2814
|
separate();
|
|
2746
2815
|
break;
|
|
2747
2816
|
case 'section': {
|
|
2817
|
+
// An inline section, as iOS draws one: a hairline before and after it, its header a caption.
|
|
2818
|
+
separate();
|
|
2748
2819
|
const header = base.props['header'];
|
|
2749
2820
|
if (typeof header === 'string' && header.length > 0) rows.push({ kind: 'caption', text: header });
|
|
2750
2821
|
collectMenuRows(base.children, rows);
|
|
2822
|
+
pendingSeparator = true;
|
|
2751
2823
|
break;
|
|
2752
2824
|
}
|
|
2753
2825
|
case 'sectionheader': {
|
|
@@ -2777,44 +2849,60 @@ function collectMenuRows(items: readonly LayoutNode[], rows: MenuRow[] = []): Me
|
|
|
2777
2849
|
return rows;
|
|
2778
2850
|
}
|
|
2779
2851
|
|
|
2780
|
-
/**
|
|
2781
|
-
|
|
2852
|
+
/**
|
|
2853
|
+
* Build the DOM for one popover row (the renderer's click handler reads the
|
|
2854
|
+
* data attributes). In a menu with checkable rows (`checks`), every item row
|
|
2855
|
+
* keeps a leading column for the checkmark, as iOS does, so titles line up;
|
|
2856
|
+
* an item's symbol follows the check, its subtitle sits under its title.
|
|
2857
|
+
*/
|
|
2858
|
+
function menuRowElement(row: MenuRow, checks: boolean): HTMLElement {
|
|
2782
2859
|
switch (row.kind) {
|
|
2783
|
-
case 'select':
|
|
2784
|
-
|
|
2785
|
-
item.type = 'button';
|
|
2786
|
-
item.className = 'sb-picker-menu-item';
|
|
2787
|
-
item.setAttribute('role', 'menuitemradio');
|
|
2788
|
-
item.setAttribute('aria-checked', String(row.checked));
|
|
2789
|
-
item.dataset['sbAction'] = 'select';
|
|
2790
|
-
item.dataset['sbTarget'] = String(row.id);
|
|
2791
|
-
item.dataset['sbIndex'] = String(row.index);
|
|
2792
|
-
const check = span('sb-picker-menu-check');
|
|
2793
|
-
const glyph = renderSymbol('checkmark');
|
|
2794
|
-
if (glyph) check.append(glyph);
|
|
2795
|
-
const title = span('sb-picker-menu-title');
|
|
2796
|
-
title.textContent = row.title;
|
|
2797
|
-
item.append(check, title);
|
|
2798
|
-
return item;
|
|
2799
|
-
}
|
|
2860
|
+
case 'select':
|
|
2861
|
+
case 'toggle':
|
|
2800
2862
|
case 'tap': {
|
|
2801
2863
|
const item = document.createElement('button');
|
|
2802
2864
|
item.type = 'button';
|
|
2803
2865
|
item.className = 'sb-picker-menu-item';
|
|
2804
|
-
item.setAttribute('role', 'menuitem');
|
|
2805
|
-
item.dataset['sbAction'] = 'tap';
|
|
2806
2866
|
item.dataset['sbTarget'] = String(row.id);
|
|
2807
|
-
|
|
2808
|
-
if (row.
|
|
2867
|
+
item.dataset['sbAction'] = row.kind;
|
|
2868
|
+
if (row.kind === 'select') {
|
|
2869
|
+
item.setAttribute('role', 'menuitemradio');
|
|
2870
|
+
item.setAttribute('aria-checked', String(row.checked));
|
|
2871
|
+
item.dataset['sbIndex'] = String(row.index);
|
|
2872
|
+
} else if (row.kind === 'toggle') {
|
|
2873
|
+
item.setAttribute('role', 'menuitemcheckbox');
|
|
2874
|
+
item.setAttribute('aria-checked', String(row.isOn));
|
|
2875
|
+
} else {
|
|
2876
|
+
item.setAttribute('role', 'menuitem');
|
|
2877
|
+
if (row.destructive) item.dataset['sbRole'] = 'destructive';
|
|
2878
|
+
}
|
|
2879
|
+
if (row.kind !== 'select' && row.disabled) item.disabled = true;
|
|
2880
|
+
if (checks) {
|
|
2881
|
+
const check = span('sb-picker-menu-check');
|
|
2882
|
+
const glyph = renderSymbol('checkmark');
|
|
2883
|
+
if (glyph) check.append(glyph);
|
|
2884
|
+
item.append(check);
|
|
2885
|
+
}
|
|
2886
|
+
const symbol = row.kind === 'select' ? null : row.symbol;
|
|
2887
|
+
if (symbol) {
|
|
2888
|
+
const holder = span('sb-picker-menu-symbol');
|
|
2889
|
+
const glyph = renderSymbol(symbol);
|
|
2890
|
+
if (glyph) holder.append(glyph);
|
|
2891
|
+
holder.dataset['sbSymbol'] = symbol;
|
|
2892
|
+
item.append(holder);
|
|
2893
|
+
}
|
|
2809
2894
|
const title = span('sb-picker-menu-title');
|
|
2810
2895
|
title.textContent = row.title;
|
|
2811
|
-
|
|
2812
|
-
if (
|
|
2813
|
-
const
|
|
2814
|
-
const
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
item.append(
|
|
2896
|
+
const subtitle = row.kind === 'select' ? null : row.subtitle;
|
|
2897
|
+
if (subtitle) {
|
|
2898
|
+
const titles = span('sb-picker-menu-titles');
|
|
2899
|
+
const sub = span('sb-picker-menu-subtitle');
|
|
2900
|
+
sub.textContent = subtitle;
|
|
2901
|
+
titles.append(title, sub);
|
|
2902
|
+
item.append(titles);
|
|
2903
|
+
item.dataset['sbSubtitled'] = '';
|
|
2904
|
+
} else {
|
|
2905
|
+
item.append(title);
|
|
2818
2906
|
}
|
|
2819
2907
|
return item;
|
|
2820
2908
|
}
|
|
@@ -2837,6 +2925,11 @@ function menuRowElement(row: MenuRow): HTMLElement {
|
|
|
2837
2925
|
}
|
|
2838
2926
|
}
|
|
2839
2927
|
|
|
2928
|
+
/** Whether scrolling dismisses the keyboard; a page cannot drag it down, so `interactively` blurs as `immediately` does. */
|
|
2929
|
+
function dismissesKeyboard(mode: unknown): boolean {
|
|
2930
|
+
return mode === true || mode === 'immediately' || mode === 'interactively';
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2840
2933
|
/** Props as the engine reads them (untrusted JSON, so a plain record). */
|
|
2841
2934
|
function asRecord(props: unknown): Record<string, unknown> {
|
|
2842
2935
|
return typeof props === 'object' && props !== null ? (props as Record<string, unknown>) : {};
|
|
@@ -2973,6 +3066,25 @@ function createShell(kind: ElementKind): HTMLElement {
|
|
|
2973
3066
|
SLOTS.set(el, slot);
|
|
2974
3067
|
return el;
|
|
2975
3068
|
}
|
|
3069
|
+
case 'disclosuregroup': {
|
|
3070
|
+
// The label (the first child) and the content below it, laid out as
|
|
3071
|
+
// a stack; a button over the label and a chevron at its trailing
|
|
3072
|
+
// edge, placed by layoutDom from the label's frame.
|
|
3073
|
+
const el = document.createElement('div');
|
|
3074
|
+
const slot = div('sb-slot');
|
|
3075
|
+
const toggle = document.createElement('button');
|
|
3076
|
+
toggle.type = 'button';
|
|
3077
|
+
toggle.className = 'sb-disclosure-toggle';
|
|
3078
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
3079
|
+
el.append(slot, toggle);
|
|
3080
|
+
const chevron = renderSymbol('chevron.right');
|
|
3081
|
+
if (chevron) {
|
|
3082
|
+
chevron.classList.add('sb-disclosure-chevron');
|
|
3083
|
+
el.append(chevron);
|
|
3084
|
+
}
|
|
3085
|
+
SLOTS.set(el, slot);
|
|
3086
|
+
return el;
|
|
3087
|
+
}
|
|
2976
3088
|
case 'toggle': {
|
|
2977
3089
|
const el = document.createElement('div');
|
|
2978
3090
|
const label = div('sb-slot sb-toggle-label');
|
|
@@ -3828,7 +3940,7 @@ function applyKindProps(el: HTMLElement, kind: ElementKind, props: ElementProps,
|
|
|
3828
3940
|
applyRefreshable(el, list.refreshable === true, list.refreshing === true);
|
|
3829
3941
|
// `.scrollContentBackground(.hidden)`: no grouped background (styles.css).
|
|
3830
3942
|
if (list.background === 'hidden') el.dataset['sbBackground'] = 'hidden';
|
|
3831
|
-
if (list.dismissesKeyboard
|
|
3943
|
+
if (dismissesKeyboard(list.dismissesKeyboard)) el.dataset['sbDismissesKeyboard'] = 'true';
|
|
3832
3944
|
applyEdgeEffect(el, list.edgeEffect);
|
|
3833
3945
|
break;
|
|
3834
3946
|
}
|
|
@@ -3859,7 +3971,7 @@ function applyKindProps(el: HTMLElement, kind: ElementKind, props: ElementProps,
|
|
|
3859
3971
|
applyRefreshable(el, scroll.refreshable === true, scroll.refreshing === true);
|
|
3860
3972
|
if (scroll.snap === 'viewAligned' || scroll.snap === 'paging') el.dataset['sbSnap'] = scroll.snap;
|
|
3861
3973
|
else delete el.dataset['sbSnap'];
|
|
3862
|
-
if (scroll.dismissesKeyboard
|
|
3974
|
+
if (dismissesKeyboard(scroll.dismissesKeyboard)) el.dataset['sbDismissesKeyboard'] = 'true';
|
|
3863
3975
|
if (scroll.reportsPosition === true) el.dataset['sbReportsPosition'] = '';
|
|
3864
3976
|
else delete el.dataset['sbReportsPosition'];
|
|
3865
3977
|
applyEdgeEffect(el, scroll.edgeEffect);
|
|
@@ -3869,6 +3981,12 @@ function applyKindProps(el: HTMLElement, kind: ElementKind, props: ElementProps,
|
|
|
3869
3981
|
case 'section':
|
|
3870
3982
|
applySection(el, props as SectionProps);
|
|
3871
3983
|
break;
|
|
3984
|
+
case 'disclosuregroup': {
|
|
3985
|
+
const expanded = (props as DisclosureGroupProps).expanded === true;
|
|
3986
|
+
el.toggleAttribute('data-sb-expanded', expanded);
|
|
3987
|
+
el.querySelector<HTMLElement>(':scope > .sb-disclosure-toggle')?.setAttribute('aria-expanded', String(expanded));
|
|
3988
|
+
break;
|
|
3989
|
+
}
|
|
3872
3990
|
case 'textfield':
|
|
3873
3991
|
applyTextField(el as HTMLInputElement | HTMLTextAreaElement, props as TextFieldProps);
|
|
3874
3992
|
break;
|
|
@@ -4409,6 +4527,10 @@ function applySection(el: HTMLElement, props: SectionProps): void {
|
|
|
4409
4527
|
header.textContent = props.header ?? '';
|
|
4410
4528
|
header.hidden = !props.header;
|
|
4411
4529
|
}
|
|
4530
|
+
// `Section(isExpanded:)`: a chevron on the header (a string one, or the
|
|
4531
|
+
// `sectionheader` view) in a sidebar-style list (styles.css); its tap toggles.
|
|
4532
|
+
el.toggleAttribute('data-sb-collapsible', props.collapsible === true);
|
|
4533
|
+
el.toggleAttribute('data-sb-expanded', props.collapsible === true && props.expanded !== false);
|
|
4412
4534
|
if (footer) {
|
|
4413
4535
|
footer.textContent = props.footer ?? '';
|
|
4414
4536
|
footer.hidden = !props.footer;
|
|
@@ -4864,6 +4986,8 @@ export function applyStyle(el: HTMLElement, style: Style): void {
|
|
|
4864
4986
|
if (style.animationToken != null) el.dataset[ANIMATION_TOKEN] = String(style.animationToken);
|
|
4865
4987
|
// A `Label`'s icon: tinted in list rows (styles.css).
|
|
4866
4988
|
if (style.labelIcon) el.dataset['sbLabelIcon'] = '';
|
|
4989
|
+
// `.textSelection(.enabled)`: the text below can be selected (styles.css).
|
|
4990
|
+
el.toggleAttribute('data-sb-text-selection', style.textSelection === true);
|
|
4867
4991
|
if (style.animation) ANIMATIONS.set(el, style.animation);
|
|
4868
4992
|
if (style.transition) setTransition(el, style.transition);
|
|
4869
4993
|
|