Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | 141x 33x 33x 33x 33x 33x 71x 33x 33x 38x 33x 36x 36x 36x 36x 36x 36x 33x 33x 46x 37x 37x 36x 33x 33x 33x 25x 17x 1x 2x 2x 2x 1x 4x 3x 2x 2x 1x | import { createRuntimeId } from '../accessibility/ids';
import { createControllerHost } from '../core/host';
import type {
ChangeDetails,
ControllableValueOptions,
RuntimeController,
RuntimeEventSource,
} from '../core/types';
import { createControllableValue } from '../state/controllable';
/** Typed causes accepted by Disclosure and Collapsible. @public */
export type DisclosureChangeReason = 'programmatic' | 'trigger' | 'keyboard';
/** Immutable semantic metadata for a disclosure trigger. @public */
export interface DisclosureTriggerProps {
/** Stable trigger ID. */
readonly id: string;
/** ID of the controlled panel. */
readonly ariaControls: string;
/** Current expansion state. */
readonly ariaExpanded: boolean;
/** Whether user interaction is disabled. */
readonly disabled: boolean;
}
/** Immutable semantic metadata for a disclosure panel. @public */
export interface DisclosurePanelProps {
/** Stable panel ID. */
readonly id: string;
/** Trigger that labels the panel. */
readonly ariaLabelledby: string;
/** Whether the consumer should hide the panel. */
readonly hidden: boolean;
/** Region role used by the WAI-ARIA disclosure pattern. */
readonly role: 'region';
}
/** Immutable Disclosure snapshot. @public */
export interface DisclosureSnapshot {
/** Current expansion state. */
readonly expanded: boolean;
/** Whether state ownership belongs to the consumer. */
readonly controlled: boolean;
/** Current interaction-disabled state. */
readonly disabled: boolean;
/** Trigger semantics for consumer rendering. */
readonly trigger: DisclosureTriggerProps;
/** Panel semantics for consumer rendering. */
readonly panel: DisclosurePanelProps;
}
/** Disclosure transition payload. @public */
export interface DisclosureChangeEvent {
/** Requested next expansion state. */
readonly expanded: boolean;
/** Typed cause and optional native event. */
readonly details: ChangeDetails<DisclosureChangeReason>;
}
/** Disclosure lifecycle event map. @public */
export interface DisclosureEvents {
/** Cancellable event emitted before expansion. */
readonly beforeOpen: DisclosureChangeEvent;
/** Event emitted after expansion. */
readonly open: DisclosureChangeEvent;
/** Event emitted after expansion resources settle. */
readonly afterOpen: DisclosureChangeEvent;
/** Cancellable event emitted before collapse. */
readonly beforeClose: DisclosureChangeEvent;
/** Event emitted after collapse. */
readonly close: DisclosureChangeEvent;
/** Event emitted after collapse resources settle. */
readonly afterClose: DisclosureChangeEvent;
/** Event emitted for each accepted transition. */
readonly stateChange: DisclosureChangeEvent;
}
/** Disclosure configuration supporting consumer-owned or internal state. @public */
export interface DisclosureOptions extends Partial<
ControllableValueOptions<boolean, DisclosureChangeReason>
> {
/** Deterministic ID base used for trigger/panel relationships. */
readonly id?: string;
/** Prevents interaction while preserving semantic state. */
readonly disabled?: boolean;
}
/** Headless Disclosure command and event surface. @public */
export interface DisclosureController
extends RuntimeController<DisclosureSnapshot>, RuntimeEventSource<DisclosureEvents> {
/** Expands unless disabled, already expanded, destroyed, or cancelled. */
expand(details?: ChangeDetails<DisclosureChangeReason>): void;
/** Collapses unless disabled, already collapsed, destroyed, or cancelled. */
collapse(details?: ChangeDetails<DisclosureChangeReason>): void;
/** Requests the opposite state with a typed cause. */
toggle(details?: ChangeDetails<DisclosureChangeReason>): void;
/** Updates interaction-disabled state without replacing the controller. */
setDisabled(disabled: boolean): void;
/** Handles click activation from consumer-rendered trigger markup. */
handleTriggerClick(event: MouseEvent): void;
/** Handles Enter and Space activation from non-native trigger integrations. */
handleTriggerKeyDown(event: KeyboardEvent): void;
}
/** Builds the shared disclosure state and relationship metadata used by grouped disclosures. */
export function createDisclosureSnapshot(
id: string,
expanded: boolean,
controlled: boolean,
disabled: boolean,
triggerId = `${id}-trigger`,
panelId = `${id}-panel`,
): DisclosureSnapshot {
return {
expanded,
controlled,
disabled,
trigger: {
id: triggerId,
ariaControls: panelId,
ariaExpanded: expanded,
disabled,
},
panel: {
id: panelId,
ariaLabelledby: triggerId,
hidden: !expanded,
role: 'region',
},
};
}
/** Creates an accessible Disclosure controller with no rendering or CSS side effects. @public */
export function createDisclosure(options: DisclosureOptions = {}): DisclosureController {
const id = options.id ?? createRuntimeId('disclosure');
let disabled = options.disabled ?? false;
const controlled = options.getValue !== undefined;
const initialExpanded = options.getValue?.() ?? options.defaultValue ?? false;
const snapshot = (expanded: boolean, controlled: boolean): DisclosureSnapshot =>
createDisclosureSnapshot(id, expanded, controlled, disabled);
const host = createControllerHost<DisclosureSnapshot, DisclosureEvents>(
snapshot(initialExpanded, controlled),
);
const sync = (): void => {
host.update(snapshot(state.get(), state.controlled));
};
const commit = (
expanded: boolean,
changeDetails?: ChangeDetails<DisclosureChangeReason>,
): void => {
sync();
Iif (!changeDetails) return;
const payload = { expanded, details: changeDetails };
host.emit(expanded ? 'open' : 'close', payload);
host.emit('stateChange', payload);
host.emit(expanded ? 'afterOpen' : 'afterClose', payload);
};
const state = createControllableValue<boolean, DisclosureChangeReason>(
{
defaultValue: options.defaultValue ?? false,
...(options.getValue ? { getValue: options.getValue } : {}),
...(options.onValueChange ? { onValueChange: options.onValueChange } : {}),
...(options.subscribeValue ? { subscribeValue: options.subscribeValue } : {}),
},
commit,
);
const change = (
expanded: boolean,
changeDetails: ChangeDetails<DisclosureChangeReason>,
): void => {
if (!host.alive() || disabled || state.get() === expanded) return;
const payload = { expanded, details: changeDetails };
if (!host.emit(expanded ? 'beforeOpen' : 'beforeClose', payload)) return;
if (state.set(expanded, changeDetails)) commit(expanded, changeDetails);
};
const fallback: ChangeDetails<DisclosureChangeReason> = { reason: 'programmatic' };
host.resources.add(() => state.destroy());
return {
getSnapshot: host.getSnapshot,
subscribe: host.subscribe,
on: host.on,
off: host.off,
once: host.once,
expand: (changeDetails = fallback) => change(true, changeDetails),
collapse: (changeDetails = fallback) => change(false, changeDetails),
toggle: (changeDetails = fallback) => change(!state.get(), changeDetails),
setDisabled(value) {
Iif (!host.alive() || disabled === value) return;
disabled = value;
sync();
},
handleTriggerClick(event) {
change(!state.get(), { reason: 'trigger', event });
},
handleTriggerKeyDown(event) {
if (!host.alive()) return;
if (event.key !== 'Enter' && event.key !== ' ') return;
event.preventDefault();
change(!state.get(), { reason: 'keyboard', event });
},
destroy: host.destroy,
};
}
/** Collapsible is the Disclosure primitive under intent-revealing naming. @public */
export type CollapsibleController = DisclosureController;
/** Creates a Collapsible by reusing the complete Disclosure state and accessibility contract. @public */
export function createCollapsible(options: DisclosureOptions = {}): CollapsibleController {
return createDisclosure(options);
}
|