@agnos-ui/core 0.0.1-alpha.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 (61) hide show
  1. package/README.md +15 -0
  2. package/dist/lib/accordion.d.ts +318 -0
  3. package/dist/lib/accordion.js +263 -0
  4. package/dist/lib/alert.d.ts +100 -0
  5. package/dist/lib/alert.js +66 -0
  6. package/dist/lib/config.d.ts +71 -0
  7. package/dist/lib/config.js +53 -0
  8. package/dist/lib/index.d.ts +11 -0
  9. package/dist/lib/index.js +11 -0
  10. package/dist/lib/modal/modal.d.ts +318 -0
  11. package/dist/lib/modal/modal.js +156 -0
  12. package/dist/lib/modal/scrollbars.d.ts +2 -0
  13. package/dist/lib/modal/scrollbars.js +27 -0
  14. package/dist/lib/pagination.d.ts +464 -0
  15. package/dist/lib/pagination.js +148 -0
  16. package/dist/lib/pagination.utils.d.ts +8 -0
  17. package/dist/lib/pagination.utils.js +110 -0
  18. package/dist/lib/rating.d.ts +209 -0
  19. package/dist/lib/rating.js +141 -0
  20. package/dist/lib/select.d.ts +199 -0
  21. package/dist/lib/select.js +240 -0
  22. package/dist/lib/services/checks.d.ts +32 -0
  23. package/dist/lib/services/checks.js +43 -0
  24. package/dist/lib/services/directiveUtils.d.ts +95 -0
  25. package/dist/lib/services/directiveUtils.js +190 -0
  26. package/dist/lib/services/focustrack.d.ts +19 -0
  27. package/dist/lib/services/focustrack.js +46 -0
  28. package/dist/lib/services/index.d.ts +6 -0
  29. package/dist/lib/services/index.js +6 -0
  30. package/dist/lib/services/portal.d.ts +6 -0
  31. package/dist/lib/services/portal.js +33 -0
  32. package/dist/lib/services/siblingsInert.d.ts +7 -0
  33. package/dist/lib/services/siblingsInert.js +40 -0
  34. package/dist/lib/services/stores.d.ts +140 -0
  35. package/dist/lib/services/stores.js +219 -0
  36. package/dist/lib/services/writables.d.ts +7 -0
  37. package/dist/lib/services/writables.js +16 -0
  38. package/dist/lib/transitions/baseTransitions.d.ts +136 -0
  39. package/dist/lib/transitions/baseTransitions.js +171 -0
  40. package/dist/lib/transitions/bootstrap/collapse.d.ts +2 -0
  41. package/dist/lib/transitions/bootstrap/collapse.js +15 -0
  42. package/dist/lib/transitions/bootstrap/fade.d.ts +1 -0
  43. package/dist/lib/transitions/bootstrap/fade.js +7 -0
  44. package/dist/lib/transitions/bootstrap/index.d.ts +2 -0
  45. package/dist/lib/transitions/bootstrap/index.js +2 -0
  46. package/dist/lib/transitions/collapse.d.ts +29 -0
  47. package/dist/lib/transitions/collapse.js +39 -0
  48. package/dist/lib/transitions/cssTransitions.d.ts +15 -0
  49. package/dist/lib/transitions/cssTransitions.js +38 -0
  50. package/dist/lib/transitions/index.d.ts +5 -0
  51. package/dist/lib/transitions/index.js +5 -0
  52. package/dist/lib/transitions/simpleClassTransition.d.ts +29 -0
  53. package/dist/lib/transitions/simpleClassTransition.js +28 -0
  54. package/dist/lib/transitions/utils.d.ts +20 -0
  55. package/dist/lib/transitions/utils.js +83 -0
  56. package/dist/lib/tsdoc-metadata.json +11 -0
  57. package/dist/lib/types.d.ts +58 -0
  58. package/dist/lib/types.js +7 -0
  59. package/dist/lib/utils.d.ts +2 -0
  60. package/dist/lib/utils.js +2 -0
  61. package/package.json +52 -0
@@ -0,0 +1,240 @@
1
+ import { asReadable, batch, computed, writable } from '@amadeus-it-group/tansu';
2
+ import { createHasFocus } from './services/focustrack';
3
+ import { stateStores, writablesForProps } from './services/stores';
4
+ function defaultMatchFn(item, text) {
5
+ return JSON.stringify(item).toLowerCase().includes(text.toLowerCase());
6
+ }
7
+ function defaultItemId(item) {
8
+ return '' + item;
9
+ }
10
+ const defaultConfig = {
11
+ opened: false,
12
+ disabled: false,
13
+ items: [],
14
+ filterText: '',
15
+ loading: false,
16
+ selected: [],
17
+ itemId: defaultItemId,
18
+ matchFn: defaultMatchFn,
19
+ onFilterTextChange: undefined,
20
+ className: '',
21
+ };
22
+ /**
23
+ * Create a SelectWidget with given config props
24
+ * @param config - an optional alert config
25
+ * @returns a SelectWidget
26
+ */
27
+ export function createSelect(config) {
28
+ // Props
29
+ const [{ opened$: _dirtyOpened$, items$, itemId$, matchFn$, onFilterTextChange$, ...otherProps }, patch] = writablesForProps(defaultConfig, config);
30
+ const { selected$, filterText$ } = otherProps;
31
+ const { hasFocus$, directive: hasFocusDirective } = createHasFocus();
32
+ const opened$ = computed(() => {
33
+ const _dirtyOpened = _dirtyOpened$();
34
+ const hasFocus = hasFocus$();
35
+ if (!hasFocus && _dirtyOpened) {
36
+ _dirtyOpened$.set(false);
37
+ }
38
+ return _dirtyOpened && hasFocus;
39
+ });
40
+ const highlightedIndex$ = (function () {
41
+ const store = writable(0);
42
+ const newStore = asReadable(store, {
43
+ set(index) {
44
+ const { length } = visible$();
45
+ if (index != undefined) {
46
+ if (!length) {
47
+ index = undefined;
48
+ }
49
+ else if (index < 0) {
50
+ index = length - 1;
51
+ }
52
+ else if (index >= length) {
53
+ index = 0;
54
+ }
55
+ }
56
+ store.set(index);
57
+ },
58
+ update(fn) {
59
+ newStore.set(fn(store()));
60
+ },
61
+ });
62
+ return newStore;
63
+ })();
64
+ const visible$ = computed(() => {
65
+ const list = [];
66
+ if (opened$()) {
67
+ const selected = selected$();
68
+ const filterText = filterText$();
69
+ const matchFn = !filterText ? () => true : matchFn$();
70
+ const itemId = itemId$();
71
+ for (const item of items$()) {
72
+ if (matchFn(item, filterText)) {
73
+ list.push({
74
+ item,
75
+ id: itemId(item),
76
+ selected: selected.includes(item),
77
+ select: function () {
78
+ widget.api.select(this);
79
+ }.bind(item),
80
+ unselect: function () {
81
+ widget.api.unselect(this);
82
+ }.bind(item),
83
+ toggle: function () {
84
+ widget.api.toggleItem(this);
85
+ }.bind(item),
86
+ });
87
+ }
88
+ }
89
+ }
90
+ return list;
91
+ });
92
+ const highlighted$ = computed(() => {
93
+ const visible = visible$();
94
+ const highlightedIndex = highlightedIndex$();
95
+ return visible.length && highlightedIndex != undefined ? visible[highlightedIndex] : undefined;
96
+ });
97
+ const widget = {
98
+ ...stateStores({
99
+ visible$,
100
+ highlighted$,
101
+ opened$,
102
+ ...otherProps,
103
+ }),
104
+ patch,
105
+ api: {
106
+ clear() {
107
+ selected$.set([]);
108
+ },
109
+ select(item) {
110
+ widget.api.toggleItem(item, true);
111
+ },
112
+ unselect(item) {
113
+ widget.api.toggleItem(item, false);
114
+ },
115
+ toggleItem(item, selected) {
116
+ if (!items$().includes(item)) {
117
+ return;
118
+ }
119
+ selected$.update((selectedItems) => {
120
+ selectedItems = [...selectedItems];
121
+ const index = selectedItems.indexOf(item);
122
+ if (selected == null) {
123
+ selected = index === -1;
124
+ }
125
+ if (selected && index === -1) {
126
+ selectedItems.push(item);
127
+ }
128
+ else if (!selected && index !== -1) {
129
+ selectedItems.splice(index, 1);
130
+ }
131
+ return selectedItems;
132
+ });
133
+ },
134
+ clearText() {
135
+ // FIXME: not implemented yet!
136
+ },
137
+ highlight(item) {
138
+ const index = visible$().findIndex((itemCtx) => itemCtx.item === item);
139
+ highlightedIndex$.set(index === -1 ? undefined : index);
140
+ },
141
+ highlightFirst() {
142
+ highlightedIndex$.set(0);
143
+ },
144
+ highlightPrevious() {
145
+ highlightedIndex$.update((highlightedIndex) => {
146
+ return highlightedIndex != null ? highlightedIndex - 1 : -1;
147
+ });
148
+ },
149
+ highlightNext() {
150
+ highlightedIndex$.update((highlightedIndex) => {
151
+ return highlightedIndex != null ? highlightedIndex + 1 : Infinity;
152
+ });
153
+ },
154
+ highlightLast() {
155
+ highlightedIndex$.set(-1);
156
+ },
157
+ focus(item) {
158
+ // FIXME: not implemented yet!
159
+ },
160
+ focusFirst() {
161
+ // FIXME: not implemented yet!
162
+ },
163
+ focusPrevious() {
164
+ // FIXME: not implemented yet!
165
+ },
166
+ focusNext() {
167
+ // FIXME: not implemented yet!
168
+ },
169
+ focusLast() {
170
+ // FIXME: not implemented yet!
171
+ },
172
+ open: () => widget.api.toggle(true),
173
+ close: () => widget.api.toggle(false),
174
+ toggle(isOpen) {
175
+ _dirtyOpened$.update((value) => (isOpen != null ? isOpen : !value));
176
+ },
177
+ },
178
+ directives: {
179
+ hasFocusDirective,
180
+ },
181
+ actions: {
182
+ onInput({ target }) {
183
+ const value = target.value;
184
+ batch(() => {
185
+ patch({
186
+ opened: value != null && value !== '',
187
+ filterText: value,
188
+ });
189
+ onFilterTextChange$()?.(value);
190
+ });
191
+ },
192
+ onInputKeydown(e) {
193
+ const { ctrlKey, key } = e;
194
+ let keyManaged = true;
195
+ switch (key) {
196
+ case 'ArrowDown': {
197
+ const isOpen = opened$();
198
+ if (isOpen) {
199
+ if (ctrlKey) {
200
+ widget.api.highlightLast();
201
+ }
202
+ else {
203
+ widget.api.highlightNext();
204
+ }
205
+ }
206
+ else {
207
+ widget.api.open();
208
+ widget.api.highlightFirst();
209
+ }
210
+ break;
211
+ }
212
+ case 'ArrowUp':
213
+ if (ctrlKey) {
214
+ widget.api.highlightFirst();
215
+ }
216
+ else {
217
+ widget.api.highlightPrevious();
218
+ }
219
+ break;
220
+ case 'Enter': {
221
+ const itemCtx = highlighted$();
222
+ if (itemCtx) {
223
+ widget.api.toggleItem(itemCtx.item);
224
+ }
225
+ break;
226
+ }
227
+ case 'Escape':
228
+ _dirtyOpened$.set(false);
229
+ break;
230
+ default:
231
+ keyManaged = false;
232
+ }
233
+ if (keyManaged) {
234
+ e.preventDefault();
235
+ }
236
+ },
237
+ },
238
+ };
239
+ return widget;
240
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * a number type guard
3
+ * @param value the value to check
4
+ * @returns true if the value is a number
5
+ */
6
+ export declare function isNumber(value: any): value is number;
7
+ /**
8
+ * a boolean type guard
9
+ * @param value the value to check
10
+ * @returns true if the value is a boolean
11
+ */
12
+ export declare function isBoolean(value: any): value is boolean;
13
+ /**
14
+ * a function type guard
15
+ * @param value the value to check
16
+ * @returns true if the value is a function
17
+ */
18
+ export declare function isFunction(value: any): value is (...args: any[]) => any;
19
+ /**
20
+ * a string type guard
21
+ * @param value the value to check
22
+ * @returns true if the value is a string
23
+ */
24
+ export declare function isString(value: any): value is string;
25
+ /**
26
+ * Clamp the value based on a maximum and optional minimum
27
+ * @param value the value to check
28
+ * @param max the max to clamp to
29
+ * @param [min] the min to clamp to
30
+ * @returns the clamped value
31
+ */
32
+ export declare function clamp(value: number, max: number, min?: number): number;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * a number type guard
3
+ * @param value the value to check
4
+ * @returns true if the value is a number
5
+ */
6
+ export function isNumber(value) {
7
+ return typeof value === 'number' && !isNaN(value) && Number.isFinite(value);
8
+ }
9
+ /**
10
+ * a boolean type guard
11
+ * @param value the value to check
12
+ * @returns true if the value is a boolean
13
+ */
14
+ export function isBoolean(value) {
15
+ return value === true || value === false;
16
+ }
17
+ /**
18
+ * a function type guard
19
+ * @param value the value to check
20
+ * @returns true if the value is a function
21
+ */
22
+ export function isFunction(value) {
23
+ return typeof value === 'function';
24
+ }
25
+ /**
26
+ * a string type guard
27
+ * @param value the value to check
28
+ * @returns true if the value is a string
29
+ */
30
+ export function isString(value) {
31
+ return typeof value === 'string';
32
+ }
33
+ // TODO should we check that max > min?
34
+ /**
35
+ * Clamp the value based on a maximum and optional minimum
36
+ * @param value the value to check
37
+ * @param max the max to clamp to
38
+ * @param [min] the min to clamp to
39
+ * @returns the clamped value
40
+ */
41
+ export function clamp(value, max, min = 0) {
42
+ return Math.max(Math.min(value, max), min);
43
+ }
@@ -0,0 +1,95 @@
1
+ import type { ReadableSignal } from '@amadeus-it-group/tansu';
2
+ import type { Directive } from '../types';
3
+ /**
4
+ * Binds the given directive to a store that provides its argument.
5
+ *
6
+ * @remarks
7
+ *
8
+ * The returned directive can be used without argument, it will ignore any argument passed to it
9
+ * and will call the provided directive with the content of the provided store as its argument,
10
+ * calling its update method when the content of the store changes.
11
+ *
12
+ * @param directive - directive to bind
13
+ * @param directiveArg$ - store containing the argument of the directive
14
+ * @returns The bound directive that can be used with no argument.
15
+ */
16
+ export declare const bindDirective: <T>(directive: Directive<T>, directiveArg$: ReadableSignal<T>) => Directive;
17
+ /**
18
+ * Returns a directive that ignores any argument passed to it and calls the provided directive without any
19
+ * argument.
20
+ *
21
+ * @param directive - directive to wrap
22
+ * @returns The resulting directive.
23
+ */
24
+ export declare const bindDirectiveNoArg: <T>(directive: Directive<void | T>) => Directive;
25
+ /**
26
+ * Returns a directive that subscribes to the given store while it is used on a DOM element,
27
+ * and that unsubscribes from it when it is no longer used.
28
+ *
29
+ * @param store - store on which there will be an active subscription while the returned directive is used.
30
+ * @param asyncUnsubscribe - true if unsubscribing from the store should be done asynchronously (which is the default), and
31
+ * false if it should be done synchronously when the directive is destroyed
32
+ * @returns The resulting directive.
33
+ */
34
+ export declare const directiveSubscribe: (store: ReadableSignal<any>, asyncUnsubscribe?: boolean) => Directive;
35
+ /**
36
+ * Returns a directive that calls the provided function with the arguments passed to the directive
37
+ * on initialization and each time they are updated.
38
+ *
39
+ * @param update - Function called with the directive argument when the directive is initialized and when its argument is updated.
40
+ * @returns The resulting directive.
41
+ */
42
+ export declare const directiveUpdate: <T>(update: (arg: T) => void) => Directive<T>;
43
+ /**
44
+ * Utility to create a store that contains an array of items.
45
+ * @returns a store containing an array of items.
46
+ */
47
+ export declare const registrationArray: <T>() => ReadableSignal<T[]> & {
48
+ register: (element: T) => () => void;
49
+ };
50
+ /**
51
+ * Returns a directive and a store. The store contains at any time the array of all the DOM elements on which the directive is
52
+ * currently used.
53
+ *
54
+ * @remarks
55
+ * If the directive is intended to be used on a single element element, it may be more appropriate to use
56
+ * {@link createStoreDirective} instead.
57
+ *
58
+ * @returns An object with two properties: the `directive` property that is the directive to use on some DOM elements,
59
+ * and the `elements$` property that is the store containing an array of all the elements on which the directive is currently
60
+ * used.
61
+ */
62
+ export declare const createStoreArrayDirective: () => {
63
+ directive: Directive;
64
+ elements$: ReadableSignal<HTMLElement[]>;
65
+ };
66
+ /**
67
+ * Returns a directive and a store. When the directive is used on a DOM element, the store contains that DOM element.
68
+ * When the directive is not used, the store contains null.
69
+ *
70
+ * @remarks
71
+ * If the directive is used on more than one element, an error is displayed in the console and the element is ignored.
72
+ * If the directive is intended to be used on more than one element, please use {@link createStoreArrayDirective} instead.
73
+ *
74
+ * @returns An object with two properties: the `directive` property that is the directive to use on one DOM element,
75
+ * and the `element$` property that is the store containing the element on which the directive is currently used (or null
76
+ * if the store is not currently used).
77
+ */
78
+ export declare const createStoreDirective: () => {
79
+ directive: Directive;
80
+ element$: ReadableSignal<HTMLElement | null>;
81
+ };
82
+ /**
83
+ * Merges multiple directives into a single directive that executes all of them when called.
84
+ *
85
+ * @remarks
86
+ * All directives receive the same argument upon initialization and update.
87
+ * Directives are created and updated in the same order as they appear in the arguments list,
88
+ * they are destroyed in the reverse order.
89
+ * All calls to the directives (to create, update and destroy them) are wrapped in a call to the
90
+ * batch function of tansu
91
+ *
92
+ * @param args - directives to merge into a single directive.
93
+ * @returns The resulting merged directive.
94
+ */
95
+ export declare const mergeDirectives: <T>(...args: (Directive | Directive<T>)[]) => Directive<T>;
@@ -0,0 +1,190 @@
1
+ import { asReadable, batch, readable, writable } from '@amadeus-it-group/tansu';
2
+ import { noop } from '../utils';
3
+ /**
4
+ * Binds the given directive to a store that provides its argument.
5
+ *
6
+ * @remarks
7
+ *
8
+ * The returned directive can be used without argument, it will ignore any argument passed to it
9
+ * and will call the provided directive with the content of the provided store as its argument,
10
+ * calling its update method when the content of the store changes.
11
+ *
12
+ * @param directive - directive to bind
13
+ * @param directiveArg$ - store containing the argument of the directive
14
+ * @returns The bound directive that can be used with no argument.
15
+ */
16
+ export const bindDirective = (directive, directiveArg$) => {
17
+ return (element) => {
18
+ let firstTime = true;
19
+ let instance;
20
+ const unsubscribe = directiveArg$.subscribe((value) => {
21
+ if (firstTime) {
22
+ firstTime = false;
23
+ instance = directive(element, value);
24
+ }
25
+ else {
26
+ instance?.update?.(value);
27
+ }
28
+ });
29
+ return {
30
+ destroy() {
31
+ instance?.destroy?.();
32
+ unsubscribe();
33
+ },
34
+ };
35
+ };
36
+ };
37
+ const noArg = readable(undefined);
38
+ /**
39
+ * Returns a directive that ignores any argument passed to it and calls the provided directive without any
40
+ * argument.
41
+ *
42
+ * @param directive - directive to wrap
43
+ * @returns The resulting directive.
44
+ */
45
+ export const bindDirectiveNoArg = (directive) => bindDirective(directive, noArg);
46
+ /**
47
+ * Returns a directive that subscribes to the given store while it is used on a DOM element,
48
+ * and that unsubscribes from it when it is no longer used.
49
+ *
50
+ * @param store - store on which there will be an active subscription while the returned directive is used.
51
+ * @param asyncUnsubscribe - true if unsubscribing from the store should be done asynchronously (which is the default), and
52
+ * false if it should be done synchronously when the directive is destroyed
53
+ * @returns The resulting directive.
54
+ */
55
+ export const directiveSubscribe = (store, asyncUnsubscribe = true) => () => {
56
+ const unsubscribe = store.subscribe(noop);
57
+ return {
58
+ destroy: async () => {
59
+ if (asyncUnsubscribe) {
60
+ await 0;
61
+ }
62
+ unsubscribe();
63
+ },
64
+ };
65
+ };
66
+ /**
67
+ * Returns a directive that calls the provided function with the arguments passed to the directive
68
+ * on initialization and each time they are updated.
69
+ *
70
+ * @param update - Function called with the directive argument when the directive is initialized and when its argument is updated.
71
+ * @returns The resulting directive.
72
+ */
73
+ export const directiveUpdate = (update) => (element, arg) => {
74
+ update(arg);
75
+ return {
76
+ update,
77
+ };
78
+ };
79
+ const equalOption = { equal: Object.is };
80
+ /**
81
+ * Utility to create a store that contains an array of items.
82
+ * @returns a store containing an array of items.
83
+ */
84
+ export const registrationArray = () => {
85
+ const elements$ = writable([], equalOption);
86
+ return asReadable(elements$, {
87
+ /**
88
+ * Add the given element to the array.
89
+ * @param element - Element to be added to the array.
90
+ * @returns A function to remove the element from the array.
91
+ */
92
+ register: (element) => {
93
+ let removed = false;
94
+ elements$.update((currentElements) => [...currentElements, element]);
95
+ return () => {
96
+ if (!removed) {
97
+ removed = true;
98
+ elements$.update((currentElements) => {
99
+ const index = currentElements.indexOf(element);
100
+ if (index > -1) {
101
+ const copy = [...currentElements];
102
+ copy.splice(index, 1);
103
+ return copy;
104
+ }
105
+ return currentElements; // no change
106
+ });
107
+ }
108
+ };
109
+ },
110
+ });
111
+ };
112
+ /**
113
+ * Returns a directive and a store. The store contains at any time the array of all the DOM elements on which the directive is
114
+ * currently used.
115
+ *
116
+ * @remarks
117
+ * If the directive is intended to be used on a single element element, it may be more appropriate to use
118
+ * {@link createStoreDirective} instead.
119
+ *
120
+ * @returns An object with two properties: the `directive` property that is the directive to use on some DOM elements,
121
+ * and the `elements$` property that is the store containing an array of all the elements on which the directive is currently
122
+ * used.
123
+ */
124
+ export const createStoreArrayDirective = () => {
125
+ const elements$ = registrationArray();
126
+ return {
127
+ elements$: asReadable(elements$),
128
+ directive: (element) => ({ destroy: elements$.register(element) }),
129
+ };
130
+ };
131
+ /**
132
+ * Returns a directive and a store. When the directive is used on a DOM element, the store contains that DOM element.
133
+ * When the directive is not used, the store contains null.
134
+ *
135
+ * @remarks
136
+ * If the directive is used on more than one element, an error is displayed in the console and the element is ignored.
137
+ * If the directive is intended to be used on more than one element, please use {@link createStoreArrayDirective} instead.
138
+ *
139
+ * @returns An object with two properties: the `directive` property that is the directive to use on one DOM element,
140
+ * and the `element$` property that is the store containing the element on which the directive is currently used (or null
141
+ * if the store is not currently used).
142
+ */
143
+ export const createStoreDirective = () => {
144
+ const element$ = writable(null, equalOption);
145
+ return {
146
+ element$: asReadable(element$),
147
+ directive: (element) => {
148
+ let valid = false;
149
+ element$.update((currentElement) => {
150
+ if (currentElement) {
151
+ console.error('The directive cannot be used on multiple elements.', currentElement, element);
152
+ return currentElement;
153
+ }
154
+ valid = true;
155
+ return element;
156
+ });
157
+ return valid
158
+ ? {
159
+ destroy() {
160
+ element$.update((currentElement) => (element === currentElement ? null : currentElement));
161
+ },
162
+ }
163
+ : undefined;
164
+ },
165
+ };
166
+ };
167
+ /**
168
+ * Merges multiple directives into a single directive that executes all of them when called.
169
+ *
170
+ * @remarks
171
+ * All directives receive the same argument upon initialization and update.
172
+ * Directives are created and updated in the same order as they appear in the arguments list,
173
+ * they are destroyed in the reverse order.
174
+ * All calls to the directives (to create, update and destroy them) are wrapped in a call to the
175
+ * batch function of tansu
176
+ *
177
+ * @param args - directives to merge into a single directive.
178
+ * @returns The resulting merged directive.
179
+ */
180
+ export const mergeDirectives = (...args) => (element, arg) => {
181
+ const instances = batch(() => args.map((directive) => directive(element, arg)));
182
+ return {
183
+ update(arg) {
184
+ batch(() => instances.forEach((instance) => instance?.update?.(arg)));
185
+ },
186
+ destroy() {
187
+ batch(() => instances.reverse().forEach((instance) => instance?.destroy?.()));
188
+ },
189
+ };
190
+ };
@@ -0,0 +1,19 @@
1
+ import type { ReadableSignal } from '@amadeus-it-group/tansu';
2
+ import type { Directive } from '../types';
3
+ export declare const activeElement$: ReadableSignal<Element | null>;
4
+ export interface HasFocus {
5
+ /**
6
+ * Directive to put on some elements.
7
+ */
8
+ directive: Directive;
9
+ /**
10
+ * Store that contains true if the activeElement is one of the elements which has the directive,
11
+ * or any of their descendants.
12
+ */
13
+ hasFocus$: ReadableSignal<boolean>;
14
+ }
15
+ /**
16
+ * Create a HasFocus
17
+ * @returns a HasFocus
18
+ */
19
+ export declare function createHasFocus(): HasFocus;
@@ -0,0 +1,46 @@
1
+ import { computed, readable } from '@amadeus-it-group/tansu';
2
+ import { createStoreArrayDirective } from './directiveUtils';
3
+ const evtFocusIn = 'focusin';
4
+ const evtFocusOut = 'focusout';
5
+ export const activeElement$ = readable(null, {
6
+ onUse({ set }) {
7
+ function setActiveElement() {
8
+ set(document.activeElement);
9
+ }
10
+ setActiveElement();
11
+ const container = document.documentElement;
12
+ function onFocusOut() {
13
+ setTimeout(setActiveElement);
14
+ }
15
+ container.addEventListener(evtFocusIn, setActiveElement);
16
+ container.addEventListener(evtFocusOut, onFocusOut);
17
+ return () => {
18
+ container.removeEventListener(evtFocusIn, setActiveElement);
19
+ container.removeEventListener(evtFocusOut, onFocusOut);
20
+ };
21
+ },
22
+ equal: Object.is,
23
+ });
24
+ /**
25
+ * Create a HasFocus
26
+ * @returns a HasFocus
27
+ */
28
+ export function createHasFocus() {
29
+ const { elements$, directive } = createStoreArrayDirective();
30
+ const hasFocus$ = computed(() => {
31
+ const activeElement = activeElement$();
32
+ if (!activeElement) {
33
+ return false;
34
+ }
35
+ for (const element of elements$()) {
36
+ if (element === activeElement || element.contains(activeElement)) {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ });
42
+ return {
43
+ directive,
44
+ hasFocus$,
45
+ };
46
+ }
@@ -0,0 +1,6 @@
1
+ export * from './siblingsInert';
2
+ export * from './directiveUtils';
3
+ export * from './focustrack';
4
+ export * from './portal';
5
+ export * from './stores';
6
+ export * from './writables';
@@ -0,0 +1,6 @@
1
+ export * from './siblingsInert';
2
+ export * from './directiveUtils';
3
+ export * from './focustrack';
4
+ export * from './portal';
5
+ export * from './stores';
6
+ export * from './writables';
@@ -0,0 +1,6 @@
1
+ import type { Directive } from '../types';
2
+ export type PortalDirectiveArg = {
3
+ container?: HTMLElement | null | undefined;
4
+ insertBefore?: HTMLElement | null | undefined;
5
+ } | null | undefined;
6
+ export declare const portal: Directive<PortalDirectiveArg>;