Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit a55a801

Browse files
committed
feat(core): support beforeprint and afterprint hooks
Extends the PrintHook service to also register beforeprint and afterprint event handlers to synchronously update styles and prevent layout switching races. Related #603
1 parent 7dcc14d commit a55a801

File tree

1 file changed

+59
-5
lines changed

1 file changed

+59
-5
lines changed

src/lib/core/media-marshaller/print-hook.ts

+59-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {BreakPoint} from '../breakpoints/break-point';
1313
import {LAYOUT_CONFIG, LayoutConfigOptions} from '../tokens/library-config';
1414
import {BreakPointRegistry, OptionalBreakPoint} from '../breakpoints/break-point-registry';
1515
import {sortDescendingPriority} from '../utils/sort';
16+
import {DOCUMENT} from '@angular/common';
1617

1718
/**
1819
* Interface to apply PrintHook to call anonymous `target.updateStyles()`
@@ -39,7 +40,8 @@ export const BREAKPOINT_PRINT = {
3940
export class PrintHook {
4041
constructor(
4142
protected breakpoints: BreakPointRegistry,
42-
@Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions) {
43+
@Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions,
44+
@Inject(DOCUMENT) protected _document: any) {
4345
}
4446

4547
/** Add 'print' mediaQuery: to listen for matchMedia activations */
@@ -83,18 +85,66 @@ export class PrintHook {
8385
return mergeAlias(event, bp);
8486
}
8587

88+
89+
// registeredBeforeAfterPrintHooks tracks if we registered the `beforeprint`
90+
// and `afterprint` event listeners.
91+
private registeredBeforeAfterPrintHooks: boolean = false;
92+
93+
// isPrintingBeforeAfterEvent is used to track if we are printing from within
94+
// a `beforeprint` event handler. This prevents the typicall `stopPrinting`
95+
// form `interceptEvents` so that printing is not stopped while the dialog
96+
// is still open. This is an extension of the `isPrinting` property on
97+
// browsers which support `beforeprint` and `afterprint` events.
98+
private isPrintingBeforeAfterEvent: boolean = false;
99+
100+
// registerBeforeAfterPrintHooks registers a `beforeprint` event hook so we can
101+
// trigger print styles synchronously and apply proper layout styles.
102+
// It is a noop if the hooks have already been registered or if the document's
103+
// `defaultView` is not available.
104+
private registerBeforeAfterPrintHooks(target: HookTarget) {
105+
// `defaultView` may be null when rendering on the server or in other contexts.
106+
if (!this._document.defaultView || this.registeredBeforeAfterPrintHooks) {
107+
return;
108+
}
109+
110+
this.registeredBeforeAfterPrintHooks = true;
111+
112+
// Could we have teardown logic to remove if there are no print listeners being used?
113+
this._document.defaultView.addEventListener('beforeprint', () => {
114+
// If we aren't already printing, start printing and update the styles as
115+
// if there was a regular print `MediaChange`(from matchMedia).
116+
if (!this.isPrinting) {
117+
this.isPrintingBeforeAfterEvent = true;
118+
this.startPrinting(target, this.getEventBreakpoints(new MediaChange(true, PRINT)));
119+
target.updateStyles();
120+
}
121+
});
122+
123+
this._document.defaultView.addEventListener('afterprint', () => {
124+
// If we aren't already printing, start printing and update the styles as
125+
// if there was a regular print `MediaChange`(from matchMedia).
126+
this.isPrintingBeforeAfterEvent = false;
127+
if (this.isPrinting) {
128+
this.stopPrinting(target);
129+
target.updateStyles();
130+
}
131+
});
132+
}
133+
86134
/**
87135
* Prepare RxJs filter operator with partial application
88136
* @return pipeable filter predicate
89137
*/
90138
interceptEvents(target: HookTarget) {
139+
this.registerBeforeAfterPrintHooks(target);
140+
91141
return (event: MediaChange) => {
92142
if (this.isPrintEvent(event)) {
93143
if (event.matches && !this.isPrinting) {
94144
this.startPrinting(target, this.getEventBreakpoints(event));
95145
target.updateStyles();
96146

97-
} else if (!event.matches && this.isPrinting) {
147+
} else if (!event.matches && this.isPrinting && !this.isPrintingBeforeAfterEvent) {
98148
this.stopPrinting(target);
99149
target.updateStyles();
100150
}
@@ -131,7 +181,8 @@ export class PrintHook {
131181
/**
132182
* To restore pre-Print Activations, we must capture the proper
133183
* list of breakpoint activations BEFORE print starts. OnBeforePrint()
134-
* is not supported; so 'print' mediaQuery activations must be used.
184+
* is supported; so 'print' mediaQuery activations are used as a fallback
185+
* in browsers without `beforeprint` support.
135186
*
136187
* > But activated breakpoints are deactivated BEFORE 'print' activation.
137188
*
@@ -146,14 +197,17 @@ export class PrintHook {
146197
* - restore as activatedTargets and clear when stop printing
147198
*/
148199
collectActivations(event: MediaChange) {
149-
if (!this.isPrinting) {
200+
if (!this.isPrinting || this.isPrintingBeforeAfterEvent) {
150201
if (!event.matches) {
151202
const bp = this.breakpoints.findByQuery(event.mediaQuery);
152203
if (bp) { // Deactivating a breakpoint
153204
this.deactivations.push(bp);
154205
this.deactivations.sort(sortDescendingPriority);
155206
}
156-
} else {
207+
} else if (!this.isPrintingBeforeAfterEvent) {
208+
// Only clear deactivations if we aren't printing from a `beforeprint` event.
209+
// Otherwise this will clear before `stopPrinting()` is called to restore
210+
// the pre-Print Activations.
157211
this.deactivations = [];
158212
}
159213
}

0 commit comments

Comments
 (0)