@agnos-ui/core 0.0.1-alpha.1 → 0.0.1-alpha.11

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 (119) hide show
  1. package/{accordion.d.ts → components/accordion/accordion.d.ts} +27 -18
  2. package/{accordion.js → components/accordion/accordion.js} +80 -76
  3. package/components/accordion/index.d.ts +1 -0
  4. package/components/accordion/index.js +1 -0
  5. package/components/alert/alert.d.ts +31 -0
  6. package/components/alert/alert.js +22 -0
  7. package/{alert.d.ts → components/alert/common.d.ts} +17 -21
  8. package/{alert.js → components/alert/common.js} +21 -19
  9. package/components/alert/index.d.ts +2 -0
  10. package/components/alert/index.js +2 -0
  11. package/components/modal/index.d.ts +1 -0
  12. package/components/modal/index.js +1 -0
  13. package/{modal → components/modal}/modal.d.ts +28 -21
  14. package/{modal → components/modal}/modal.js +58 -28
  15. package/components/pagination/index.d.ts +2 -0
  16. package/components/pagination/index.js +2 -0
  17. package/{pagination.d.ts → components/pagination/pagination.d.ts} +2 -3
  18. package/{pagination.js → components/pagination/pagination.js} +5 -4
  19. package/components/progressbar/index.d.ts +1 -0
  20. package/components/progressbar/index.js +1 -0
  21. package/components/progressbar/progressbar.d.ts +86 -0
  22. package/components/progressbar/progressbar.js +78 -0
  23. package/components/rating/index.d.ts +1 -0
  24. package/components/rating/index.js +1 -0
  25. package/{rating.d.ts → components/rating/rating.d.ts} +4 -5
  26. package/{rating.js → components/rating/rating.js} +6 -9
  27. package/components/select/index.d.ts +1 -0
  28. package/components/select/index.js +1 -0
  29. package/components/select/select.d.ts +337 -0
  30. package/components/select/select.js +266 -0
  31. package/components/slider/index.d.ts +1 -0
  32. package/components/slider/index.js +1 -0
  33. package/components/slider/slider.d.ts +245 -0
  34. package/components/slider/slider.js +415 -0
  35. package/config.d.ts +17 -7
  36. package/config.js +3 -3
  37. package/index.d.ts +23 -10
  38. package/index.js +29 -10
  39. package/package.json +32 -4
  40. package/services/extendWidget.d.ts +23 -0
  41. package/services/extendWidget.js +35 -0
  42. package/services/floatingUI.d.ts +56 -0
  43. package/services/floatingUI.js +105 -0
  44. package/services/focustrack.js +5 -5
  45. package/services/intersection.d.ts +9 -1
  46. package/services/intersection.js +10 -2
  47. package/services/navManager.d.ts +93 -0
  48. package/services/navManager.js +172 -0
  49. package/services/portal.d.ts +7 -0
  50. package/services/portal.js +15 -4
  51. package/services/siblingsInert.d.ts +2 -1
  52. package/services/siblingsInert.js +2 -2
  53. package/{transitions → services/transitions}/baseTransitions.d.ts +15 -2
  54. package/{transitions → services/transitions}/baseTransitions.js +21 -10
  55. package/services/transitions/bootstrap/collapse.d.ts +2 -0
  56. package/services/transitions/bootstrap/fade.d.ts +1 -0
  57. package/services/transitions/bootstrap.d.ts +2 -0
  58. package/services/transitions/bootstrap.js +2 -0
  59. package/services/transitions/collapse.d.ts +43 -0
  60. package/{transitions → services/transitions}/collapse.js +15 -2
  61. package/{transitions → services/transitions}/cssTransitions.d.ts +6 -0
  62. package/{transitions → services/transitions}/cssTransitions.js +8 -4
  63. package/{transitions → services/transitions}/simpleClassTransition.d.ts +12 -1
  64. package/services/transitions/simpleClassTransition.js +42 -0
  65. package/types.d.ts +43 -4
  66. package/types.js +7 -0
  67. package/{services/directiveUtils.js → utils/directive.js} +1 -1
  68. package/utils/internal/checks.d.ts +49 -0
  69. package/utils/internal/checks.js +60 -0
  70. package/utils/internal/dom.d.ts +25 -0
  71. package/utils/internal/dom.js +61 -0
  72. package/utils/internal/func.d.ts +11 -0
  73. package/utils/internal/func.js +11 -0
  74. package/utils/internal/isFocusable.d.ts +9 -0
  75. package/utils/internal/isFocusable.js +35 -0
  76. package/utils/internal/math.d.ts +5 -0
  77. package/utils/internal/math.js +13 -0
  78. package/utils/internal/promise.d.ts +87 -0
  79. package/utils/internal/promise.js +169 -0
  80. package/utils/internal/scrollbars.d.ts +8 -0
  81. package/{modal → utils/internal}/scrollbars.js +7 -1
  82. package/utils/internal/sort.d.ts +16 -0
  83. package/utils/internal/sort.js +28 -0
  84. package/utils/internal/textDirection.d.ts +7 -0
  85. package/utils/internal/textDirection.js +7 -0
  86. package/utils/internal/traversal.d.ts +54 -0
  87. package/utils/internal/traversal.js +105 -0
  88. package/{services → utils}/stores.d.ts +67 -33
  89. package/{services → utils}/stores.js +121 -59
  90. package/utils/writables.d.ts +32 -0
  91. package/utils/writables.js +72 -0
  92. package/modal/scrollbars.d.ts +0 -2
  93. package/select.d.ts +0 -196
  94. package/select.js +0 -240
  95. package/services/checks.d.ts +0 -32
  96. package/services/checks.js +0 -43
  97. package/services/index.d.ts +0 -7
  98. package/services/index.js +0 -7
  99. package/services/writables.d.ts +0 -7
  100. package/services/writables.js +0 -16
  101. package/transitions/bootstrap/collapse.d.ts +0 -2
  102. package/transitions/bootstrap/fade.d.ts +0 -1
  103. package/transitions/bootstrap/index.d.ts +0 -2
  104. package/transitions/bootstrap/index.js +0 -2
  105. package/transitions/collapse.d.ts +0 -29
  106. package/transitions/index.d.ts +0 -5
  107. package/transitions/index.js +0 -5
  108. package/transitions/simpleClassTransition.js +0 -28
  109. package/transitions/utils.d.ts +0 -20
  110. package/transitions/utils.js +0 -83
  111. package/utils.d.ts +0 -2
  112. package/utils.js +0 -2
  113. /package/{commonProps.d.ts → components/commonProps.d.ts} +0 -0
  114. /package/{commonProps.js → components/commonProps.js} +0 -0
  115. /package/{pagination.utils.d.ts → components/pagination/bootstrap.d.ts} +0 -0
  116. /package/{pagination.utils.js → components/pagination/bootstrap.js} +0 -0
  117. /package/{transitions → services/transitions}/bootstrap/collapse.js +0 -0
  118. /package/{transitions → services/transitions}/bootstrap/fade.js +0 -0
  119. /package/{services/directiveUtils.d.ts → utils/directive.d.ts} +0 -0
@@ -0,0 +1,172 @@
1
+ import { computed, writable } from '@amadeus-it-group/tansu';
2
+ import { registrationArray } from '../utils/directive';
3
+ import { computeCommonAncestor } from '../utils/internal/dom';
4
+ import { isFocusable } from '../utils/internal/isFocusable';
5
+ import { compareDomOrder } from '../utils/internal/sort';
6
+ import { getTextDirection } from '../utils/internal/textDirection';
7
+ // cf https://html.spec.whatwg.org/multipage/input.html#concept-input-apply
8
+ const textInputTypes = new Set(['text', 'search', 'url', 'tel', 'password']);
9
+ const isTextInput = (element) => element instanceof HTMLInputElement && textInputTypes.has(element.type);
10
+ /**
11
+ * Returns the key name given the keyboard event. The key name is built using event.key (such as ArrowLeft, PageDown...),
12
+ * prefixed with the modifiers. If present, modifiers are always in the same order: Meta+Ctrl+Alt+Shift+...
13
+ * @param event - keyboard event
14
+ * @returns the name of the key, including modifiers
15
+ */
16
+ export const getKeyName = (event) => {
17
+ let key = event.key;
18
+ if (event.shiftKey) {
19
+ key = `Shift+${key}`;
20
+ }
21
+ if (event.altKey) {
22
+ key = `Alt+${key}`;
23
+ }
24
+ if (event.ctrlKey) {
25
+ key = `Ctrl+${key}`;
26
+ }
27
+ if (event.metaKey) {
28
+ key = `Meta+${key}`;
29
+ }
30
+ return key;
31
+ };
32
+ /**
33
+ * Returns true if the keyboard event is an ArrowLeft, ArrowRight, Home or End key press that should make the cursor move inside
34
+ * the input and false otherwise (i.e. the key is not ArrowLeft, ArrowRight, Home or End key, or that would not make the cursor move
35
+ * because it is already at one end of the input)
36
+ * @param event - keyboard event
37
+ * @returns true if the keyboard event is an ArrowLeft, ArrowRight, Home or End key press that should make the cursor move inside
38
+ * the input and false otherwise.
39
+ */
40
+ export const isInternalInputNavigation = (event) => {
41
+ const { target, key } = event;
42
+ if (isTextInput(target) && (key === 'ArrowLeft' || key === 'ArrowRight' || key === 'Home' || key === 'End')) {
43
+ let startPosition;
44
+ if (key === 'ArrowLeft' || key === 'ArrowRight') {
45
+ const direction = getTextDirection(target);
46
+ startPosition = key === (direction === 'ltr' ? 'ArrowLeft' : 'ArrowRight');
47
+ }
48
+ else {
49
+ startPosition = key === 'Home';
50
+ }
51
+ const cursorPosition = target.selectionStart === target.selectionEnd ? target.selectionStart : null;
52
+ if ((startPosition && cursorPosition !== 0) || (!startPosition && cursorPosition !== target.value.length)) {
53
+ // let the text input process the key
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ };
59
+ const defaultSelector = (directiveElement) => [directiveElement];
60
+ /**
61
+ * Returns a new instance of the navigation manager.
62
+ *
63
+ * The navigation manager simplifies keyboard navigation for a set of DOM elements.
64
+ * It provides a directive to use on some DOM elements, both to add the keydown event handler and to specify which elements should be managed
65
+ * (either by directly putting the directive on those elements, or by putting the directive on a parent element and
66
+ * specifying which child elements should be included through a selector function).
67
+ *
68
+ * It provides some utilities to move the focus between those elements (focusFirst/focusLast, focusLeft/focusRight, focusPrevious/focusNext).
69
+ *
70
+ * @returns a new instance of the navigation manager
71
+ */
72
+ export const createNavManager = () => {
73
+ const directiveInstances$ = registrationArray();
74
+ const elementsRefresh$ = writable({});
75
+ const refreshElements = () => elementsRefresh$.set({});
76
+ const elements$ = computed(() => {
77
+ elementsRefresh$();
78
+ const res = [];
79
+ for (const item of directiveInstances$()) {
80
+ res.push(...item());
81
+ }
82
+ return res;
83
+ });
84
+ const commonAncestor$ = computed(() => computeCommonAncestor(elements$()), { equal: Object.is });
85
+ const elementsInDomOrder$ = computed(() => [...elements$()].sort(compareDomOrder));
86
+ const ancestorDirection = () => {
87
+ const commonAncestor = commonAncestor$();
88
+ return commonAncestor ? getTextDirection(commonAncestor) : 'ltr';
89
+ };
90
+ const preventDefaultIfRelevant = (value, event) => {
91
+ if (value) {
92
+ event?.preventDefault();
93
+ }
94
+ return value;
95
+ };
96
+ const focusIndex = (index, moveDirection = 0) => {
97
+ const array = elementsInDomOrder$();
98
+ while (index >= 0 && index < array.length) {
99
+ const newItem = array[index];
100
+ if (isFocusable(newItem)) {
101
+ newItem.focus();
102
+ if (moveDirection != 0 && isTextInput(newItem)) {
103
+ const changeDirection = ancestorDirection() !== getTextDirection(newItem);
104
+ const position = moveDirection > 0 !== changeDirection ? 0 : newItem.value.length;
105
+ newItem.setSelectionRange(position, position, position === 0 ? 'forward' : 'backward');
106
+ }
107
+ return newItem;
108
+ }
109
+ if (moveDirection === 0) {
110
+ break;
111
+ }
112
+ else {
113
+ index += moveDirection;
114
+ }
115
+ }
116
+ return null;
117
+ };
118
+ const createFocusNeighbour = (moveDirection) => ({ event, referenceElement = event?.target ?? document.activeElement, } = {}) => {
119
+ const curIndex = referenceElement ? elementsInDomOrder$().indexOf(referenceElement) : -1;
120
+ if (curIndex > -1) {
121
+ return preventDefaultIfRelevant(focusIndex(curIndex + moveDirection, moveDirection), event);
122
+ }
123
+ return null;
124
+ };
125
+ const directive = (directiveElement, config) => {
126
+ const onKeyDown = (event) => {
127
+ if (isInternalInputNavigation(event)) {
128
+ return;
129
+ }
130
+ const keyName = getKeyName(event);
131
+ const handler = config.keys?.[keyName];
132
+ if (handler) {
133
+ refreshElements();
134
+ handler({ event, directiveElement, navManager });
135
+ }
136
+ };
137
+ directiveElement.addEventListener('keydown', onKeyDown);
138
+ const unregister = directiveInstances$.register(() => (config?.selector ?? defaultSelector)(directiveElement));
139
+ return {
140
+ update(newConfig) {
141
+ config = newConfig;
142
+ },
143
+ destroy() {
144
+ directiveElement.removeEventListener('keydown', onKeyDown);
145
+ unregister();
146
+ },
147
+ };
148
+ };
149
+ const focusPrevious = createFocusNeighbour(-1);
150
+ const focusNext = createFocusNeighbour(1);
151
+ const focusFirst = ({ event } = {}) => preventDefaultIfRelevant(focusIndex(0, 1), event);
152
+ const focusLast = ({ event } = {}) => preventDefaultIfRelevant(focusIndex(elementsInDomOrder$().length - 1, -1), event);
153
+ const focusLeft = (...args) => (ancestorDirection() === 'rtl' ? focusNext : focusPrevious)(...args);
154
+ const focusRight = (...args) => (ancestorDirection() === 'rtl' ? focusPrevious : focusNext)(...args);
155
+ const focusFirstLeft = (...args) => (ancestorDirection() === 'rtl' ? focusLast : focusFirst)(...args);
156
+ const focusFirstRight = (...args) => (ancestorDirection() === 'rtl' ? focusFirst : focusLast)(...args);
157
+ const navManager = {
158
+ elementsInDomOrder$,
159
+ directive,
160
+ focusIndex,
161
+ focusPrevious,
162
+ focusNext,
163
+ focusFirst,
164
+ focusFirstLeft,
165
+ focusFirstRight,
166
+ focusLast,
167
+ focusLeft,
168
+ focusRight,
169
+ refreshElements,
170
+ };
171
+ return navManager;
172
+ };
@@ -3,4 +3,11 @@ export type PortalDirectiveArg = {
3
3
  container?: HTMLElement | null | undefined;
4
4
  insertBefore?: HTMLElement | null | undefined;
5
5
  } | null | undefined;
6
+ /**
7
+ * Creates a portal directive, allowing to attach content to any element.
8
+ *
9
+ * @param content - the content of the portal
10
+ * @param newArg - {@link PortalDirectiveArg} args
11
+ * @returns the portal directive
12
+ */
6
13
  export declare const portal: Directive<PortalDirectiveArg>;
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Creates a portal directive, allowing to attach content to any element.
3
+ *
4
+ * @param content - the content of the portal
5
+ * @param newArg - {@link PortalDirectiveArg} args
6
+ * @returns the portal directive
7
+ */
1
8
  export const portal = (content, newArg) => {
2
9
  let arg;
3
10
  let replaceComment;
@@ -8,14 +15,18 @@ export const portal = (content, newArg) => {
8
15
  }
9
16
  };
10
17
  const update = (newArg) => {
11
- if (newArg !== arg) {
18
+ if (newArg !== arg && (newArg?.container !== arg?.container || newArg?.insertBefore !== arg?.insertBefore)) {
12
19
  arg = newArg;
13
20
  const container = arg?.container ?? arg?.insertBefore?.parentElement;
14
21
  if (container) {
15
- if (!replaceComment) {
16
- replaceComment = content.parentNode?.insertBefore(content.ownerDocument.createComment('portal'), content);
22
+ const insertBefore = arg?.insertBefore ?? null;
23
+ const moveNeeded = content.parentElement !== container || content.nextSibling !== insertBefore;
24
+ if (moveNeeded) {
25
+ if (!replaceComment) {
26
+ replaceComment = content.parentNode?.insertBefore(content.ownerDocument.createComment('portal'), content);
27
+ }
28
+ container.insertBefore(content, insertBefore);
17
29
  }
18
- container.insertBefore(content, arg?.insertBefore ?? null);
19
30
  }
20
31
  else {
21
32
  removeReplaceComment();
@@ -1,7 +1,8 @@
1
+ import type { Directive } from '../types';
1
2
  /**
2
3
  * sliblingsInert directive
3
4
  * When used on an element, all siblings of the element and of its ancestors will be inert with the inert attribute.
4
5
  * In case it is used on multiple elements, only the last one has an effect (the directive keeps a stack of elements
5
6
  * on which it is used, so when the last one disappears, the previous one in the list becomes the one in effect).
6
7
  */
7
- export declare const sliblingsInert: import("..").Directive<void>;
8
+ export declare const sliblingsInert: Directive;
@@ -1,6 +1,6 @@
1
1
  import { computed } from '@amadeus-it-group/tansu';
2
- import { noop } from '../utils';
3
- import { createStoreArrayDirective, directiveSubscribe, mergeDirectives } from './directiveUtils';
2
+ import { noop } from '../utils/internal/func';
3
+ import { createStoreArrayDirective, directiveSubscribe, mergeDirectives } from '../utils/directive';
4
4
  const internalSetSiblingsInert = (element) => {
5
5
  const inertValues = new Map();
6
6
  const recursiveHelper = (element) => {
@@ -1,5 +1,4 @@
1
- import type { PropsConfig } from '../services';
2
- import type { Directive, Widget } from '../types';
1
+ import type { Directive, PropsConfig, Widget } from '../../types';
3
2
  /**
4
3
  * Function that implements a transition.
5
4
  */
@@ -132,5 +131,19 @@ export interface TransitionDirectives {
132
131
  directive: Directive<void | Partial<TransitionProps>>;
133
132
  }
134
133
  export type TransitionWidget = Widget<TransitionProps, TransitionState, TransitionApi, object, TransitionDirectives>;
134
+ /**
135
+ * A transition to show / hide an element without any animation. It uses the HTML `display` attribute.
136
+ *
137
+ * @param element - the element to animate
138
+ * @param direction - the direction
139
+ */
135
140
  export declare const noAnimation: TransitionFn;
141
+ /**
142
+ * Create a transition widget.
143
+ *
144
+ * The widget will include a patch function, stores to track the animation states and a directive to apply the animation to an element.
145
+ *
146
+ * @param config - the props config of the transition
147
+ * @returns the transition widget
148
+ */
136
149
  export declare const createTransition: (config?: PropsConfig<TransitionProps>) => TransitionWidget;
@@ -1,8 +1,16 @@
1
1
  import { batch, computed, derived, writable } from '@amadeus-it-group/tansu';
2
- import { bindableDerived, createStoreDirective, directiveSubscribe, directiveUpdate, mergeDirectives, stateStores, writablesForProps, } from '../services';
3
- import { typeBoolean, typeFunction } from '../services/writables';
4
- const noop = () => { };
2
+ import { typeBoolean, typeBooleanOrNull, typeFunction } from '../../utils/writables';
3
+ import { promiseWithResolve } from '../../utils/internal/promise';
4
+ import { noop } from '../../utils/internal/func';
5
+ import { bindableDerived, stateStores, writablesForProps } from '../../utils/stores';
6
+ import { createStoreDirective, directiveSubscribe, directiveUpdate, mergeDirectives } from '../../utils/directive';
5
7
  const neverEndingPromise = new Promise(noop);
8
+ /**
9
+ * A transition to show / hide an element without any animation. It uses the HTML `display` attribute.
10
+ *
11
+ * @param element - the element to animate
12
+ * @param direction - the direction
13
+ */
6
14
  export const noAnimation = async (element, direction) => {
7
15
  element.style.display = direction === 'show' ? '' : 'none';
8
16
  };
@@ -23,14 +31,17 @@ const configValidator = {
23
31
  transition: typeFunction,
24
32
  onShown: typeFunction,
25
33
  onHidden: typeFunction,
34
+ onVisibleChange: typeFunction,
35
+ initDone: typeBooleanOrNull,
26
36
  };
27
- const promiseWithResolve = () => {
28
- let resolve;
29
- const promise = new Promise((r) => {
30
- resolve = r;
31
- });
32
- return { promise, resolve: resolve };
33
- };
37
+ /**
38
+ * Create a transition widget.
39
+ *
40
+ * The widget will include a patch function, stores to track the animation states and a directive to apply the animation to an element.
41
+ *
42
+ * @param config - the props config of the transition
43
+ * @returns the transition widget
44
+ */
34
45
  export const createTransition = (config) => {
35
46
  const [{ animation$, initDone$, visible$: requestedVisible$, transition$, onShown$, onHidden$, onVisibleChange$, animationOnInit$ }, patch] = writablesForProps(defaultValues, config, configValidator);
36
47
  const { element$, directive: storeDirective } = createStoreDirective();
@@ -0,0 +1,2 @@
1
+ export declare const collapseVerticalTransition: import("../baseTransitions").TransitionFn;
2
+ export declare const collapseHorizontalTransition: import("../baseTransitions").TransitionFn;
@@ -0,0 +1 @@
1
+ export declare const fadeTransition: import("../baseTransitions").TransitionFn;
@@ -0,0 +1,2 @@
1
+ export * from './bootstrap/collapse';
2
+ export * from './bootstrap/fade';
@@ -0,0 +1,2 @@
1
+ export * from './bootstrap/collapse';
2
+ export * from './bootstrap/fade';
@@ -0,0 +1,43 @@
1
+ import type { TransitionFn } from './baseTransitions';
2
+ export interface CollapseContext {
3
+ /**
4
+ * the maximum size of the collapseable content.
5
+ */
6
+ maxSize?: string;
7
+ /**
8
+ * the minimum size of the collapseable content
9
+ */
10
+ minSize?: string;
11
+ }
12
+ export interface CollapseConfig {
13
+ /**
14
+ * the direction in which the collapsing is performed
15
+ */
16
+ dimension?: 'width' | 'height';
17
+ /**
18
+ * the list of classes to add to the collapsable element when shown
19
+ */
20
+ showClasses?: string[];
21
+ /**
22
+ * the list of classes to add to the collapsable element when collapsed
23
+ */
24
+ hideClasses?: string[];
25
+ /**
26
+ * the list of classes to add to the collapsable element while transitioning
27
+ */
28
+ animationPendingClasses?: string[];
29
+ }
30
+ /**
31
+ * Create a collapse transition.
32
+ *
33
+ * The transition attaches / removes classes during the different states of the collapse transition.
34
+ * It also updates the dimension value when reaching a non-pending state.
35
+ *
36
+ * @param config - the collapse config
37
+ * @param config.dimension - the dimension, `height` or `width`, on which the collapse applies
38
+ * @param config.showClasses - the classes to attach when the element is fully visible
39
+ * @param config.hideClasses - the classes to attach when the element is fully collapsed
40
+ * @param config.animationPendingClasses - the classes to attach when the transition is pending
41
+ * @returns the collapse transition
42
+ */
43
+ export declare const createCollapseTransition: ({ dimension, showClasses, hideClasses, animationPendingClasses, }?: CollapseConfig) => TransitionFn;
@@ -1,6 +1,19 @@
1
1
  import { createCSSTransition } from './cssTransitions';
2
- import { addClasses, reflow, removeClasses } from './utils';
3
- export const createCollapseTransition = ({ dimension = 'height', showClasses, hideClasses, animationPendingClasses } = {}) => createCSSTransition((element, direction, animation, context) => {
2
+ import { addClasses, reflow, removeClasses } from '../../utils/internal/dom';
3
+ /**
4
+ * Create a collapse transition.
5
+ *
6
+ * The transition attaches / removes classes during the different states of the collapse transition.
7
+ * It also updates the dimension value when reaching a non-pending state.
8
+ *
9
+ * @param config - the collapse config
10
+ * @param config.dimension - the dimension, `height` or `width`, on which the collapse applies
11
+ * @param config.showClasses - the classes to attach when the element is fully visible
12
+ * @param config.hideClasses - the classes to attach when the element is fully collapsed
13
+ * @param config.animationPendingClasses - the classes to attach when the transition is pending
14
+ * @returns the collapse transition
15
+ */
16
+ export const createCollapseTransition = ({ dimension = 'height', showClasses, hideClasses, animationPendingClasses, } = {}) => createCSSTransition((element, direction, animation, context) => {
4
17
  if (animation) {
5
18
  let { maxSize, minSize } = context;
6
19
  if (!maxSize) {
@@ -12,4 +12,10 @@ export declare function hasTransition(element: HTMLElement): boolean;
12
12
  */
13
13
  export declare function getTransitionDurationMs(element: HTMLElement): number;
14
14
  export type CSSTransitionFn = (element: HTMLElement, direction: 'show' | 'hide', animation: boolean, context: object) => void | (() => void);
15
+ /**
16
+ * Create a simple css transition.
17
+ *
18
+ * @param start - a function that creates the css animation and returns a clean-up function
19
+ * @returns the css transition
20
+ */
15
21
  export declare const createCSSTransition: (start: CSSTransitionFn) => TransitionFn;
@@ -1,4 +1,5 @@
1
- import { promiseFromEvent, promiseFromTimeout } from './utils';
1
+ import { noop } from '../../utils/internal/func';
2
+ import { promiseFromEvent, promiseFromTimeout } from '../../utils/internal/promise';
2
3
  /**
3
4
  * Check if the provided html element has a transition
4
5
  * @param element - the html element
@@ -18,9 +19,12 @@ export function getTransitionDurationMs(element) {
18
19
  const transitionDurationSec = parseFloat(transitionDuration);
19
20
  return (transitionDelaySec + transitionDurationSec) * 1000;
20
21
  }
21
- const noop = () => {
22
- /* do nothing */
23
- };
22
+ /**
23
+ * Create a simple css transition.
24
+ *
25
+ * @param start - a function that creates the css animation and returns a clean-up function
26
+ * @returns the css transition
27
+ */
24
28
  export const createCSSTransition = (start) => async (element, direction, animation, signal, context) => {
25
29
  const endFn = start(element, direction, animation, context) ?? noop;
26
30
  if (animation && hasTransition(element)) {
@@ -26,4 +26,15 @@ export interface SimpleClassTransitionContext {
26
26
  */
27
27
  started?: boolean;
28
28
  }
29
- export declare const createSimpleClassTransition: ({ animationPendingClasses, animationPendingShowClasses, animationPendingHideClasses, showClasses, hideClasses, }: SimpleClassTransitionConfig) => import("./baseTransitions").TransitionFn;
29
+ /**
30
+ * Create a transition based on css classes to attach.
31
+ *
32
+ * The config includes the classes that will be attached / removed depending on the transition state.
33
+ * `animationPendingClasses` are the classes attached when the transition is in a pending state
34
+ * `animationPendingShowClasses` and `animationPendingHideClasses` are attached when transitionning towards one direction
35
+ * `showClasses` and `hideClasses` are attached when the transition has reached the show or hide state respectively
36
+ *
37
+ * @param config - the transition config
38
+ * @returns the simple class transition
39
+ */
40
+ export declare const createSimpleClassTransition: (config: SimpleClassTransitionConfig) => import("./baseTransitions").TransitionFn;
@@ -0,0 +1,42 @@
1
+ import { createCSSTransition } from './cssTransitions';
2
+ import { addClasses, reflow, removeClasses } from '../../utils/internal/dom';
3
+ /**
4
+ * Create a transition based on css classes to attach.
5
+ *
6
+ * The config includes the classes that will be attached / removed depending on the transition state.
7
+ * `animationPendingClasses` are the classes attached when the transition is in a pending state
8
+ * `animationPendingShowClasses` and `animationPendingHideClasses` are attached when transitionning towards one direction
9
+ * `showClasses` and `hideClasses` are attached when the transition has reached the show or hide state respectively
10
+ *
11
+ * @param config - the transition config
12
+ * @returns the simple class transition
13
+ */
14
+ export const createSimpleClassTransition = (config) => {
15
+ const { animationPendingClasses, animationPendingShowClasses, animationPendingHideClasses, showClasses, hideClasses } = config;
16
+ return createCSSTransition((element, direction, animation, context) => {
17
+ removeClasses(element, showClasses);
18
+ removeClasses(element, hideClasses);
19
+ if (animation) {
20
+ removeClasses(element, direction === 'show' ? animationPendingHideClasses : animationPendingShowClasses);
21
+ if (!context.started) {
22
+ context.started = true;
23
+ // if the animation is starting, explicitly sets the initial state (reverse of the direction)
24
+ // so that it is not impacted by another reflow done somewhere else before we had time to put
25
+ // the right classes:
26
+ const classes = direction === 'show' ? hideClasses : showClasses;
27
+ addClasses(element, classes);
28
+ reflow(element);
29
+ removeClasses(element, classes);
30
+ }
31
+ addClasses(element, animationPendingClasses);
32
+ reflow(element);
33
+ addClasses(element, direction === 'show' ? animationPendingShowClasses : animationPendingHideClasses);
34
+ }
35
+ return () => {
36
+ removeClasses(element, animationPendingClasses);
37
+ removeClasses(element, animationPendingShowClasses);
38
+ removeClasses(element, animationPendingHideClasses);
39
+ addClasses(element, direction === 'show' ? showClasses : hideClasses);
40
+ };
41
+ });
42
+ };
package/types.d.ts CHANGED
@@ -1,5 +1,22 @@
1
- import type { ReadableSignal, SubscribableStore } from '@amadeus-it-group/tansu';
2
- import type { PropsConfig } from './services';
1
+ import type { ReadableSignal, StoreOptions, SubscribableStore, WritableSignal } from '@amadeus-it-group/tansu';
2
+ export type ValuesOrReadableSignals<T extends object> = {
3
+ [K in keyof T]?: ReadableSignal<T[K] | undefined> | T[K];
4
+ };
5
+ export type ValuesOrWritableSignals<T extends object> = {
6
+ [K in keyof T]?: WritableSignal<T[K] | undefined> | T[K];
7
+ };
8
+ export interface PropsConfig<U extends object> {
9
+ /**
10
+ * Object containing, for each property, either its initial value, or a store that will contain the value at any time.
11
+ * When the value of a property is undefined or invalid, the value from the config is used.
12
+ */
13
+ props?: ValuesOrWritableSignals<U>;
14
+ /**
15
+ * Either a store of objects containing, for each property, the default value,
16
+ * or an object containing, for each property, either a store containing the default value or the default value itself.
17
+ */
18
+ config?: ReadableSignal<Partial<U>> | ValuesOrReadableSignals<Partial<U>>;
19
+ }
3
20
  export interface Widget<Props extends object = object, State extends object = object, Api extends object = object, Actions extends object = object, Directives extends object = object> {
4
21
  /**
5
22
  * the reactive state of the widget, combining all the values served by the stores
@@ -29,6 +46,7 @@ export interface Widget<Props extends object = object, State extends object = ob
29
46
  */
30
47
  api: Api;
31
48
  }
49
+ export type ContextWidget<W extends Widget> = Pick<W, 'actions' | 'api' | 'directives' | 'state$' | 'stores'>;
32
50
  export interface WidgetSlotContext<W extends Widget> {
33
51
  /**
34
52
  * the state of the widget
@@ -37,9 +55,15 @@ export interface WidgetSlotContext<W extends Widget> {
37
55
  /**
38
56
  * the widget
39
57
  */
40
- widget: Pick<W, 'actions' | 'api' | 'directives' | 'state$' | 'stores'>;
58
+ widget: ContextWidget<W>;
41
59
  }
42
- export declare const toSlotContextWidget: <W extends Widget<object, object, object, object, object>>(w: W) => Pick<W, "actions" | "api" | "directives" | "state$" | "stores">;
60
+ /**
61
+ * Extract actions, api, directives, state and stores from the widget to be passed to slots as context.
62
+ *
63
+ * @param w - the widget
64
+ * @returns the slot context
65
+ */
66
+ export declare const toSlotContextWidget: <W extends Widget<object, object, object, object, object>>(w: W) => ContextWidget<W>;
43
67
  export type WidgetState<T extends {
44
68
  state$: SubscribableStore<any>;
45
69
  }> = T extends {
@@ -56,3 +80,18 @@ export type Directive<T = void> = (node: HTMLElement, args: T) => void | {
56
80
  destroy?: () => void;
57
81
  };
58
82
  export type SlotContent<Props extends object = object> = undefined | null | string | ((props: Props) => string);
83
+ export declare const INVALID_VALUE: unique symbol;
84
+ export type NormalizeValue<T> = (value: T) => T | typeof INVALID_VALUE;
85
+ export interface WritableWithDefaultOptions<T> {
86
+ /**
87
+ * the normalize value function. should return the invalidValue symbol when the provided value is invalid
88
+ */
89
+ normalizeValue?: NormalizeValue<T>;
90
+ /**
91
+ * the equal function, allowing to compare two values. used to check if a previous and current values are equals.
92
+ */
93
+ equal?: StoreOptions<T>['equal'];
94
+ }
95
+ export type ConfigValidator<T extends object> = {
96
+ [K in keyof T]?: WritableWithDefaultOptions<T[K]>;
97
+ };
package/types.js CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Extract actions, api, directives, state and stores from the widget to be passed to slots as context.
3
+ *
4
+ * @param w - the widget
5
+ * @returns the slot context
6
+ */
1
7
  export const toSlotContextWidget = (w) => ({
2
8
  actions: w.actions,
3
9
  api: w.api,
@@ -5,3 +11,4 @@ export const toSlotContextWidget = (w) => ({
5
11
  state$: w.state$,
6
12
  stores: w.stores,
7
13
  });
14
+ export const INVALID_VALUE = Symbol();
@@ -1,5 +1,5 @@
1
1
  import { asReadable, batch, readable, writable } from '@amadeus-it-group/tansu';
2
- import { noop } from '../utils';
2
+ import { noop } from './internal/func';
3
3
  /**
4
4
  * Binds the given directive to a store that provides its argument.
5
5
  *
@@ -0,0 +1,49 @@
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
+ * an array type guard
27
+ * @returns true if the value is an array
28
+ */
29
+ export declare const isArray: (arg: any) => arg is any[];
30
+ /**
31
+ * Clamp the value based on a maximum and optional minimum
32
+ * @param value - the value to check
33
+ * @param max - the max to clamp to
34
+ * @param [min] - the min to clamp to
35
+ * @returns the clamped value
36
+ */
37
+ export declare function clamp(value: number, max: number, min?: number): number;
38
+ /**
39
+ * an html element type guard
40
+ * @param value - the value to check
41
+ * @returns true if the value is an instance of HTMLElement
42
+ */
43
+ export declare const isHTMLElement: (value: any) => value is HTMLElement;
44
+ /**
45
+ * Returns a new type guard that is based on the provided type guard and also returns true for null values.
46
+ * @param isType - base type guard
47
+ * @returns A type guard function that returns true for null values and calls the provided type guard for other values.
48
+ */
49
+ export declare const allowNull: <T>(isType: (value: any) => value is T) => (value: any) => value is T | null;