Skip to content

Commit

Permalink
feat: isolatedDeclarations in core and core-bootstrap (#883)
Browse files Browse the repository at this point in the history
  • Loading branch information
quentinderoubaix authored Sep 9, 2024
1 parent 094cee7 commit 968badc
Show file tree
Hide file tree
Showing 33 changed files with 212 additions and 76 deletions.
3 changes: 2 additions & 1 deletion core-bootstrap/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"outDir": "dist",
"noEmit": false,
"emitDeclarationOnly": true,
"declaration": true
"declaration": true,
"isolatedDeclarations": true
},
"include": ["src"],
"exclude": ["**/*.spec.ts", "**/__mocks__/**", "**/*.spec-utils.ts"]
Expand Down
2 changes: 1 addition & 1 deletion core/src/components/accordion/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,4 +638,4 @@ export function factoryCreateAccordion(
* @param config - config of the accordion, either as a store or as an object containing values or stores.
* @returns a new accordion widget instance
*/
export const createAccordion = factoryCreateAccordion();
export const createAccordion: WidgetFactory<AccordionWidget> = factoryCreateAccordion();
6 changes: 3 additions & 3 deletions core/src/components/modal/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import {sliblingsInert} from '../../services/siblingsInert';
* Value present in the {@link ModalBeforeCloseEvent.result|result} property of the {@link ModalProps.onBeforeClose|onBeforeClose} event
* and returned by the {@link ModalApi.open|open} method, when the modal is closed by a click inside the viewport but outside the modal.
*/
export const modalOutsideClick = Symbol();
export const modalOutsideClick: unique symbol = Symbol();

/**
* Value present in the {@link ModalBeforeCloseEvent.result|result} property of the {@link ModalProps.onBeforeClose|onBeforeClose} event
* and returned by the {@link ModalApi.open|open} method, when the modal is closed by a click on the close button.
*/
export const modalCloseButtonClick = Symbol();
export const modalCloseButtonClick: unique symbol = Symbol();

/**
* Properties of the modal widget that are also in the state of the modal.
Expand Down Expand Up @@ -336,7 +336,7 @@ const configValidator: ConfigValidator<ModalProps> = {
* Returns a copy of the default modal config.
* @returns a copy of the default modal config
*/
export function getModalDefaultConfig() {
export function getModalDefaultConfig(): ModalProps {
return {...defaultConfig};
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/components/pagination/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ const defaultConfig: PaginationProps = {
* Returns a shallow copy of the default pagination config
* @returns a copy of the default config
*/
export function getPaginationDefaultConfig() {
export function getPaginationDefaultConfig(): PaginationProps {
return {...defaultConfig};
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/components/rating/rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ const defaultConfig: RatingProps = {
* Returns a shallow copy of the default rating config.
* @returns a copy of the default config
*/
export function getRatingDefaultConfig() {
export function getRatingDefaultConfig(): RatingProps {
return {...defaultConfig};
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/components/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ const defaultConfig: SelectProps<any> = {
* Returns a shallow copy of the default select config.
* @returns a copy of the default config
*/
export function getSelectDefaultConfig() {
export function getSelectDefaultConfig(): SelectProps<any> {
return {...defaultConfig};
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/components/slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ const defaultSliderConfig: SliderProps = {
* Returns a shallow copy of the default slider config.
* @returns a copy of the default config
*/
export function getSliderDefaultConfig() {
export function getSliderDefaultConfig(): SliderProps {
return {...defaultSliderConfig};
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type WidgetsConfigStore<T> = WritableSignal<Partial2Levels<T>> & {
* @returns the destination object in most cases, or the source in some cases (if the source is not undefined and either levels is smaller than 1
* or the source is not an object)
*/
export const mergeInto = <T>(destination: T, source: T | undefined, levels = Infinity): T => {
export const mergeInto = <T>(destination: T, source: T | undefined, levels: number = Infinity): T => {
if (source !== undefined) {
if (typeof source === 'object' && source && levels >= 1) {
if (!destination) {
Expand Down
21 changes: 18 additions & 3 deletions core/src/services/floatingUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
Strategy,
} from '@floating-ui/dom';
import {arrow, autoUpdate, computePosition} from '@floating-ui/dom';
import type {PropsConfig} from '../types';
import type {Directive, PropsConfig, SSRHTMLElement, Widget} from '../types';
import {createBrowserStoreDirective, directiveSubscribe, mergeDirectives} from '../utils/directive';
import {promiseStoreToValueStore} from '../utils/internal/promise';
import {stateStores, writablesForProps} from '../utils/stores';
Expand Down Expand Up @@ -55,13 +55,28 @@ export interface FloatingUIState {
middlewareData: MiddlewareData | undefined;
}

export interface FloatingUIDirectives {
/**
* Directive to attach to the reference element
*/
referenceDirective: Directive<void, SSRHTMLElement>;
/**
* Directive to attach to the floating element
*/
floatingDirective: Directive<void, SSRHTMLElement>;
/**
* Directive to attach to the arrow element
*/
arrowDirective: Directive<void, SSRHTMLElement>;
}

const defaultConfig: FloatingUIProps = {
computePositionOptions: {},
autoUpdateOptions: {},
arrowOptions: {},
};

export type FloatingUI = ReturnType<typeof createFloatingUI>;
export type FloatingUI = Omit<Widget<FloatingUIProps, FloatingUIState, object, object, FloatingUIDirectives>, 'api' | 'actions'>;

/**
* Create a floating UI service.
Expand All @@ -71,7 +86,7 @@ export type FloatingUI = ReturnType<typeof createFloatingUI>;
* @param propsConfig - the props config for the floating UI service
* @returns the floating UI service
*/
export const createFloatingUI = (propsConfig?: PropsConfig<FloatingUIProps>) => {
export const createFloatingUI = (propsConfig?: PropsConfig<FloatingUIProps>): FloatingUI => {
const [{autoUpdateOptions$, computePositionOptions$: computePositionInputOptions$, arrowOptions$: arrowInputOptions$}, patch] = writablesForProps(
defaultConfig,
propsConfig,
Expand Down
2 changes: 1 addition & 1 deletion core/src/services/focustrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {BROWSER} from 'esm-env';
const evtFocusIn = 'focusin';
const evtFocusOut = 'focusout';

export const activeElement$ = !BROWSER
export const activeElement$: ReadableSignal<Element | null> = !BROWSER
? readable(null)
: readable(<Element | null>null, {
onUse({set}) {
Expand Down
4 changes: 2 additions & 2 deletions core/src/services/hash.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {readable} from '@amadeus-it-group/tansu';
import {readable, type ReadableSignal} from '@amadeus-it-group/tansu';

/** Store exposing the location.hash string */
export const hash$ = readable(<string>'', {
export const hash$: ReadableSignal<string> = readable(<string>'', {
onUse({set}) {
function handleHashChange() {
const hash = location.hash;
Expand Down
10 changes: 8 additions & 2 deletions core/src/services/intersection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {asReadable, derived} from '@amadeus-it-group/tansu';
import {asReadable, derived, type ReadableSignal} from '@amadeus-it-group/tansu';
import {noop} from '../utils/internal/func';
import type {PropsConfig} from '../types';
import {writablesForProps} from '../utils/stores';
Expand Down Expand Up @@ -31,7 +31,13 @@ const defaultValues: IntersectionProps = {
* @param config - the props config for the intersection service
* @returns the intersection service
*/
export const createIntersection = (config?: PropsConfig<IntersectionProps>) => {
export const createIntersection = (
config?: PropsConfig<IntersectionProps>,
): {
elements$: ReadableSignal<HTMLElement[]>;
visibleElements$: ReadableSignal<Map<Element, IntersectionObserverEntry>>;
patch: (storesValues: Partial<IntersectionProps>) => void;
} => {
const [{elements$, options$}, patch] = writablesForProps(defaultValues, config);

const visibleElements$ = derived(
Expand Down
4 changes: 2 additions & 2 deletions core/src/services/matchMedia.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {readable} from '@amadeus-it-group/tansu';
import {readable, type ReadableSignal} from '@amadeus-it-group/tansu';
import {BROWSER} from 'esm-env';
import {addEvent} from '../utils/internal/dom';

Expand All @@ -8,7 +8,7 @@ import {addEvent} from '../utils/internal/dom';
* @param query - the query to match
* @returns a readable store tracking the match media query state
*/
export const createMatchMedia = (query: string) =>
export const createMatchMedia = (query: string): ReadableSignal<boolean> =>
BROWSER
? readable(false, {
onUse({set}) {
Expand Down
72 changes: 65 additions & 7 deletions core/src/services/navManager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,69 @@
import {computed, writable} from '@amadeus-it-group/tansu';
import {computed, type ReadableSignal, writable} from '@amadeus-it-group/tansu';
import {browserDirective, registrationArray} from '../utils/directive';
import {computeCommonAncestor} from '../utils/internal/dom';
import {isFocusable} from '../utils/internal/isFocusable';
import {compareDomOrder} from '../utils/internal/sort';
import {getTextDirection} from '../utils/internal/textDirection';
import type {Directive, SSRHTMLElement} from '../types';

export type NavManager = ReturnType<typeof createNavManager>;
export type FocusNeighbour = (arg?: {event?: Event; referenceElement?: HTMLElement | null}) => HTMLElement | null;
export type FocusEnd = (arg?: {event?: Event}) => HTMLElement | null;

export type NavManager<T> = {
/**
* Store containing the navigable elements in DOM order
*/
elementsInDomOrder$: ReadableSignal<HTMLElement[]>;
/**
* Directive to attach the nav manager
*/
directive: Directive<NavManagerItemConfig<T>, SSRHTMLElement>;
/**
* Refresh the elements list.
* @param now force the instant refresh of the elements
*/
refreshElements: (now?: boolean) => void;
/**
* Focus the element at the given idex.
* If the element at the given index is not focusable, use the moveDirection to step into the next focusable element.
* @param index the index of the element to focus
* @param moveDirection a move direction
* @returns the new focusable element if found, null otherwise
*/
focusIndex: (index: number, moveDirection: -1 | 0 | 1) => HTMLElement | null;
/**
* Focus the previous element, respecting the anscestor direction.
*/
focusPrevious: FocusNeighbour;
/**
* Focus the next element, respecting the anscestor direction.
*/
focusNext: FocusNeighbour;
/**
* Focus the first element, respecting the anscestor direction.
*/
focusFirst: FocusEnd;
/**
* Focus the element at the left-end of the list.
*/
focusFirstLeft: FocusEnd;
/**
* Focus the element at the right-end of the list.
*/
focusFirstRight: FocusEnd;
/**
* Focus the last element, respecting the anscestor direction.
*/
focusLast: FocusEnd;
/**
* Focus the next focusable element to the left of the currently focused element.
*/
focusLeft: FocusNeighbour;
/**
* Focus the next focusable element to the right of the currently focused element.
*/
focusRight: FocusNeighbour;
};

// cf https://html.spec.whatwg.org/multipage/input.html#concept-input-apply
const textInputTypes = new Set(['text', 'search', 'url', 'tel', 'password']);
Expand All @@ -17,7 +75,7 @@ const isTextInput = (element: any): element is HTMLInputElement => element insta
* @param event - keyboard event
* @returns the name of the key, including modifiers
*/
export const getKeyName = (event: KeyboardEvent) => {
export const getKeyName = (event: KeyboardEvent): string => {
let key = event.key;
if (event.shiftKey) {
key = `Shift+${key}`;
Expand All @@ -42,7 +100,7 @@ export const getKeyName = (event: KeyboardEvent) => {
* @returns true if the keyboard event is an ArrowLeft, ArrowRight, Home or End key press that should make the cursor move inside
* the input and false otherwise.
*/
export const isInternalInputNavigation = (event: KeyboardEvent) => {
export const isInternalInputNavigation = (event: KeyboardEvent): boolean => {
const {target, key} = event;
if (isTextInput(target) && (key === 'ArrowLeft' || key === 'ArrowRight' || key === 'Home' || key === 'End')) {
let startPosition: boolean;
Expand All @@ -68,7 +126,7 @@ export const isInternalInputNavigation = (event: KeyboardEvent) => {
* - directiveElement: DOM element which has the navigation manager directive
* - navManager: navigation manager instance
*/
export type NavManagerKeyHandler<T = any> = (info: {directiveElement: HTMLElement; event: Event; navManager: NavManager; context?: T}) => void;
export type NavManagerKeyHandler<T = any> = (info: {directiveElement: HTMLElement; event: Event; navManager: NavManager<T>; context?: T}) => void;

/**
* Type of the parameter of the navigation manager directive.
Expand Down Expand Up @@ -108,7 +166,7 @@ const defaultSelector: NavManagerItemConfig<any>['selector'] = (directiveElement
*
* @returns a new instance of the navigation manager
*/
export const createNavManager = () => {
export const createNavManager = <T>(): NavManager<T> => {
const directiveInstances$ = registrationArray<() => Iterable<HTMLElement>>();
const elementsRefresh$ = writable({});
const refreshElements = (now = true) => {
Expand Down Expand Up @@ -179,7 +237,7 @@ export const createNavManager = () => {
return null;
};

const directive = browserDirective(<T = any>(directiveElement: HTMLElement, config: NavManagerItemConfig<T>) => {
const directive = browserDirective((directiveElement: HTMLElement, config: NavManagerItemConfig<T>) => {
const onKeyDown = (event: KeyboardEvent) => {
if (isInternalInputNavigation(event)) {
return;
Expand Down
6 changes: 5 additions & 1 deletion core/src/services/resizeObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import type {ReadableSignal} from '@amadeus-it-group/tansu';
import {derived} from '@amadeus-it-group/tansu';
import {createBrowserStoreDirective} from '../utils/directive';
import {noop} from '../utils/internal/func';
import type {Directive, SSRHTMLElement} from '../types';

/**
* Create a resize observer object
* @returns An object containing the store with the dimentions of observed element (ResizeObserverEntry), the directive to be applied to the html element to be observed
*/
export const createResizeObserver = () => {
export const createResizeObserver = (): {
dimensions$: ReadableSignal<ResizeObserverEntry | undefined>;
directive: Directive<void, SSRHTMLElement>;
} => {
const {element$, directive} = createBrowserStoreDirective();

const observedElement$ = derived<ResizeObserverEntry | undefined, ReadableSignal<HTMLElement | null>>(
Expand Down
2 changes: 1 addition & 1 deletion core/src/services/transitions/baseTransitions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe(`createTransition`, () => {
onVisibleChange,
},
});
const directiveInstance = transitionInstance.directives.directive(element);
const directiveInstance = transitionInstance.directives.directive(element, {});
await transitionInstance.api.show();
events.push('here');
await transitionInstance.api.hide();
Expand Down
10 changes: 9 additions & 1 deletion core/src/services/transitions/baseTransitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,15 @@ export const createTransition = (config?: PropsConfig<TransitionProps>): Transit
}
};

const directive = mergeDirectives<void | Partial<TransitionProps>>(storeDirective, directiveUpdate(patch), directiveSubscribe(visibleAction$));
const directive = mergeDirectives<void | Partial<TransitionProps>>(
storeDirective,
directiveUpdate((args: void | Partial<TransitionProps>) => {
if (args) {
patch(args);
}
}),
directiveSubscribe(visibleAction$),
);

return {
...stateStores({
Expand Down
3 changes: 2 additions & 1 deletion core/src/services/transitions/simpleClassTransition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {createCSSTransition} from './cssTransitions';
import {addClasses, reflow, removeClasses} from '../../utils/internal/dom';
import type {TransitionFn} from './baseTransitions';

export interface SimpleClassTransitionConfig {
/**
Expand Down Expand Up @@ -42,7 +43,7 @@ export interface SimpleClassTransitionContext {
* @param config - the transition config
* @returns the simple class transition
*/
export const createSimpleClassTransition = (config: SimpleClassTransitionConfig) => {
export const createSimpleClassTransition = (config: SimpleClassTransitionConfig): TransitionFn => {
const {animationPendingClasses, animationPendingShowClasses, animationPendingHideClasses, showClasses, hideClasses} = config;
return createCSSTransition((element, direction, animated, context: SimpleClassTransitionContext) => {
removeClasses(element, showClasses);
Expand Down
2 changes: 1 addition & 1 deletion core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export type DirectivesAndOptParam<T extends any[], U extends SSRHTMLElement = SS

export type SlotContent<Props extends object = object> = undefined | null | string | ((props: Props) => string);

export const INVALID_VALUE = Symbol();
export const INVALID_VALUE: unique symbol = Symbol();
export type NormalizeValue<T> = (value: T) => T | typeof INVALID_VALUE;

export interface WritableWithDefaultOptions<T> {
Expand Down
Loading

0 comments on commit 968badc

Please sign in to comment.