@mittwald/flow-react-components 1.6.0-next.39 → 1.6.0-next.40

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.
@@ -13,6 +13,7 @@ var useCloseOverlayConfirmationController = (parentController) => {
13
13
  });
14
14
  useEffect(() => {
15
15
  if (showConfirmationModal) confirmationModalController.open();
16
+ else confirmationModalController.close();
16
17
  }, [confirmationModalController, showConfirmationModal]);
17
18
  if (parentController.confirmClose) return {
18
19
  controller: confirmationModalController,
@@ -1 +1 @@
1
- {"version":3,"file":"useCloseOverlayConfirmationController.mjs","names":[],"sources":["../../../../../../../../src/lib/controller/overlay/useCloseOverlayConfirmationController.ts"],"sourcesContent":["import type { OverlayController } from \"@/lib/controller/overlay/OverlayController\";\nimport { useModalController } from \"@/lib/controller/overlay/useModalController\";\nimport { useEffect } from \"react\";\n\nexport const useCloseOverlayConfirmationController = (\n parentController: OverlayController,\n) => {\n const showConfirmationModal = parentController.useShowConfirmationModal();\n\n const confirmationModalController = useModalController({\n reuseControllerFromContext: false,\n onClose: () => {\n if (showConfirmationModal) {\n parentController.cancelConfirmation();\n }\n },\n });\n\n useEffect(() => {\n if (showConfirmationModal) {\n confirmationModalController.open();\n }\n }, [confirmationModalController, showConfirmationModal]);\n\n if (parentController.confirmClose) {\n return {\n controller: confirmationModalController,\n cancel: () => parentController.cancelConfirmation(),\n confirm: () => {\n parentController.confirmClose();\n parentController.close();\n },\n } as const;\n }\n};\n"],"mappings":";;;;;AAIA,IAAa,yCACX,qBACG;CACH,MAAM,wBAAwB,iBAAiB,yBAAyB;CAExE,MAAM,8BAA8B,mBAAmB;EACrD,4BAA4B;EAC5B,eAAe;GACb,IAAI,uBACF,iBAAiB,mBAAmB;EAExC;CACF,CAAC;CAED,gBAAgB;EACd,IAAI,uBACF,4BAA4B,KAAK;CAErC,GAAG,CAAC,6BAA6B,qBAAqB,CAAC;CAEvD,IAAI,iBAAiB,cACnB,OAAO;EACL,YAAY;EACZ,cAAc,iBAAiB,mBAAmB;EAClD,eAAe;GACb,iBAAiB,aAAa;GAC9B,iBAAiB,MAAM;EACzB;CACF;AAEJ"}
1
+ {"version":3,"file":"useCloseOverlayConfirmationController.mjs","names":[],"sources":["../../../../../../../../src/lib/controller/overlay/useCloseOverlayConfirmationController.ts"],"sourcesContent":["import type { OverlayController } from \"@/lib/controller/overlay/OverlayController\";\nimport { useModalController } from \"@/lib/controller/overlay/useModalController\";\nimport { useEffect } from \"react\";\n\nexport const useCloseOverlayConfirmationController = (\n parentController: OverlayController,\n) => {\n const showConfirmationModal = parentController.useShowConfirmationModal();\n\n const confirmationModalController = useModalController({\n reuseControllerFromContext: false,\n onClose: () => {\n if (showConfirmationModal) {\n parentController.cancelConfirmation();\n }\n },\n });\n\n useEffect(() => {\n if (showConfirmationModal) {\n confirmationModalController.open();\n } else {\n confirmationModalController.close();\n }\n }, [confirmationModalController, showConfirmationModal]);\n\n if (parentController.confirmClose) {\n return {\n controller: confirmationModalController,\n cancel: () => parentController.cancelConfirmation(),\n confirm: () => {\n parentController.confirmClose();\n parentController.close();\n },\n } as const;\n }\n};\n"],"mappings":";;;;;AAIA,IAAa,yCACX,qBACG;CACH,MAAM,wBAAwB,iBAAiB,yBAAyB;CAExE,MAAM,8BAA8B,mBAAmB;EACrD,4BAA4B;EAC5B,eAAe;GACb,IAAI,uBACF,iBAAiB,mBAAmB;EAExC;CACF,CAAC;CAED,gBAAgB;EACd,IAAI,uBACF,4BAA4B,KAAK;OAEjC,4BAA4B,MAAM;CAEtC,GAAG,CAAC,6BAA6B,qBAAqB,CAAC;CAEvD,IAAI,iBAAiB,cACnB,OAAO;EACL,YAAY;EACZ,cAAc,iBAAiB,mBAAmB;EAClD,eAAe;GACb,iBAAiB,aAAa;GAC9B,iBAAiB,MAAM;EACzB;CACF;AAEJ"}
@@ -71,11 +71,20 @@ var OverlayState = class {
71
71
  * @internal Bindings call it from their lifecycle.
72
72
  */
73
73
  setConfirmOnCloseSource(source, confirmOnClose) {
74
- runInAction(() => this.confirmOnCloseSources.set(source, confirmOnClose));
74
+ runInAction(() => {
75
+ this.confirmOnCloseSources.set(source, confirmOnClose);
76
+ this.withdrawUnneededConfirmation();
77
+ });
75
78
  }
76
79
  /** @internal Bindings call it from their lifecycle. */
77
80
  removeConfirmOnCloseSource(source) {
78
- runInAction(() => this.confirmOnCloseSources.delete(source));
81
+ runInAction(() => {
82
+ this.confirmOnCloseSources.delete(source);
83
+ this.withdrawUnneededConfirmation();
84
+ });
85
+ }
86
+ withdrawUnneededConfirmation() {
87
+ if (!this.confirmOnCloseEnabled) this.showConfirmationModal = false;
79
88
  }
80
89
  addOpenStateHandler(handler, handlersSet) {
81
90
  handlersSet.add(handler);
@@ -125,10 +134,19 @@ var OverlayState = class {
125
134
  /**
126
135
  * Sets the open state without running any handler. Use it to mirror a
127
136
  * controlled `isOpen` prop, whose change comes from the consumer – notifying
128
- * them of their own state would echo it back.
137
+ * them of their own state would echo it back. Closing this way drops a
138
+ * pending close confirmation as well.
129
139
  */
130
140
  syncOpen(toOpen) {
141
+ this.assignOpenState(toOpen);
142
+ }
143
+ /** A closed overlay has no close left to confirm. */
144
+ assignOpenState(toOpen) {
131
145
  this.isOpen = toOpen;
146
+ if (!toOpen) {
147
+ this.showConfirmationModal = false;
148
+ this.closeIsConfirmed = false;
149
+ }
132
150
  }
133
151
  applyOpenState(toOpen, options) {
134
152
  const { bypassConfirmation = false } = options ?? {};
@@ -144,7 +162,7 @@ var OverlayState = class {
144
162
  if (toOpen) aborted = this.executeOnOpen();
145
163
  else aborted = this.executeOnClose();
146
164
  if (!aborted) aborted = this.executeOnOpenChange(toOpen);
147
- if (!aborted) this.isOpen = toOpen;
165
+ if (!aborted) this.assignOpenState(toOpen);
148
166
  }
149
167
  setIsContentSuspended(to) {
150
168
  this.isContentSuspended = to;
@@ -1 +1 @@
1
- {"version":3,"file":"OverlayState.mjs","names":[],"sources":["../../../../../../../components-base/src/overlay/OverlayState.ts"],"sourcesContent":["import {\n action,\n computed,\n makeObservable,\n observable,\n runInAction,\n} from \"mobx\";\n\nexport type OverlayOpenHandler = () => unknown;\nexport type OverlayCloseHandler = () => unknown;\nexport type OverlayOpenStateHandler = (isOpen: boolean) => unknown;\nexport type AnyOverlayOpenStateHandler =\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler;\n\nexport type DisposerFn = () => void;\n\nexport interface OverlayCloseOptions {\n /** Close without asking, even when the overlay requires confirmation. */\n bypassConfirmation?: boolean;\n}\n\nexport interface OverlayStateOptions {\n isDefaultOpen?: boolean;\n /**\n * Whether closing the overlay must be confirmed while no source has an\n * opinion (`setConfirmOnCloseSource`).\n */\n confirmOnClose?: boolean;\n}\n\n/**\n * Whether an overlay is open, and whether closing it has to be confirmed first.\n * Each binding adds its subscription and its lifecycle: React's\n * `OverlayController` subclasses it, Vue's adapter wraps it in refs.\n *\n * `TCloseOptions` lets a binding pass more on a close than the state reads —\n * React's `overlay`, resolved by `Action` before the close arrives here.\n */\nexport class OverlayState<\n TCloseOptions extends OverlayCloseOptions = OverlayCloseOptions,\n> {\n public isOpen = false;\n public isContentSuspended = false;\n private onOpenHandlers = new Set<OverlayOpenHandler>();\n private onCloseHandlers = new Set<OverlayCloseHandler>();\n private onOpenChangeHandlers = new Set<OverlayOpenStateHandler>();\n\n public showConfirmationModal = false;\n public closeIsConfirmed = false;\n private readonly confirmOnCloseFromOptions: boolean;\n /**\n * Every mounted component that has an opinion on close confirmation\n * contributes one entry here – e.g. a `Modal` with a `confirmOnClose` prop\n * and a `Form` tracking its dirty state. Combining them instead of assigning\n * a single flag keeps them from overwriting each other.\n */\n private confirmOnCloseSources = observable.map<object, boolean>();\n /**\n * Active grants that allow closing without a confirmation. Each entry is\n * scoped to an ongoing operation that legitimately closes the overlay – e.g.\n * a `Form` submit – and is dropped again when that operation has finished, so\n * an operation that does _not_ close the overlay leaves the confirmation\n * armed.\n */\n private closeWithoutConfirmationGrants = new Set<object>();\n /**\n * The open state a `setOpen` call is currently applying. Handlers run before\n * `isOpen` is assigned, so a handler that calls `setOpen` again would not be\n * caught by the equality check at the top and would recurse.\n */\n private openStateInFlight: boolean | undefined;\n\n public constructor(options: OverlayStateOptions = {}) {\n makeObservable(this, {\n isOpen: observable,\n isContentSuspended: observable,\n showConfirmationModal: observable,\n confirmOnCloseEnabled: computed,\n open: action.bound,\n close: action.bound,\n toggle: action.bound,\n setOpen: action.bound,\n syncOpen: action.bound,\n setIsContentSuspended: action.bound,\n confirmClose: action.bound,\n cancelConfirmation: action.bound,\n });\n const { isDefaultOpen = false, confirmOnClose = false } = options;\n this.isOpen = isDefaultOpen;\n this.confirmOnCloseFromOptions = confirmOnClose;\n }\n\n /** Whether closing this overlay currently requires a confirmation. */\n public get confirmOnCloseEnabled(): boolean {\n if (this.confirmOnCloseSources.size === 0) {\n return this.confirmOnCloseFromOptions;\n }\n return Array.from(this.confirmOnCloseSources.values()).some(Boolean);\n }\n\n /**\n * Sets the opinion of one source – any object the caller keeps – on close\n * confirmation. Remove the source again when its owner goes away.\n *\n * @internal Bindings call it from their lifecycle.\n */\n public setConfirmOnCloseSource(source: object, confirmOnClose: boolean) {\n runInAction(() => this.confirmOnCloseSources.set(source, confirmOnClose));\n }\n\n /** @internal Bindings call it from their lifecycle. */\n public removeConfirmOnCloseSource(source: object) {\n runInAction(() => this.confirmOnCloseSources.delete(source));\n }\n\n private addOpenStateHandler<T extends AnyOverlayOpenStateHandler>(\n handler: T,\n handlersSet: Set<T>,\n ): DisposerFn {\n handlersSet.add(handler);\n return () => {\n handlersSet.delete(handler);\n };\n }\n\n private executeHandlers(\n isOpen: boolean,\n handlers: Set<\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler\n >,\n ): boolean {\n const handlerResult = Array.from(handlers).map((handler) =>\n handler(isOpen),\n );\n return handlerResult.some((result) => result === false);\n }\n\n private executeOnClose(): boolean {\n return this.executeHandlers(false, this.onCloseHandlers);\n }\n\n private executeOnOpen(): boolean {\n return this.executeHandlers(true, this.onOpenHandlers);\n }\n\n private executeOnOpenChange(isOpen: boolean): boolean {\n return this.executeHandlers(isOpen, this.onOpenChangeHandlers);\n }\n\n public addOnClose(handler: OverlayCloseHandler) {\n return this.addOpenStateHandler(handler, this.onCloseHandlers);\n }\n\n public addOnOpen(handler: OverlayOpenHandler) {\n return this.addOpenStateHandler(handler, this.onOpenHandlers);\n }\n\n public addOnOpenChange(handler: OverlayOpenStateHandler) {\n return this.addOpenStateHandler(handler, this.onOpenChangeHandlers);\n }\n\n public open(): void {\n this.setOpen(true);\n }\n\n public close(options?: TCloseOptions): void {\n this.setOpen(false, options);\n }\n\n public toggle(): void {\n this.setOpen(!this.isOpen);\n }\n\n public setOpen(toOpen: boolean, options?: TCloseOptions): void {\n if (this.isOpen === toOpen || this.openStateInFlight === toOpen) {\n return;\n }\n\n this.openStateInFlight = toOpen;\n try {\n this.applyOpenState(toOpen, options);\n } finally {\n this.openStateInFlight = undefined;\n }\n }\n\n /**\n * Sets the open state without running any handler. Use it to mirror a\n * controlled `isOpen` prop, whose change comes from the consumer – notifying\n * them of their own state would echo it back.\n */\n public syncOpen(toOpen: boolean): void {\n this.isOpen = toOpen;\n }\n\n private applyOpenState(toOpen: boolean, options?: TCloseOptions): void {\n const { bypassConfirmation = false } = options ?? {};\n\n if (toOpen === false) {\n // The confirmation is a one-shot permission for exactly this close\n // attempt – consume it here instead of only when a close succeeds, so it\n // cannot survive an aborted close and disarm later attempts.\n const closeIsConfirmed = this.closeIsConfirmed;\n this.closeIsConfirmed = false;\n\n if (\n this.confirmOnCloseEnabled &&\n !closeIsConfirmed &&\n this.closeWithoutConfirmationGrants.size === 0 &&\n !bypassConfirmation\n ) {\n this.showConfirmationModal = true;\n return;\n }\n }\n\n let aborted: boolean;\n if (toOpen) {\n aborted = this.executeOnOpen();\n } else {\n aborted = this.executeOnClose();\n }\n if (!aborted) {\n aborted = this.executeOnOpenChange(toOpen);\n }\n\n if (!aborted) {\n this.isOpen = toOpen;\n }\n }\n\n public setIsContentSuspended(to: boolean): void {\n this.isContentSuspended = to;\n }\n\n /**\n * Allows closing this overlay without a confirmation until the returned\n * disposer is called. Use it around an operation that may legitimately close\n * the overlay – e.g. a `Form` submit, where a \"discard unsaved changes?\"\n * prompt would be nonsense – and dispose it as soon as that operation has\n * finished. An operation that ends without closing the overlay therefore\n * leaves the confirmation armed (#2775).\n */\n public grantCloseWithoutConfirmation(): DisposerFn {\n const grant = {};\n this.closeWithoutConfirmationGrants.add(grant);\n return () => {\n this.closeWithoutConfirmationGrants.delete(grant);\n };\n }\n\n public confirmClose(): void {\n this.closeIsConfirmed = true;\n this.showConfirmationModal = false;\n }\n\n public cancelConfirmation(): void {\n this.showConfirmationModal = false;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OverlayState.mjs","names":[],"sources":["../../../../../../../components-base/src/overlay/OverlayState.ts"],"sourcesContent":["import {\n action,\n computed,\n makeObservable,\n observable,\n runInAction,\n} from \"mobx\";\n\nexport type OverlayOpenHandler = () => unknown;\nexport type OverlayCloseHandler = () => unknown;\nexport type OverlayOpenStateHandler = (isOpen: boolean) => unknown;\nexport type AnyOverlayOpenStateHandler =\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler;\n\nexport type DisposerFn = () => void;\n\nexport interface OverlayCloseOptions {\n /** Close without asking, even when the overlay requires confirmation. */\n bypassConfirmation?: boolean;\n}\n\nexport interface OverlayStateOptions {\n isDefaultOpen?: boolean;\n /**\n * Whether closing the overlay must be confirmed while no source has an\n * opinion (`setConfirmOnCloseSource`).\n */\n confirmOnClose?: boolean;\n}\n\n/**\n * Whether an overlay is open, and whether closing it has to be confirmed first.\n * Each binding adds its subscription and its lifecycle: React's\n * `OverlayController` subclasses it, Vue's adapter wraps it in refs.\n *\n * `TCloseOptions` lets a binding pass more on a close than the state reads —\n * React's `overlay`, resolved by `Action` before the close arrives here.\n */\nexport class OverlayState<\n TCloseOptions extends OverlayCloseOptions = OverlayCloseOptions,\n> {\n public isOpen = false;\n public isContentSuspended = false;\n private onOpenHandlers = new Set<OverlayOpenHandler>();\n private onCloseHandlers = new Set<OverlayCloseHandler>();\n private onOpenChangeHandlers = new Set<OverlayOpenStateHandler>();\n\n public showConfirmationModal = false;\n public closeIsConfirmed = false;\n private readonly confirmOnCloseFromOptions: boolean;\n /**\n * Every mounted component that has an opinion on close confirmation\n * contributes one entry here – e.g. a `Modal` with a `confirmOnClose` prop\n * and a `Form` tracking its dirty state. Combining them instead of assigning\n * a single flag keeps them from overwriting each other.\n */\n private confirmOnCloseSources = observable.map<object, boolean>();\n /**\n * Active grants that allow closing without a confirmation. Each entry is\n * scoped to an ongoing operation that legitimately closes the overlay – e.g.\n * a `Form` submit – and is dropped again when that operation has finished, so\n * an operation that does _not_ close the overlay leaves the confirmation\n * armed.\n */\n private closeWithoutConfirmationGrants = new Set<object>();\n /**\n * The open state a `setOpen` call is currently applying. Handlers run before\n * `isOpen` is assigned, so a handler that calls `setOpen` again would not be\n * caught by the equality check at the top and would recurse.\n */\n private openStateInFlight: boolean | undefined;\n\n public constructor(options: OverlayStateOptions = {}) {\n makeObservable(this, {\n isOpen: observable,\n isContentSuspended: observable,\n showConfirmationModal: observable,\n confirmOnCloseEnabled: computed,\n open: action.bound,\n close: action.bound,\n toggle: action.bound,\n setOpen: action.bound,\n syncOpen: action.bound,\n setIsContentSuspended: action.bound,\n confirmClose: action.bound,\n cancelConfirmation: action.bound,\n });\n const { isDefaultOpen = false, confirmOnClose = false } = options;\n this.isOpen = isDefaultOpen;\n this.confirmOnCloseFromOptions = confirmOnClose;\n }\n\n /** Whether closing this overlay currently requires a confirmation. */\n public get confirmOnCloseEnabled(): boolean {\n if (this.confirmOnCloseSources.size === 0) {\n return this.confirmOnCloseFromOptions;\n }\n return Array.from(this.confirmOnCloseSources.values()).some(Boolean);\n }\n\n /**\n * Sets the opinion of one source – any object the caller keeps – on close\n * confirmation. Remove the source again when its owner goes away.\n *\n * @internal Bindings call it from their lifecycle.\n */\n public setConfirmOnCloseSource(source: object, confirmOnClose: boolean) {\n runInAction(() => {\n this.confirmOnCloseSources.set(source, confirmOnClose);\n this.withdrawUnneededConfirmation();\n });\n }\n\n /** @internal Bindings call it from their lifecycle. */\n public removeConfirmOnCloseSource(source: object) {\n runInAction(() => {\n this.confirmOnCloseSources.delete(source);\n this.withdrawUnneededConfirmation();\n });\n }\n\n private withdrawUnneededConfirmation() {\n if (!this.confirmOnCloseEnabled) {\n this.showConfirmationModal = false;\n }\n }\n\n private addOpenStateHandler<T extends AnyOverlayOpenStateHandler>(\n handler: T,\n handlersSet: Set<T>,\n ): DisposerFn {\n handlersSet.add(handler);\n return () => {\n handlersSet.delete(handler);\n };\n }\n\n private executeHandlers(\n isOpen: boolean,\n handlers: Set<\n OverlayOpenHandler | OverlayCloseHandler | OverlayOpenStateHandler\n >,\n ): boolean {\n const handlerResult = Array.from(handlers).map((handler) =>\n handler(isOpen),\n );\n return handlerResult.some((result) => result === false);\n }\n\n private executeOnClose(): boolean {\n return this.executeHandlers(false, this.onCloseHandlers);\n }\n\n private executeOnOpen(): boolean {\n return this.executeHandlers(true, this.onOpenHandlers);\n }\n\n private executeOnOpenChange(isOpen: boolean): boolean {\n return this.executeHandlers(isOpen, this.onOpenChangeHandlers);\n }\n\n public addOnClose(handler: OverlayCloseHandler) {\n return this.addOpenStateHandler(handler, this.onCloseHandlers);\n }\n\n public addOnOpen(handler: OverlayOpenHandler) {\n return this.addOpenStateHandler(handler, this.onOpenHandlers);\n }\n\n public addOnOpenChange(handler: OverlayOpenStateHandler) {\n return this.addOpenStateHandler(handler, this.onOpenChangeHandlers);\n }\n\n public open(): void {\n this.setOpen(true);\n }\n\n public close(options?: TCloseOptions): void {\n this.setOpen(false, options);\n }\n\n public toggle(): void {\n this.setOpen(!this.isOpen);\n }\n\n public setOpen(toOpen: boolean, options?: TCloseOptions): void {\n if (this.isOpen === toOpen || this.openStateInFlight === toOpen) {\n return;\n }\n\n this.openStateInFlight = toOpen;\n try {\n this.applyOpenState(toOpen, options);\n } finally {\n this.openStateInFlight = undefined;\n }\n }\n\n /**\n * Sets the open state without running any handler. Use it to mirror a\n * controlled `isOpen` prop, whose change comes from the consumer – notifying\n * them of their own state would echo it back. Closing this way drops a\n * pending close confirmation as well.\n */\n public syncOpen(toOpen: boolean): void {\n this.assignOpenState(toOpen);\n }\n\n /** A closed overlay has no close left to confirm. */\n private assignOpenState(toOpen: boolean): void {\n this.isOpen = toOpen;\n if (!toOpen) {\n this.showConfirmationModal = false;\n this.closeIsConfirmed = false;\n }\n }\n\n private applyOpenState(toOpen: boolean, options?: TCloseOptions): void {\n const { bypassConfirmation = false } = options ?? {};\n\n if (toOpen === false) {\n // The confirmation is a one-shot permission for exactly this close\n // attempt – consume it here instead of only when a close succeeds, so it\n // cannot survive an aborted close and disarm later attempts.\n const closeIsConfirmed = this.closeIsConfirmed;\n this.closeIsConfirmed = false;\n\n if (\n this.confirmOnCloseEnabled &&\n !closeIsConfirmed &&\n this.closeWithoutConfirmationGrants.size === 0 &&\n !bypassConfirmation\n ) {\n this.showConfirmationModal = true;\n return;\n }\n }\n\n let aborted: boolean;\n if (toOpen) {\n aborted = this.executeOnOpen();\n } else {\n aborted = this.executeOnClose();\n }\n if (!aborted) {\n aborted = this.executeOnOpenChange(toOpen);\n }\n\n if (!aborted) {\n this.assignOpenState(toOpen);\n }\n }\n\n public setIsContentSuspended(to: boolean): void {\n this.isContentSuspended = to;\n }\n\n /**\n * Allows closing this overlay without a confirmation until the returned\n * disposer is called. Use it around an operation that may legitimately close\n * the overlay – e.g. a `Form` submit, where a \"discard unsaved changes?\"\n * prompt would be nonsense – and dispose it as soon as that operation has\n * finished. An operation that ends without closing the overlay therefore\n * leaves the confirmation armed (#2775).\n */\n public grantCloseWithoutConfirmation(): DisposerFn {\n const grant = {};\n this.closeWithoutConfirmationGrants.add(grant);\n return () => {\n this.closeWithoutConfirmationGrants.delete(grant);\n };\n }\n\n public confirmClose(): void {\n this.closeIsConfirmed = true;\n this.showConfirmationModal = false;\n }\n\n public cancelConfirmation(): void {\n this.showConfirmationModal = false;\n }\n}\n"],"mappings":"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"}
@@ -56,6 +56,7 @@ export declare class OverlayState<TCloseOptions extends OverlayCloseOptions = Ov
56
56
  constructor(options?: OverlayStateOptions);
57
57
  /** Whether closing this overlay currently requires a confirmation. */
58
58
  get confirmOnCloseEnabled(): boolean;
59
+ private withdrawUnneededConfirmation;
59
60
  private addOpenStateHandler;
60
61
  private executeHandlers;
61
62
  private executeOnClose;
@@ -71,9 +72,12 @@ export declare class OverlayState<TCloseOptions extends OverlayCloseOptions = Ov
71
72
  /**
72
73
  * Sets the open state without running any handler. Use it to mirror a
73
74
  * controlled `isOpen` prop, whose change comes from the consumer – notifying
74
- * them of their own state would echo it back.
75
+ * them of their own state would echo it back. Closing this way drops a
76
+ * pending close confirmation as well.
75
77
  */
76
78
  syncOpen(toOpen: boolean): void;
79
+ /** A closed overlay has no close left to confirm. */
80
+ private assignOpenState;
77
81
  private applyOpenState;
78
82
  setIsContentSuspended(to: boolean): void;
79
83
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/flow-react-components",
3
- "version": "1.6.0-next.39",
3
+ "version": "1.6.0-next.40",
4
4
  "type": "module",
5
5
  "description": "A React implementation of Flow, mittwald’s design system",
6
6
  "homepage": "https://flow.mittwald.de",
@@ -84,9 +84,9 @@
84
84
  "@internationalized/string-compiler": "^3.4.1",
85
85
  "@lezer/common": "^1.5.2",
86
86
  "@lezer/highlight": "^1.2.3",
87
- "@mittwald/flow-icons": "^1.6.0-next.39",
87
+ "@mittwald/flow-icons": "^1.6.0-next.40",
88
88
  "@mittwald/password-tools-js": "^3.0.0",
89
- "@mittwald/react-tunnel": "^1.6.0-next.39",
89
+ "@mittwald/react-tunnel": "^1.6.0-next.40",
90
90
  "@mittwald/react-use-promise": "^4.2.2",
91
91
  "@react-aria/form": "^3.2.1",
92
92
  "@react-aria/i18n": "^3.13.1",
@@ -139,13 +139,13 @@
139
139
  "@internationalized/date": "^3.12.3",
140
140
  "@lezer/generator": "^1.8.0",
141
141
  "@lezer/lr": "^1.4.10",
142
- "@mittwald/flow-components-base": "1.6.0-next.39",
143
- "@mittwald/flow-core": "1.6.0-next.39",
144
- "@mittwald/flow-design-tokens": "1.6.0-next.39",
145
- "@mittwald/flow-icons-base": "1.6.0-next.39",
142
+ "@mittwald/flow-components-base": "1.6.0-next.40",
143
+ "@mittwald/flow-core": "1.6.0-next.40",
144
+ "@mittwald/flow-design-tokens": "1.6.0-next.40",
145
+ "@mittwald/flow-icons-base": "1.6.0-next.40",
146
146
  "@mittwald/react-use-promise": "^4.2.2",
147
147
  "@mittwald/remote-dom-react": "1.2.2-mittwald.10",
148
- "@mittwald/typescript-config": "1.6.0-next.39",
148
+ "@mittwald/typescript-config": "1.6.0-next.40",
149
149
  "@nx/storybook": "^22.7.6",
150
150
  "@storybook/addon-a11y": "^10.5.10",
151
151
  "@storybook/addon-actions": "^9.0.8",
@@ -199,7 +199,7 @@
199
199
  },
200
200
  "peerDependencies": {
201
201
  "@internationalized/date": "^3.12.2",
202
- "@mittwald/flow-icons-pro": "1.6.0-next.39",
202
+ "@mittwald/flow-icons-pro": "1.6.0-next.40",
203
203
  "@mittwald/react-use-promise": "^4.2.2",
204
204
  "next": "^16.2.3",
205
205
  "react": "^19.2.0",
@@ -223,5 +223,5 @@
223
223
  "browserslist": [
224
224
  "baseline widely available"
225
225
  ],
226
- "gitHead": "ff0590a0a5c672cef5cf8e08817b5c17da235d7e"
226
+ "gitHead": "6eadd7989e4a3d12911acbe13e54cb8ef7165aca"
227
227
  }