diff --git a/angular/demo/bootstrap/src/app/samples/transition/collapse.component.ts b/angular/demo/bootstrap/src/app/samples/transition/collapse.component.ts index a856c09ddc..ad23ee155a 100644 --- a/angular/demo/bootstrap/src/app/samples/transition/collapse.component.ts +++ b/angular/demo/bootstrap/src/app/samples/transition/collapse.component.ts @@ -1,4 +1,4 @@ -import {ChangeDetectionStrategy, Component, booleanAttribute, input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, effect, input, model} from '@angular/core'; import {UseDirective, collapseVerticalTransition, createTransition, toAngularSignal} from '@agnos-ui/angular-bootstrap'; @Component({ @@ -15,10 +15,10 @@ import {UseDirective, collapseVerticalTransition, createTransition, toAngularSig (click)="transition.api.toggle()" class="btn toggle-button" aria-controls="collapse-content" - [attr.aria-expanded]="state().visible" + [attr.aria-expanded]="expanded()" > {{ headerText() }} - + @@ -26,8 +26,8 @@ import {UseDirective, collapseVerticalTransition, createTransition, toAngularSig - @if (!state().hidden) { -
+ @if (!hidden()) { +
@@ -38,13 +38,23 @@ import {UseDirective, collapseVerticalTransition, createTransition, toAngularSig styles: "@import '@agnos-ui/common/samples/transition/collapse.scss';", }) export default class CollapseComponent { + readonly headerText = input(); + readonly expanded = model(false); + readonly transition = createTransition({ props: { transition: collapseVerticalTransition, + onVisibleChange: (visible) => this.expanded.set(visible), }, }); - readonly state = toAngularSignal(this.transition.state$); + readonly hidden = toAngularSignal(this.transition.stores.hidden$); - readonly headerText = input(); - readonly expanded = input(false, {transform: booleanAttribute}); + constructor() { + effect( + () => { + this.transition.patch({visible: this.expanded()}); + }, + {allowSignalWrites: true}, + ); + } } diff --git a/angular/demo/bootstrap/src/app/samples/transition/collapseDemo.route.ts b/angular/demo/bootstrap/src/app/samples/transition/collapseDemo.route.ts index a4c30eb479..f38bb5192f 100644 --- a/angular/demo/bootstrap/src/app/samples/transition/collapseDemo.route.ts +++ b/angular/demo/bootstrap/src/app/samples/transition/collapseDemo.route.ts @@ -5,6 +5,6 @@ import CollapseComponent from './collapse.component'; standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CollapseComponent], - template: ` Content to display / hide. `, + template: ` Content to display / hide. `, }) export default class CollapseDemoComponent {} diff --git a/angular/demo/bootstrap/src/app/samples/transition/innerComponent.component.ts b/angular/demo/bootstrap/src/app/samples/transition/innerComponent.component.ts index 1a91af5d4c..b862275bbd 100644 --- a/angular/demo/bootstrap/src/app/samples/transition/innerComponent.component.ts +++ b/angular/demo/bootstrap/src/app/samples/transition/innerComponent.component.ts @@ -94,10 +94,7 @@ const paramRemoveFromDom$ = writable(true); @if ((paramRemoveFromDom$ | async) === false || state().hidden === false) { -
+
You can collapse this card by clicking Toggle
@@ -117,6 +114,7 @@ export class InnerComponent { animatedOnInit: paramAnimatedOnInit$, animated: paramAnimated$, visible: paramVisible$, + transition: paramTransition$, }, }); state = toAngularSignal(this.transition.state$); diff --git a/core-bootstrap/src/components/collapse/collapse.ts b/core-bootstrap/src/components/collapse/collapse.ts index 73c778a71f..dee4cff885 100644 --- a/core-bootstrap/src/components/collapse/collapse.ts +++ b/core-bootstrap/src/components/collapse/collapse.ts @@ -1,7 +1,7 @@ import {createTransition} from '@agnos-ui/core/services/transitions/baseTransitions'; import type {ConfigValidator, Directive, PropsConfig, Widget} from '@agnos-ui/core/types'; import {stateStores, writablesForProps} from '@agnos-ui/core/utils/stores'; -import {bindDirectiveNoArg, createAttributesDirective, mergeDirectives} from '@agnos-ui/core/utils/directive'; +import {createAttributesDirective, mergeDirectives} from '@agnos-ui/core/utils/directive'; import {typeBoolean, typeFunction, typeString} from '@agnos-ui/core/utils/writables'; import {collapseHorizontalTransition, collapseVerticalTransition} from '../../services/transitions/collapse'; import {asWritable, computed} from '@amadeus-it-group/tansu'; @@ -178,7 +178,7 @@ export function createCollapse(config?: PropsConfig): CollapseWid }, directives: { collapseDirective: mergeDirectives( - bindDirectiveNoArg(transition.directives.directive), + transition.directives.directive, createAttributesDirective(() => ({ attributes: { id: computed(() => id$() || undefined), diff --git a/core/src/components/accordion/accordion.ts b/core/src/components/accordion/accordion.ts index 7fe0975666..6a861906ae 100644 --- a/core/src/components/accordion/accordion.ts +++ b/core/src/components/accordion/accordion.ts @@ -6,7 +6,7 @@ import {asWritable, computed, readable, writable} from '@amadeus-it-group/tansu' import {noop} from '../../utils/internal/func'; import type {WidgetsCommonPropsAndState} from '../commonProps'; import {typeBoolean, typeFunction, typeString} from '../../utils/writables'; -import {bindDirectiveNoArg, createAttributesDirective, directiveSubscribe, mergeDirectives, registrationArray} from '../../utils/directive'; +import {createAttributesDirective, directiveSubscribe, mergeDirectives, registrationArray} from '../../utils/directive'; import {generateId} from '../../utils/internal/dom'; function adjustItemsCloseOthers(items: AccordionItemWidget[], openItems: string[], oldOpen?: string): AccordionItemWidget[] { @@ -446,7 +446,7 @@ export function createAccordionItem(config?: PropsConfig): A }, }, })); - const transitionDirective = bindDirectiveNoArg(transition.directives.directive); + const transitionDirective = transition.directives.directive; const bodyContainerAttrsDirective = createAttributesDirective(() => ({ attributes: { id: computed(() => `${id$()}-body-container`), diff --git a/core/src/components/alert/common.ts b/core/src/components/alert/common.ts index e0aa2de1ea..78e412f3b3 100644 --- a/core/src/components/alert/common.ts +++ b/core/src/components/alert/common.ts @@ -4,7 +4,6 @@ import type {WidgetsCommonPropsAndState} from '../commonProps'; import type {ConfigValidator, Directive, PropsConfig, Widget} from '../../types'; import {noop} from '../../utils/internal/func'; import {stateStores, writablesForProps} from '../../utils/stores'; -import {bindDirectiveNoArg} from '../../utils/directive'; import {typeBoolean, typeFunction, typeString} from '../../utils/writables'; export interface CommonAlertCommonPropsAndState extends WidgetsCommonPropsAndState { @@ -198,7 +197,7 @@ export function createCommonAlert(config?: PropsConfig): Commo close: transition.api.hide, }, directives: { - transitionDirective: bindDirectiveNoArg(transition.directives.directive), + transitionDirective: transition.directives.directive, }, }; } diff --git a/core/src/components/modal/modal.ts b/core/src/components/modal/modal.ts index 5407a95209..ae9c68f8f5 100644 --- a/core/src/components/modal/modal.ts +++ b/core/src/components/modal/modal.ts @@ -10,7 +10,6 @@ import {noop} from '../../utils/internal/func'; import {removeScrollbars, revertScrollbars} from '../../utils/internal/scrollbars'; import { bindDirective, - bindDirectiveNoArg, browserDirective, createAttributesDirective, directiveSubscribe, @@ -472,13 +471,8 @@ export function createModal(config$?: PropsConfig): ModalWidget { directives: { modalPortalDirective, backdropPortalDirective, - backdropDirective: mergeDirectives(bindDirectiveNoArg(backdropTransition.directives.directive), backdropAttributeDirective), - modalDirective: mergeDirectives( - bindDirectiveNoArg(modalTransition.directives.directive), - sliblingsInert, - directiveSubscribe(action$), - modalAttributeDirective, - ), + backdropDirective: mergeDirectives(backdropTransition.directives.directive, backdropAttributeDirective), + modalDirective: mergeDirectives(modalTransition.directives.directive, sliblingsInert, directiveSubscribe(action$), modalAttributeDirective), closeButtonDirective, dialogDirective: bindDirective( browserDirective((dialog: HTMLDialogElement, visible: boolean) => { diff --git a/core/src/services/transitions/baseTransitions.spec.ts b/core/src/services/transitions/baseTransitions.spec.ts index 2d6245bc68..44e5abc37e 100644 --- a/core/src/services/transitions/baseTransitions.spec.ts +++ b/core/src/services/transitions/baseTransitions.spec.ts @@ -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(); @@ -479,7 +479,7 @@ describe(`createTransition`, () => { events.push(`state = ${JSON.stringify(state)}`); }); events.push('beforeCallingDirective1'); - let directiveInstance = transitionInstance.directives.directive(element, {}); + let directiveInstance = transitionInstance.directives.directive(element); events.push('afterCallingDirective1'); await promiseFromStore(transitionInstance.stores.shown$).promise; events.push('beforeDestroyingDirective1'); @@ -487,7 +487,7 @@ describe(`createTransition`, () => { events.push('afterDestroyingDirective1'); element = {id: 'domEl2'}; events.push('beforeCallingDirective2'); - directiveInstance = transitionInstance.directives.directive(element, {}); + directiveInstance = transitionInstance.directives.directive(element); events.push('afterCallingDirective2'); await promiseFromStore(transitionInstance.stores.shown$).promise; events.push('beforeDestroyingDirective2'); @@ -495,7 +495,8 @@ describe(`createTransition`, () => { events.push('afterDestroyingDirective2'); element = {id: 'domEl3'}; events.push('beforeCallingDirective3'); - directiveInstance = transitionInstance.directives.directive(element, {visible: false}); + transitionInstance.patch({visible: false}); + directiveInstance = transitionInstance.directives.directive(element); events.push('afterCallingDirective3'); await promiseFromStore(transitionInstance.stores.hidden$).promise; events.push('beforeDestroyingDirective3'); @@ -553,6 +554,14 @@ describe(`createTransition`, () => { 'afterDestroyingDirective2', 'beforeCallingDirective3', 'onVisibleChange:false', + `state = ${JSON.stringify({ + visible: false, + element: null, + elementPresent: false, + transitioning: false, + shown: false, + hidden: true, + })}`, `transitionStart:3:domEl3:hide:anim=false:ctxt=3`, `state = ${JSON.stringify({ visible: false, @@ -594,7 +603,7 @@ describe(`createTransition`, () => { events.push(`state = ${JSON.stringify(state)}`); }); events.push('beforeCallingDirective1'); - let directiveInstance = transitionInstance.directives.directive(element, {}); + let directiveInstance = transitionInstance.directives.directive(element); events.push('afterCallingDirective1'); await promiseFromStore(transitionInstance.stores.hidden$).promise; events.push('beforeDestroyingDirective1'); @@ -602,7 +611,8 @@ describe(`createTransition`, () => { events.push('afterDestroyingDirective1'); element = {id: 'domEl2'}; events.push('beforeCallingDirective2'); - directiveInstance = transitionInstance.directives.directive(element, {visible: true}); + transitionInstance.patch({visible: true}); + directiveInstance = transitionInstance.directives.directive(element); events.push('afterCallingDirective2'); await promiseFromStore(transitionInstance.stores.shown$).promise; events.push('beforeDestroyingDirective2'); @@ -637,6 +647,14 @@ describe(`createTransition`, () => { 'afterDestroyingDirective1', 'beforeCallingDirective2', 'onVisibleChange:true', + `state = ${JSON.stringify({ + visible: true, + element: null, + elementPresent: false, + transitioning: false, + shown: false, + hidden: false, + })}`, `transitionStart:2:domEl2:show:anim=${animated}:ctxt=2`, `state = ${JSON.stringify({ visible: true, diff --git a/core/src/services/transitions/baseTransitions.ts b/core/src/services/transitions/baseTransitions.ts index bf0f850d51..930ef4b56d 100644 --- a/core/src/services/transitions/baseTransitions.ts +++ b/core/src/services/transitions/baseTransitions.ts @@ -4,7 +4,7 @@ import type {ConfigValidator, Directive, PropsConfig, SSRHTMLElement, Widget} fr import {promiseWithResolve} from '../../utils/internal/promise'; import {noop} from '../../utils/internal/func'; import {bindableDerived, stateStores, writablesForProps} from '../../utils/stores'; -import {createStoreDirective, directiveSubscribe, directiveUpdate, mergeDirectives} from '../../utils/directive'; +import {createStoreDirective, directiveSubscribe, mergeDirectives} from '../../utils/directive'; /** * Function that implements a transition. @@ -158,7 +158,7 @@ export interface TransitionDirectives { /** * the transition directive */ - directive: Directive>; + directive: Directive; } export type TransitionWidget = Widget; @@ -338,15 +338,7 @@ export const createTransition = (config?: PropsConfig): Transit } }; - const directive = mergeDirectives>( - storeDirective, - directiveUpdate((args: void | Partial) => { - if (args) { - patch(args); - } - }), - directiveSubscribe(visibleAction$), - ); + const directive = mergeDirectives(storeDirective, directiveSubscribe(visibleAction$)); return { ...stateStores({ diff --git a/demo/src/routes/menu/CollapsibleSection.svelte b/demo/src/routes/menu/CollapsibleSection.svelte index aa4ce2e26b..b3d22f3e76 100644 --- a/demo/src/routes/menu/CollapsibleSection.svelte +++ b/demo/src/routes/menu/CollapsibleSection.svelte @@ -39,6 +39,7 @@ animated: paramAnimated$, animatedOnInit: paramAnimated$, visible: paramVisible$, + transition: collapseVerticalTransition, }, }); @@ -58,7 +59,7 @@ -
+
{@render children()}
diff --git a/react/demo/src/bootstrap/samples/transition/Collapse.tsx b/react/demo/src/bootstrap/samples/transition/Collapse.tsx index 4cc31027fd..f7ef8ba89e 100644 --- a/react/demo/src/bootstrap/samples/transition/Collapse.tsx +++ b/react/demo/src/bootstrap/samples/transition/Collapse.tsx @@ -1,11 +1,11 @@ -import {useMemo, type PropsWithChildren} from 'react'; +import {type PropsWithChildren} from 'react'; import {createTransition} from '@agnos-ui/react-bootstrap/services/transitions/baseTransitions'; import {collapseVerticalTransition} from '@agnos-ui/react-bootstrap/services/transitions/bootstrap'; import {useDirective} from '@agnos-ui/react-bootstrap/utils/directive'; import type {Directive} from '@agnos-ui/react-bootstrap/types'; import '@agnos-ui/common/samples/transition/collapse.scss'; import CollapseIcon from '@agnos-ui/common/samples/transition/collapseButton.svg?react'; -import {useObservable} from '@agnos-ui/react-bootstrap/utils/stores'; +import {useWidget} from '@agnos-ui/react-bootstrap/utils/widget'; const CollapseContent = ({directive, children}: PropsWithChildren<{directive: Directive}>) => (
@@ -13,22 +13,17 @@ const CollapseContent = ({directive, children}: PropsWithChildren<{directive: Di
); -const Collapse = ({expanded, headerText, children}: PropsWithChildren<{expanded?: boolean; headerText: string}>) => { +const Collapse = ({ + expanded, + onExpandedChange, + headerText, + children, +}: PropsWithChildren<{expanded?: boolean; onExpandedChange?: (expanded: boolean) => void; headerText: string}>) => { const { - state$, + state, directives: {directive}, api: {toggle}, - } = useMemo( - () => - createTransition({ - props: { - visible: expanded, - transition: collapseVerticalTransition, - }, - }), - [], - ); - const state = useObservable(state$); + } = useWidget(createTransition, {visible: expanded, transition: collapseVerticalTransition, onVisibleChange: onExpandedChange}); return (
diff --git a/svelte/demo/src/bootstrap/samples/transition/Collapse.svelte b/svelte/demo/src/bootstrap/samples/transition/Collapse.svelte index 9dcf76648c..e07c357d33 100644 --- a/svelte/demo/src/bootstrap/samples/transition/Collapse.svelte +++ b/svelte/demo/src/bootstrap/samples/transition/Collapse.svelte @@ -6,7 +6,7 @@ import type {Snippet} from 'svelte'; import {callWidgetFactory} from '@agnos-ui/svelte-bootstrap/config'; - let {headerText, expanded = false, children}: {headerText: string; expanded: boolean; children: Snippet} = $props(); + let {headerText, expanded = $bindable(), children}: {headerText: string; expanded: boolean; children: Snippet} = $props(); const { state, @@ -14,9 +14,17 @@ directives: {directive}, } = callWidgetFactory({ factory: createTransition, - props: { - visible: expanded, - transition: collapseVerticalTransition, + get props() { + return { + visible: expanded, + transition: collapseVerticalTransition, + }; + }, + enablePatchChanged: true, + events: { + onVisibleChange: (val: boolean) => { + expanded = val; + }, }, }); diff --git a/svelte/demo/src/bootstrap/samples/transition/InnerComponent.svelte b/svelte/demo/src/bootstrap/samples/transition/InnerComponent.svelte index 9f8cc7721c..ee504a837d 100644 --- a/svelte/demo/src/bootstrap/samples/transition/InnerComponent.svelte +++ b/svelte/demo/src/bootstrap/samples/transition/InnerComponent.svelte @@ -22,6 +22,7 @@ animatedOnInit: paramAnimatedOnInit$, animated: paramAnimated$, visible: paramVisible$, + transition: paramTransition$, }, }); @@ -81,7 +82,7 @@ {#if !$paramRemoveFromDom$ || !$hidden$} -
+
You can collapse this card by clicking Toggle