@aiworker/sdk 1.24.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 (77) hide show
  1. package/API.md +575 -0
  2. package/DOCUMENTATION.md +120 -0
  3. package/GUEST_SERVICES.md +166 -0
  4. package/LICENSE +21 -0
  5. package/README.md +249 -0
  6. package/dist/api-version.d.ts +6 -0
  7. package/dist/api-version.js +5 -0
  8. package/dist/contract.d.ts +504 -0
  9. package/dist/contract.js +256 -0
  10. package/dist/host-version.d.ts +16 -0
  11. package/dist/host-version.js +50 -0
  12. package/dist/host.d.ts +107 -0
  13. package/dist/host.js +639 -0
  14. package/dist/index.d.ts +13 -0
  15. package/dist/index.js +8 -0
  16. package/dist/manifest.d.ts +303 -0
  17. package/dist/manifest.js +222 -0
  18. package/dist/parse.d.ts +293 -0
  19. package/dist/parse.js +398 -0
  20. package/dist/protocol.d.ts +1148 -0
  21. package/dist/protocol.js +473 -0
  22. package/dist/schemas.d.ts +4 -0
  23. package/dist/schemas.js +6 -0
  24. package/dist/scrollbar-style.d.ts +2 -0
  25. package/dist/scrollbar-style.js +32 -0
  26. package/dist/ui/badge.d.ts +10 -0
  27. package/dist/ui/badge.js +26 -0
  28. package/dist/ui/banner.d.ts +13 -0
  29. package/dist/ui/banner.js +48 -0
  30. package/dist/ui/button.d.ts +14 -0
  31. package/dist/ui/button.js +49 -0
  32. package/dist/ui/checkbox.d.ts +12 -0
  33. package/dist/ui/checkbox.js +45 -0
  34. package/dist/ui/dom.d.ts +15 -0
  35. package/dist/ui/dom.js +60 -0
  36. package/dist/ui/empty.d.ts +11 -0
  37. package/dist/ui/empty.js +44 -0
  38. package/dist/ui/field.d.ts +18 -0
  39. package/dist/ui/field.js +49 -0
  40. package/dist/ui/icons.d.ts +10 -0
  41. package/dist/ui/icons.js +23 -0
  42. package/dist/ui/index.d.ts +35 -0
  43. package/dist/ui/index.js +17 -0
  44. package/dist/ui/list.d.ts +26 -0
  45. package/dist/ui/list.js +90 -0
  46. package/dist/ui/menu.d.ts +20 -0
  47. package/dist/ui/menu.js +115 -0
  48. package/dist/ui/navigation.d.ts +18 -0
  49. package/dist/ui/navigation.js +38 -0
  50. package/dist/ui/option.d.ts +16 -0
  51. package/dist/ui/option.js +35 -0
  52. package/dist/ui/popup.d.ts +5 -0
  53. package/dist/ui/popup.js +43 -0
  54. package/dist/ui/progress.d.ts +11 -0
  55. package/dist/ui/progress.js +44 -0
  56. package/dist/ui/search.d.ts +11 -0
  57. package/dist/ui/search.js +62 -0
  58. package/dist/ui/select.d.ts +22 -0
  59. package/dist/ui/select.js +168 -0
  60. package/dist/ui/separator.d.ts +6 -0
  61. package/dist/ui/separator.js +26 -0
  62. package/dist/ui/spinner.d.ts +8 -0
  63. package/dist/ui/spinner.js +29 -0
  64. package/dist/ui/style.d.ts +1 -0
  65. package/dist/ui/style.js +202 -0
  66. package/dist/ui/tabs.d.ts +15 -0
  67. package/dist/ui/tabs.js +61 -0
  68. package/dist/ui/text.d.ts +23 -0
  69. package/dist/ui/text.js +89 -0
  70. package/dist/ui/theme.d.ts +22 -0
  71. package/dist/ui/theme.js +79 -0
  72. package/dist/workspace-schemas.d.ts +136 -0
  73. package/dist/workspace-schemas.js +44 -0
  74. package/dist/workspace.d.ts +109 -0
  75. package/dist/workspace.js +4 -0
  76. package/package.json +55 -0
  77. package/scripts/bundle-guest.ts +44 -0
@@ -0,0 +1,18 @@
1
+ /** Pure keyboard navigation shared by list, select, menu, and tabs. No DOM here. */
2
+ export type NavigableItem = {
3
+ id: string;
4
+ disabled?: boolean;
5
+ };
6
+ export type NavigationKey = 'next' | 'previous' | 'first' | 'last';
7
+ type KeyLike = {
8
+ key: string;
9
+ ctrlKey: boolean;
10
+ };
11
+ /** Maps a keyboard event to a navigation step. Ctrl+N / Ctrl+P mirror the arrow keys, as in the host. */
12
+ export declare const navigationKey: (event: KeyLike, axis?: "vertical" | "horizontal") => NavigationKey | null;
13
+ /**
14
+ * Returns the id that `key` lands on from `currentId`, skipping disabled items and
15
+ * stopping at the edges. Returns `null` when nothing is enabled.
16
+ */
17
+ export declare const moveListSelection: (items: readonly NavigableItem[], currentId: string | null, key: NavigationKey) => string | null;
18
+ export {};
@@ -0,0 +1,38 @@
1
+ /** Pure keyboard navigation shared by list, select, menu, and tabs. No DOM here. */
2
+ /** Maps a keyboard event to a navigation step. Ctrl+N / Ctrl+P mirror the arrow keys, as in the host. */
3
+ export const navigationKey = (event, axis = 'vertical') => {
4
+ const [next, previous] = axis === 'vertical' ? ['ArrowDown', 'ArrowUp'] : ['ArrowRight', 'ArrowLeft'];
5
+ if (event.key === next || (event.ctrlKey && event.key.toLowerCase() === 'n'))
6
+ return 'next';
7
+ if (event.key === previous || (event.ctrlKey && event.key.toLowerCase() === 'p'))
8
+ return 'previous';
9
+ if (event.key === 'Home')
10
+ return 'first';
11
+ if (event.key === 'End')
12
+ return 'last';
13
+ return null;
14
+ };
15
+ /**
16
+ * Returns the id that `key` lands on from `currentId`, skipping disabled items and
17
+ * stopping at the edges. Returns `null` when nothing is enabled.
18
+ */
19
+ export const moveListSelection = (items, currentId, key) => {
20
+ const enabled = items.filter((item) => !item.disabled);
21
+ if (enabled.length === 0) {
22
+ return null;
23
+ }
24
+ const first = enabled[0];
25
+ const last = enabled[enabled.length - 1];
26
+ if (key === 'first' || !first || !last) {
27
+ return first?.id ?? null;
28
+ }
29
+ if (key === 'last') {
30
+ return last.id;
31
+ }
32
+ const index = enabled.findIndex((item) => item.id === currentId);
33
+ if (index === -1) {
34
+ return key === 'next' ? first.id : last.id;
35
+ }
36
+ const target = enabled[Math.min(enabled.length - 1, Math.max(0, index + (key === 'next' ? 1 : -1)))];
37
+ return target?.id ?? null;
38
+ };
@@ -0,0 +1,16 @@
1
+ /** One popup row shared by select (`role="option"`) and menu (`role="menuitem"`). */
2
+ type OptionSpec = {
3
+ id: string;
4
+ label: string;
5
+ hint?: string;
6
+ selected?: boolean;
7
+ destructive?: boolean;
8
+ disabled?: boolean;
9
+ };
10
+ export declare const createOption: (uid: string, role: "option" | "menuitem", spec: OptionSpec, on: {
11
+ hover: () => void;
12
+ pick: () => void;
13
+ }) => HTMLButtonElement;
14
+ /** Moves the `data-active` highlight and `aria-activedescendant`; scrolls the row into view. */
15
+ export declare const highlightOption: (container: HTMLElement, focusOwner: HTMLElement, uid: string, id: string | null) => void;
16
+ export {};
@@ -0,0 +1,35 @@
1
+ import { button, el, setAttr } from "./dom.js";
2
+ const optionId = (uid, id) => `${uid}-${id ?? ''}`;
3
+ export const createOption = (uid, role, spec, on) => {
4
+ const node = button('oc-sdk-option');
5
+ node.id = optionId(uid, spec.id);
6
+ node.setAttribute('role', role);
7
+ node.tabIndex = -1;
8
+ node.disabled = Boolean(spec.disabled);
9
+ if (role === 'option') {
10
+ node.setAttribute('aria-selected', spec.selected ? 'true' : 'false');
11
+ }
12
+ node.dataset.destructive = spec.destructive ? 'true' : 'false';
13
+ const label = el('span', 'oc-sdk-option-label');
14
+ label.textContent = spec.label;
15
+ node.append(label);
16
+ if (spec.hint) {
17
+ const hint = el('span', 'oc-sdk-option-hint');
18
+ hint.textContent = spec.hint;
19
+ node.append(hint);
20
+ }
21
+ node.addEventListener('pointerenter', on.hover);
22
+ node.addEventListener('click', on.pick);
23
+ return node;
24
+ };
25
+ /** Moves the `data-active` highlight and `aria-activedescendant`; scrolls the row into view. */
26
+ export const highlightOption = (container, focusOwner, uid, id) => {
27
+ const target = optionId(uid, id);
28
+ for (const child of Array.from(container.children)) {
29
+ if (child instanceof HTMLElement && child.classList.contains('oc-sdk-option')) {
30
+ child.dataset.active = child.id === target ? 'true' : 'false';
31
+ }
32
+ }
33
+ setAttr(focusOwner, 'aria-activedescendant', id ? target : null);
34
+ container.querySelector('[data-active="true"]')?.scrollIntoView({ block: 'nearest' });
35
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Attaches `popup` next to `trigger` and wires the ways it closes: outside press,
3
+ * viewport resize, or scroll. Returns the disposer; the caller decides what `close` does.
4
+ */
5
+ export declare const openPopup: (host: Element, trigger: HTMLElement, popup: HTMLElement, close: () => void) => (() => void);
@@ -0,0 +1,43 @@
1
+ import { onOutsideClick } from "./dom.js";
2
+ /** Places a fixed popup under `trigger`; flips above when the room below is too small. */
3
+ const placePopup = (popup, trigger) => {
4
+ const rect = trigger.getBoundingClientRect();
5
+ popup.style.minWidth = `${Math.round(rect.width)}px`;
6
+ popup.style.left = `${Math.round(rect.left)}px`;
7
+ popup.style.top = `${Math.round(rect.bottom + 4)}px`;
8
+ const height = popup.offsetHeight;
9
+ const roomBelow = window.innerHeight - rect.bottom - 8;
10
+ if (height > roomBelow && rect.top - 8 > roomBelow) {
11
+ popup.style.top = `${Math.max(8, Math.round(rect.top - 4 - height))}px`;
12
+ }
13
+ const overflow = rect.left + popup.offsetWidth - window.innerWidth + 8;
14
+ if (overflow > 0) {
15
+ popup.style.left = `${Math.max(8, Math.round(rect.left - overflow))}px`;
16
+ }
17
+ };
18
+ /**
19
+ * Attaches `popup` next to `trigger` and wires the ways it closes: outside press,
20
+ * viewport resize, or scroll. Returns the disposer; the caller decides what `close` does.
21
+ */
22
+ export const openPopup = (host, trigger, popup, close) => {
23
+ host.append(popup);
24
+ placePopup(popup, trigger);
25
+ const stopOutside = onOutsideClick(host, close);
26
+ const onResize = () => {
27
+ close();
28
+ };
29
+ const onScroll = (event) => {
30
+ if (event.target instanceof Node && popup.contains(event.target)) {
31
+ return;
32
+ }
33
+ close();
34
+ };
35
+ window.addEventListener('resize', onResize);
36
+ window.addEventListener('scroll', onScroll, true);
37
+ return () => {
38
+ stopOutside();
39
+ window.removeEventListener('resize', onResize);
40
+ window.removeEventListener('scroll', onScroll, true);
41
+ popup.remove();
42
+ };
43
+ };
@@ -0,0 +1,11 @@
1
+ import { type Tone } from './badge.ts';
2
+ import { type Handle } from './dom.ts';
3
+ export type ProgressProps = {
4
+ /** 0 to 100. Values outside that range are clamped. */
5
+ value: number;
6
+ tone?: Tone;
7
+ label?: string;
8
+ };
9
+ export type ProgressHandle = Handle<ProgressProps>;
10
+ export declare const clampProgress: (value: number) => number;
11
+ export declare const mountProgress: (root: Element, initial: ProgressProps) => ProgressHandle;
@@ -0,0 +1,44 @@
1
+ import { applyTone } from "./badge.js";
2
+ import { el, ensureStyle, setText } from "./dom.js";
3
+ import { UI_CSS } from "./style.js";
4
+ export const clampProgress = (value) => (Number.isFinite(value) ? Math.min(100, Math.max(0, Math.round(value))) : 0);
5
+ export const mountProgress = (root, initial) => {
6
+ ensureStyle(UI_CSS);
7
+ let props = initial;
8
+ const node = el('div', 'oc-sdk oc-sdk-progress');
9
+ const caption = el('div', 'oc-sdk-progress-label');
10
+ const label = el('span');
11
+ const percent = el('span');
12
+ caption.append(label, percent);
13
+ const track = el('div', 'oc-sdk-progress-track');
14
+ track.setAttribute('role', 'progressbar');
15
+ track.setAttribute('aria-valuemin', '0');
16
+ track.setAttribute('aria-valuemax', '100');
17
+ const fill = el('div', 'oc-sdk-progress-fill');
18
+ track.append(fill);
19
+ node.append(caption, track);
20
+ root.append(node);
21
+ const paint = () => {
22
+ const value = clampProgress(props.value);
23
+ applyTone(fill, props.tone);
24
+ fill.style.transform = `scaleX(${value / 100})`;
25
+ track.setAttribute('aria-valuenow', String(value));
26
+ if (props.label)
27
+ track.setAttribute('aria-label', props.label);
28
+ else
29
+ track.removeAttribute('aria-label');
30
+ setText(label, props.label);
31
+ setText(percent, `${value}%`);
32
+ caption.hidden = !props.label;
33
+ };
34
+ paint();
35
+ return {
36
+ update: (next) => {
37
+ props = { ...props, ...next };
38
+ paint();
39
+ },
40
+ dispose: () => {
41
+ node.remove();
42
+ },
43
+ };
44
+ };
@@ -0,0 +1,11 @@
1
+ import { type Handle } from './dom.ts';
2
+ export type SearchFieldProps = {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ placeholder?: string;
6
+ /** Accessible name. Falls back to the placeholder. */
7
+ label?: string;
8
+ autofocus?: boolean;
9
+ };
10
+ export type SearchFieldHandle = Handle<SearchFieldProps>;
11
+ export declare const mountSearchField: (root: Element, initial: SearchFieldProps) => SearchFieldHandle;
@@ -0,0 +1,62 @@
1
+ import { button, el, ensureStyle, setAttr } from "./dom.js";
2
+ import { icon } from "./icons.js";
3
+ import { UI_CSS } from "./style.js";
4
+ export const mountSearchField = (root, initial) => {
5
+ ensureStyle(UI_CSS);
6
+ let props = initial;
7
+ const wrap = el('div', 'oc-sdk oc-sdk-search');
8
+ const input = el('input', 'oc-sdk-input');
9
+ input.type = 'text';
10
+ input.spellcheck = false;
11
+ input.autocomplete = 'off';
12
+ input.setAttribute('role', 'searchbox');
13
+ const clear = button('oc-sdk-search-clear');
14
+ clear.append(icon('close', 14));
15
+ clear.tabIndex = -1;
16
+ wrap.append(icon('search', 16, 'oc-sdk-search-icon'), input, clear);
17
+ root.append(wrap);
18
+ const paint = () => {
19
+ const placeholder = props.placeholder ?? 'Search';
20
+ setAttr(input, 'placeholder', placeholder);
21
+ input.setAttribute('aria-label', props.label ?? placeholder);
22
+ clear.setAttribute('aria-label', 'Clear search');
23
+ if (input.value !== props.value) {
24
+ input.value = props.value;
25
+ }
26
+ wrap.dataset.active = props.value.trim() === '' ? 'false' : 'true';
27
+ };
28
+ const clearValue = () => {
29
+ if (props.value !== '') {
30
+ props.onChange('');
31
+ }
32
+ input.focus();
33
+ };
34
+ const onInput = () => {
35
+ props.onChange(input.value);
36
+ };
37
+ const onKeyDown = (event) => {
38
+ if (event.key === 'Escape' && input.value !== '') {
39
+ event.preventDefault();
40
+ clearValue();
41
+ }
42
+ };
43
+ input.addEventListener('input', onInput);
44
+ input.addEventListener('keydown', onKeyDown);
45
+ clear.addEventListener('click', clearValue);
46
+ paint();
47
+ if (props.autofocus) {
48
+ input.focus();
49
+ }
50
+ return {
51
+ update: (next) => {
52
+ props = { ...props, ...next };
53
+ paint();
54
+ },
55
+ dispose: () => {
56
+ input.removeEventListener('input', onInput);
57
+ input.removeEventListener('keydown', onKeyDown);
58
+ clear.removeEventListener('click', clearValue);
59
+ wrap.remove();
60
+ },
61
+ };
62
+ };
@@ -0,0 +1,22 @@
1
+ import { type Handle } from './dom.ts';
2
+ export type SelectOption = {
3
+ id: string;
4
+ label: string;
5
+ /** Small muted text at the right of the option. */
6
+ hint?: string;
7
+ };
8
+ export type SelectProps = {
9
+ label?: string;
10
+ value: string | null;
11
+ options: SelectOption[];
12
+ onChange: (id: string) => void;
13
+ placeholder?: string;
14
+ /** Adds a search box at the top of the popup. */
15
+ searchable?: boolean;
16
+ searchPlaceholder?: string;
17
+ disabled?: boolean;
18
+ };
19
+ export type SelectHandle = Handle<SelectProps>;
20
+ /** Case-insensitive substring match on label and id. Empty query keeps every option. */
21
+ export declare const filterSelectOptions: (options: readonly SelectOption[], query: string) => SelectOption[];
22
+ export declare const mountSelect: (root: Element, initial: SelectProps) => SelectHandle;
@@ -0,0 +1,168 @@
1
+ import { button, clearNode, el, ensureStyle, setText } from "./dom.js";
2
+ import { icon } from "./icons.js";
3
+ import { moveListSelection, navigationKey } from "./navigation.js";
4
+ import { createOption, highlightOption } from "./option.js";
5
+ import { openPopup } from "./popup.js";
6
+ import { UI_CSS } from "./style.js";
7
+ /** Case-insensitive substring match on label and id. Empty query keeps every option. */
8
+ export const filterSelectOptions = (options, query) => {
9
+ const needle = query.trim().toLowerCase();
10
+ if (!needle) {
11
+ return [...options];
12
+ }
13
+ return options.filter((option) => (option.label.toLowerCase().includes(needle) || option.id.toLowerCase().includes(needle)));
14
+ };
15
+ let selectCount = 0;
16
+ export const mountSelect = (root, initial) => {
17
+ ensureStyle(UI_CSS);
18
+ let props = initial;
19
+ const uid = `oc-sdk-select-${selectCount += 1}`;
20
+ const wrap = el('div', 'oc-sdk oc-sdk-select');
21
+ const caption = el('span', 'oc-sdk-field-label');
22
+ caption.id = `${uid}-label`;
23
+ const trigger = button('oc-sdk-trigger');
24
+ trigger.setAttribute('aria-haspopup', 'listbox');
25
+ trigger.setAttribute('aria-labelledby', caption.id);
26
+ const value = el('span', 'oc-sdk-trigger-value');
27
+ trigger.append(value, icon('chevron', 14, 'oc-sdk-trigger-chevron'));
28
+ wrap.append(caption, trigger);
29
+ root.append(wrap);
30
+ let query = '';
31
+ let activeId = null;
32
+ let closePopup = null;
33
+ const popup = el('div', 'oc-sdk oc-sdk-popup');
34
+ const searchSlot = el('div', 'oc-sdk-popup-search');
35
+ const search = el('input', 'oc-sdk-input');
36
+ search.type = 'text';
37
+ search.autocomplete = 'off';
38
+ searchSlot.append(search);
39
+ const listbox = el('div');
40
+ listbox.setAttribute('role', 'listbox');
41
+ listbox.tabIndex = -1;
42
+ popup.append(listbox);
43
+ const visible = () => filterSelectOptions(props.options, props.searchable ? query : '');
44
+ const focusOwner = () => (props.searchable ? search : listbox);
45
+ const setActive = (id) => {
46
+ activeId = id;
47
+ highlightOption(listbox, focusOwner(), uid, id);
48
+ };
49
+ const paintOptions = () => {
50
+ clearNode(listbox);
51
+ const options = visible();
52
+ if (options.length === 0) {
53
+ const empty = el('div', 'oc-sdk-popup-empty');
54
+ empty.textContent = 'No matches';
55
+ listbox.append(empty);
56
+ }
57
+ for (const option of options) {
58
+ listbox.append(createOption(uid, 'option', { ...option, selected: option.id === props.value }, {
59
+ hover: () => setActive(option.id),
60
+ pick: () => pick(option.id),
61
+ }));
62
+ }
63
+ setActive(options.some((option) => option.id === activeId) ? activeId : options[0]?.id ?? null);
64
+ };
65
+ const close = () => {
66
+ // Removing a focused row fires `focusout` synchronously from inside the disposer, and
67
+ // that handler calls `close` again. Clearing the slot first makes the re-entry a no-op
68
+ // instead of a second `popup.remove()` that throws before the pick reaches `onChange`.
69
+ const dispose = closePopup;
70
+ closePopup = null;
71
+ dispose?.();
72
+ query = '';
73
+ search.value = '';
74
+ trigger.setAttribute('aria-expanded', 'false');
75
+ };
76
+ const open = () => {
77
+ if (closePopup || props.disabled) {
78
+ return;
79
+ }
80
+ activeId = props.value;
81
+ search.placeholder = props.searchPlaceholder ?? 'Search';
82
+ if (props.searchable)
83
+ popup.prepend(searchSlot);
84
+ else
85
+ searchSlot.remove();
86
+ paintOptions();
87
+ closePopup = openPopup(wrap, trigger, popup, close);
88
+ trigger.setAttribute('aria-expanded', 'true');
89
+ focusOwner().focus();
90
+ };
91
+ const pick = (id) => {
92
+ close();
93
+ trigger.focus();
94
+ if (id !== props.value)
95
+ props.onChange(id);
96
+ };
97
+ const onTriggerClick = () => {
98
+ if (closePopup)
99
+ close();
100
+ else
101
+ open();
102
+ };
103
+ const onTriggerKey = (event) => {
104
+ if (!closePopup && navigationKey(event)) {
105
+ event.preventDefault();
106
+ open();
107
+ }
108
+ };
109
+ const onPopupKey = (event) => {
110
+ const step = navigationKey(event);
111
+ if (step) {
112
+ event.preventDefault();
113
+ setActive(moveListSelection(visible(), activeId, step));
114
+ }
115
+ else if (event.key === 'Enter') {
116
+ event.preventDefault();
117
+ if (activeId)
118
+ pick(activeId);
119
+ }
120
+ else if (event.key === 'Escape') {
121
+ event.preventDefault();
122
+ close();
123
+ trigger.focus();
124
+ }
125
+ else if (props.searchable && event.target !== search && event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
126
+ search.focus();
127
+ }
128
+ };
129
+ const onSearchInput = () => {
130
+ query = search.value;
131
+ paintOptions();
132
+ };
133
+ const onFocusOut = (event) => {
134
+ if (closePopup && !(event.relatedTarget instanceof Node && wrap.contains(event.relatedTarget)))
135
+ close();
136
+ };
137
+ const paint = () => {
138
+ setText(caption, props.label);
139
+ caption.hidden = !props.label;
140
+ const current = props.options.find((option) => option.id === props.value);
141
+ setText(value, current?.label ?? props.placeholder ?? 'Select');
142
+ value.dataset.empty = current ? 'false' : 'true';
143
+ trigger.disabled = Boolean(props.disabled);
144
+ if (closePopup)
145
+ paintOptions();
146
+ };
147
+ trigger.addEventListener('click', onTriggerClick);
148
+ trigger.addEventListener('keydown', onTriggerKey);
149
+ popup.addEventListener('keydown', onPopupKey);
150
+ search.addEventListener('input', onSearchInput);
151
+ wrap.addEventListener('focusout', onFocusOut);
152
+ paint();
153
+ return {
154
+ update: (next) => {
155
+ props = { ...props, ...next };
156
+ paint();
157
+ },
158
+ dispose: () => {
159
+ close();
160
+ trigger.removeEventListener('click', onTriggerClick);
161
+ trigger.removeEventListener('keydown', onTriggerKey);
162
+ popup.removeEventListener('keydown', onPopupKey);
163
+ search.removeEventListener('input', onSearchInput);
164
+ wrap.removeEventListener('focusout', onFocusOut);
165
+ wrap.remove();
166
+ },
167
+ };
168
+ };
@@ -0,0 +1,6 @@
1
+ import { type Handle } from './dom.ts';
2
+ export type SeparatorProps = {
3
+ label?: string;
4
+ };
5
+ export type SeparatorHandle = Handle<SeparatorProps>;
6
+ export declare const mountSeparator: (root: Element, initial?: SeparatorProps) => SeparatorHandle;
@@ -0,0 +1,26 @@
1
+ import { el, ensureStyle, setText } from "./dom.js";
2
+ import { UI_CSS } from "./style.js";
3
+ export const mountSeparator = (root, initial = {}) => {
4
+ ensureStyle(UI_CSS);
5
+ let props = initial;
6
+ const node = el('div', 'oc-sdk oc-sdk-separator');
7
+ node.setAttribute('role', 'separator');
8
+ const label = el('span');
9
+ node.append(label);
10
+ root.append(node);
11
+ const paint = () => {
12
+ setText(label, props.label);
13
+ label.hidden = !props.label;
14
+ node.dataset.labeled = props.label ? 'true' : 'false';
15
+ };
16
+ paint();
17
+ return {
18
+ update: (next) => {
19
+ props = { ...props, ...next };
20
+ paint();
21
+ },
22
+ dispose: () => {
23
+ node.remove();
24
+ },
25
+ };
26
+ };
@@ -0,0 +1,8 @@
1
+ import { type Handle } from './dom.ts';
2
+ export type SpinnerProps = {
3
+ size?: 'sm' | 'default';
4
+ /** Text next to the ring. Also the accessible name. */
5
+ label?: string;
6
+ };
7
+ export type SpinnerHandle = Handle<SpinnerProps>;
8
+ export declare const mountSpinner: (root: Element, initial?: SpinnerProps) => SpinnerHandle;
@@ -0,0 +1,29 @@
1
+ import { el, ensureStyle, setText } from "./dom.js";
2
+ import { UI_CSS } from "./style.js";
3
+ export const mountSpinner = (root, initial = {}) => {
4
+ ensureStyle(UI_CSS);
5
+ let props = initial;
6
+ const node = el('span', 'oc-sdk oc-sdk-spinner');
7
+ node.setAttribute('role', 'status');
8
+ const ring = el('span', 'oc-sdk-spinner-ring');
9
+ ring.setAttribute('aria-hidden', 'true');
10
+ const label = el('span');
11
+ node.append(ring, label);
12
+ root.append(node);
13
+ const paint = () => {
14
+ node.dataset.size = props.size ?? 'default';
15
+ setText(label, props.label);
16
+ label.hidden = !props.label;
17
+ node.setAttribute('aria-label', props.label ?? 'Loading');
18
+ };
19
+ paint();
20
+ return {
21
+ update: (next) => {
22
+ props = { ...props, ...next };
23
+ paint();
24
+ },
25
+ dispose: () => {
26
+ node.remove();
27
+ },
28
+ };
29
+ };
@@ -0,0 +1 @@
1
+ export declare const UI_CSS: string;