@mtes-mct/monitor-ui 5.6.0 → 5.7.0

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [5.6.0](https://github.com/MTES-MCT/monitor-ui/compare/v5.5.0...v5.6.0) (2023-04-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * **icons:** add expand icon ([3376d0f](https://github.com/MTES-MCT/monitor-ui/commit/3376d0f2e8244effafc5722ae6f4bea0dc46c65b))
7
+
1
8
  # [5.5.0](https://github.com/MTES-MCT/monitor-ui/compare/v5.4.0...v5.5.0) (2023-04-27)
2
9
 
3
10
 
@@ -1,14 +1,51 @@
1
+ import { PureComponent, ReactNode } from 'react';
1
2
  import type { Promisable } from 'type-fest';
2
3
  export type NewWindowProps = {
3
- center?: 'parent' | 'screen';
4
- children: any;
5
- height?: number;
6
- isStoryBook?: boolean;
7
- name?: string;
8
- onOpen?: (window: Window) => Promisable<void>;
9
- onUnload?: () => Promisable<void>;
10
- title?: string;
11
- url?: string;
12
- width?: number;
4
+ center?: 'parent' | 'screen' | undefined;
5
+ children?: ReactNode | undefined;
6
+ closeOnUnmount?: boolean | undefined;
7
+ copyStyles?: boolean | undefined;
8
+ features?: Partial<{
9
+ height: number;
10
+ left: number;
11
+ top: number;
12
+ width: number;
13
+ }> | undefined;
14
+ name?: string | undefined;
15
+ onBlock?: ((a: null) => Promisable<void>) | undefined;
16
+ onChangeFocus?: ((visibility: 'hidden' | 'visible') => Promisable<void>) | undefined;
17
+ onOpen?: ((window: Window) => Promisable<void>) | undefined;
18
+ onUnload?: ((a: null) => Promisable<void>) | undefined;
19
+ shouldHaveFocus?: boolean | undefined;
20
+ showPrompt?: boolean | undefined;
21
+ title?: string | undefined;
22
+ url?: string | undefined;
13
23
  };
14
- export declare function NewWindow({ center, children, height, isStoryBook, name, onOpen, onUnload, title, url, width }: NewWindowProps): JSX.Element;
24
+ type NewWindowState = {
25
+ mounted: boolean;
26
+ };
27
+ export declare class NewWindow extends PureComponent<NewWindowProps, NewWindowState> {
28
+ private container;
29
+ private released;
30
+ private window;
31
+ private windowCheckerInterval;
32
+ constructor(props: NewWindowProps);
33
+ componentDidMount(): void;
34
+ componentDidUpdate(prevProps: any): void;
35
+ /**
36
+ * Closes the opened window (if any) when NewWindow will unmount if the
37
+ * prop {closeOnUnmount} is true, otherwise the NewWindow will remain open
38
+ */
39
+ componentWillUnmount(): void;
40
+ beforeUnloadListener(event: BeforeUnloadEvent): "blocked" | null;
41
+ /**
42
+ * Create the new window when NewWindow component mount.
43
+ */
44
+ openChild(): void;
45
+ /**
46
+ * Release the new window and anything that was bound to it.
47
+ */
48
+ release(): void;
49
+ render(): any;
50
+ }
51
+ export {};
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ import type { NewWindowContextValue } from './types';
3
+ export declare const NewWindowContext: import("react").Context<NewWindowContextValue>;
@@ -0,0 +1,5 @@
1
+ import { NewWindowContext } from './context';
2
+ import type { NewWindowContextValue } from './types';
3
+ export { NewWindowContext };
4
+ export declare function useNewWindow(): NewWindowContextValue;
5
+ export type { NewWindowContextValue };
@@ -0,0 +1,4 @@
1
+ import type { MutableRefObject } from 'react';
2
+ export type NewWindowContextValue = {
3
+ newWindowContainerRef: MutableRefObject<HTMLDivElement>;
4
+ };
package/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export { OnlyFontGlobalStyle } from './OnlyFontGlobalStyle';
11
11
  export { THEME } from './theme';
12
12
  export { ThemeProvider } from './ThemeProvider';
13
13
  export { Dropdown } from './components/Dropdown';
14
+ export { NewWindow } from './components/NewWindow';
14
15
  export { SingleTag } from './components/SingleTag';
15
16
  export { Button } from './elements/Button';
16
17
  export { Field } from './elements/Field';
@@ -51,6 +52,7 @@ export { useClickOutsideEffect } from './hooks/useClickOutsideEffect';
51
52
  export { useFieldControl } from './hooks/useFieldControl';
52
53
  export { useForceUpdate } from './hooks/useForceUpdate';
53
54
  export { useKey } from './hooks/useKey';
55
+ export { NewWindowContext, useNewWindow } from './hooks/useNewWindow';
54
56
  export { usePrevious } from './hooks/usePrevious';
55
57
  export { customDayjs } from './utils/customDayjs';
56
58
  export { getCoordinates, coordinatesAreDistinct } from './utils/coordinates';
@@ -63,6 +65,7 @@ export { stopMouseEventPropagation } from './utils/stopMouseEventPropagation';
63
65
  export type { PartialTheme, Theme } from './theme';
64
66
  export type { Coordinates, DateAsStringRange, DateRange, Defined, IconProps, Option, OptionValueType, Undefine } from './types';
65
67
  export type { DropdownProps, DropdownItemProps } from './components/Dropdown';
68
+ export type { NewWindowProps } from './components/NewWindow';
66
69
  export type { SingleTagProps } from './components/SingleTag';
67
70
  export type { ButtonProps } from './elements/Button';
68
71
  export type { FieldProps } from './elements/Field';
@@ -98,3 +101,4 @@ export type { FormikNumberInputProps } from './formiks/FormikNumberInput';
98
101
  export type { FormikSelectProps } from './formiks/FormikSelect';
99
102
  export type { FormikTextareaProps } from './formiks/FormikTextarea';
100
103
  export type { FormikTextInputProps } from './formiks/FormikTextInput';
104
+ export type { NewWindowContextValue } from './hooks/useNewWindow';
package/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import styled, { createGlobalStyle, ThemeProvider as ThemeProvider$1, css } from 'styled-components';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import React, { useMemo, useCallback, useRef, useEffect, useReducer, useState, forwardRef, useImperativeHandle } from 'react';
3
+ import React, { useMemo, PureComponent, useCallback, useRef, useEffect, useReducer, useState, forwardRef, useImperativeHandle, createContext, useContext } from 'react';
4
4
  import { Dropdown as Dropdown$1, AutoComplete, Checkbox as Checkbox$1, DatePicker as DatePicker$1, DateRangePicker as DateRangePicker$1, TagPicker, Radio, Input, SelectPicker } from 'rsuite';
5
+ import ReactDOM from 'react-dom';
5
6
  import { useField, useFormikContext } from 'formik';
6
7
 
7
8
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -2245,6 +2246,297 @@ const Dropdown = Object.assign(RawDropdown, {
2245
2246
  Item
2246
2247
  });
2247
2248
 
2249
+ /* eslint-disable no-null/no-null, @typescript-eslint/naming-convention */
2250
+ class NewWindow extends PureComponent {
2251
+ container;
2252
+ released;
2253
+ window;
2254
+ windowCheckerInterval;
2255
+ constructor(props) {
2256
+ super(props);
2257
+ this.container = null;
2258
+ this.window = null;
2259
+ this.windowCheckerInterval = undefined;
2260
+ this.released = false;
2261
+ this.state = {
2262
+ mounted: false
2263
+ };
2264
+ this.beforeUnloadListener = this.beforeUnloadListener.bind(this);
2265
+ }
2266
+ componentDidMount() {
2267
+ const { onChangeFocus } = this.props;
2268
+ this.openChild();
2269
+ this.setState({ mounted: true });
2270
+ this.window?.addEventListener('beforeunload', this.beforeUnloadListener, { capture: true });
2271
+ this.window?.addEventListener('blur', () => {
2272
+ onChangeFocus('hidden');
2273
+ });
2274
+ this.window?.addEventListener('focus', () => {
2275
+ onChangeFocus('visible');
2276
+ });
2277
+ }
2278
+ componentDidUpdate(prevProps) {
2279
+ const { shouldHaveFocus } = this.props;
2280
+ if (prevProps.shouldHaveFocus !== shouldHaveFocus && shouldHaveFocus) {
2281
+ this.window?.focus();
2282
+ }
2283
+ }
2284
+ /**
2285
+ * Closes the opened window (if any) when NewWindow will unmount if the
2286
+ * prop {closeOnUnmount} is true, otherwise the NewWindow will remain open
2287
+ */
2288
+ componentWillUnmount() {
2289
+ const { children, closeOnUnmount } = this.props;
2290
+ if (this.window) {
2291
+ if (closeOnUnmount) {
2292
+ this.window.close();
2293
+ }
2294
+ else if (children) {
2295
+ // Clone any children so they aren't removed when react stops rendering
2296
+ const clone = this.container?.cloneNode(true);
2297
+ clone?.setAttribute('id', 'new-window-container-static');
2298
+ this.window.document.body.appendChild(clone);
2299
+ }
2300
+ }
2301
+ this.window?.removeEventListener('beforeunload', this.beforeUnloadListener, { capture: true });
2302
+ }
2303
+ beforeUnloadListener(event) {
2304
+ const { showPrompt } = this.props;
2305
+ if (showPrompt) {
2306
+ event.preventDefault();
2307
+ // eslint-disable-next-line no-param-reassign, no-return-assign
2308
+ return (event.returnValue = 'blocked');
2309
+ }
2310
+ // eslint-disable-next-line no-return-assign, no-param-reassign
2311
+ return null;
2312
+ }
2313
+ /**
2314
+ * Create the new window when NewWindow component mount.
2315
+ */
2316
+ openChild() {
2317
+ const { center, copyStyles, features, name, onBlock, onOpen, title, url } = this.props;
2318
+ // Prepare position of the new window to be centered against the 'parent' window or 'screen'.
2319
+ if (typeof center === 'string' && (features.width === undefined || features.height === undefined)) {
2320
+ console.warn('width and height window features must be present when a center prop is provided');
2321
+ }
2322
+ else if (center === 'parent') {
2323
+ if (!window.top) {
2324
+ console.error('`window.top` is null.');
2325
+ return;
2326
+ }
2327
+ features.left = window.top.outerWidth / 2 + window.top.screenX - features.width / 2;
2328
+ features.top = window.top.outerHeight / 2 + window.top.screenY - features.height / 2;
2329
+ }
2330
+ else if (center === 'screen') {
2331
+ // eslint-disable-next-line no-nested-ternary
2332
+ const width = window.innerWidth
2333
+ ? window.innerWidth
2334
+ : document.documentElement.clientWidth
2335
+ ? document.documentElement.clientWidth
2336
+ : window.screen.width;
2337
+ // eslint-disable-next-line no-nested-ternary
2338
+ const height = window.innerHeight
2339
+ ? window.innerHeight
2340
+ : document.documentElement.clientHeight
2341
+ ? document.documentElement.clientHeight
2342
+ : window.screen.height;
2343
+ features.left = width / 2 - features.width / 2 + window.screenLeft;
2344
+ features.top = height / 2 - features.height / 2 + window.screenTop;
2345
+ }
2346
+ // Open a new window.
2347
+ this.window = window.open(url, name, toWindowFeatures(features));
2348
+ this.container = this.window?.document.createElement('div');
2349
+ // When a new window use content from a cross-origin there's no way we can attach event
2350
+ // to it. Therefore, we need to detect in a interval when the new window was destroyed
2351
+ // or was closed.
2352
+ this.windowCheckerInterval = setInterval(() => {
2353
+ if (!this.window || this.window.closed) {
2354
+ this.release();
2355
+ }
2356
+ }, 50);
2357
+ // Check if the new window was succesfully opened.
2358
+ if (this.window) {
2359
+ this.window.document.title = title;
2360
+ // Check if the container already exists as the window may have been already open
2361
+ this.container = this.window.document.getElementById('new-window-container');
2362
+ if (this.container === null) {
2363
+ this.container = this.window.document.createElement('div');
2364
+ this.container.setAttribute('id', 'new-window-container');
2365
+ this.window.document.body.appendChild(this.container);
2366
+ }
2367
+ else {
2368
+ // Remove any existing content
2369
+ const staticContainer = this.window.document.getElementById('new-window-container-static');
2370
+ if (staticContainer) {
2371
+ this.window.document.body.removeChild(staticContainer);
2372
+ }
2373
+ }
2374
+ // If specified, copy styles from parent window's document.
2375
+ if (copyStyles) {
2376
+ setTimeout(() => onCopyStyles(document, this.window?.document), 0);
2377
+ }
2378
+ if (typeof onOpen === 'function') {
2379
+ onOpen(this.window);
2380
+ }
2381
+ // Release anything bound to this component before the new window unload.
2382
+ }
2383
+ else if (typeof onBlock === 'function') {
2384
+ // Handle error on opening of new window.
2385
+ onBlock(null);
2386
+ }
2387
+ else {
2388
+ console.warn('A new window could not be opened. Maybe it was blocked.');
2389
+ }
2390
+ }
2391
+ /**
2392
+ * Release the new window and anything that was bound to it.
2393
+ */
2394
+ release() {
2395
+ // This method can be called once.
2396
+ if (this.released) {
2397
+ return;
2398
+ }
2399
+ this.released = true;
2400
+ // Remove checker interval.
2401
+ clearInterval(this.windowCheckerInterval);
2402
+ // Call any function bound to the `onUnload` prop.
2403
+ const { onUnload } = this.props;
2404
+ if (typeof onUnload === 'function') {
2405
+ onUnload(null);
2406
+ }
2407
+ }
2408
+ render() {
2409
+ const { mounted } = this.state;
2410
+ const { children } = this.props;
2411
+ if (!mounted || !this.container) {
2412
+ return null;
2413
+ }
2414
+ return ReactDOM.createPortal(children, this.container);
2415
+ }
2416
+ }
2417
+ NewWindow.defaultProps = {
2418
+ center: 'parent',
2419
+ closeOnUnmount: true,
2420
+ copyStyles: true,
2421
+ features: { height: '640px', width: '600px' },
2422
+ name: '',
2423
+ onBlock: null,
2424
+ onChangeFocus: () => { },
2425
+ onOpen: null,
2426
+ onUnload: null,
2427
+ shouldHaveFocus: false,
2428
+ showPrompt: false,
2429
+ title: '',
2430
+ url: ''
2431
+ };
2432
+ /**
2433
+ * Utility functions.
2434
+ * @private
2435
+ */
2436
+ /**
2437
+ * Copy styles from a source document to a target.
2438
+ */
2439
+ function onCopyStyles(source, target) {
2440
+ if (!target) {
2441
+ console.error('`target` is undefined.');
2442
+ return;
2443
+ }
2444
+ // Store style tags, avoid reflow in the loop
2445
+ const headFrag = target.createDocumentFragment();
2446
+ Array.from(source.styleSheets).forEach(styleSheet => {
2447
+ // For <style> elements
2448
+ let rules;
2449
+ try {
2450
+ rules = styleSheet.cssRules;
2451
+ }
2452
+ catch (err) {
2453
+ console.error(err);
2454
+ }
2455
+ if (rules) {
2456
+ // IE11 is very slow for appendChild, so use plain string here
2457
+ const ruleText = [];
2458
+ // Write the text of each rule into the body of the style element
2459
+ Array.from(styleSheet.cssRules).forEach(cssRule => {
2460
+ const { type } = cssRule;
2461
+ let returnText = '';
2462
+ if (type === CSSRule.KEYFRAMES_RULE) {
2463
+ // IE11 will throw error when trying to access cssText property, so we
2464
+ // need to assemble them
2465
+ returnText = getKeyFrameText(cssRule);
2466
+ }
2467
+ else if ([CSSRule.IMPORT_RULE, CSSRule.FONT_FACE_RULE].includes(type)) {
2468
+ // Check if the cssRule type is CSSImportRule (3) or CSSFontFaceRule (5)
2469
+ // to handle local imports on a about:blank page
2470
+ // '/custom.css' turns to 'http://my-site.com/custom.css'
2471
+ returnText = fixUrlForRule(cssRule);
2472
+ }
2473
+ else {
2474
+ returnText = cssRule.cssText;
2475
+ }
2476
+ ruleText.push(returnText);
2477
+ });
2478
+ const newStyleEl = target.createElement('style');
2479
+ newStyleEl.textContent = ruleText.join('\n');
2480
+ headFrag.appendChild(newStyleEl);
2481
+ }
2482
+ else if (styleSheet.href) {
2483
+ // for <link> elements loading CSS from a URL
2484
+ const newLinkEl = target.createElement('link');
2485
+ newLinkEl.rel = 'stylesheet';
2486
+ newLinkEl.href = styleSheet.href;
2487
+ headFrag.appendChild(newLinkEl);
2488
+ }
2489
+ });
2490
+ target.head.appendChild(headFrag);
2491
+ }
2492
+ /**
2493
+ * Make keyframe rules.
2494
+ */
2495
+ // This should be `CSSRule` instead of `any` but this code is a bit tedious.
2496
+ function getKeyFrameText(cssRule /** CSSRule */) {
2497
+ const tokens = ['@keyframes', cssRule.name, '{'];
2498
+ Array.from(cssRule.cssRules).forEach((keyframesCssRule) => {
2499
+ // type === CSSRule.KEYFRAME_RULE should always be true
2500
+ tokens.push(keyframesCssRule.keyText, '{', keyframesCssRule.style.cssText, '}');
2501
+ });
2502
+ tokens.push('}');
2503
+ return tokens.join(' ');
2504
+ }
2505
+ /**
2506
+ * Handle local import urls.
2507
+ */
2508
+ function fixUrlForRule(cssRule) {
2509
+ return cssRule.cssText
2510
+ .split('url(')
2511
+ .map(line => {
2512
+ if (line[1] === '/') {
2513
+ return `${line.slice(0, 1)}${window.location.origin}${line.slice(1)}`;
2514
+ }
2515
+ return line;
2516
+ })
2517
+ .join('url(');
2518
+ }
2519
+ /**
2520
+ * Convert features props to window features format (name=value, other=value).
2521
+ */
2522
+ function toWindowFeatures(features) {
2523
+ if (!features) {
2524
+ return '';
2525
+ }
2526
+ return Object.keys(features)
2527
+ .reduce((featuresAsStrings, name) => {
2528
+ const value = features[name];
2529
+ if (typeof value === 'boolean') {
2530
+ featuresAsStrings.push(`${name}=${value ? 'yes' : 'no'}`);
2531
+ }
2532
+ else {
2533
+ featuresAsStrings.push(`${name}=${value}`);
2534
+ }
2535
+ return featuresAsStrings;
2536
+ }, [])
2537
+ .join(',');
2538
+ }
2539
+
2248
2540
  function stopMouseEventPropagation(event) {
2249
2541
  event.stopPropagation();
2250
2542
  }
@@ -32071,5 +32363,16 @@ function useFieldControl(value, onChange, defaultValueWhenUndefined) {
32071
32363
  return { controlledOnChange, controlledValue };
32072
32364
  }
32073
32365
 
32074
- export { Accent, Button, Checkbox, CoordinatesFormat, CoordinatesInput, DatePicker, DateRangePicker, Dropdown, Field$2 as Field, Fieldset, FormikCheckbox, FormikCoordinatesInput, FormikDatePicker, FormikDateRangePicker, FormikEffect, FormikMultiCheckbox, FormikMultiRadio, FormikMultiSelect, FormikNumberInput, FormikSearch, FormikSelect, FormikTextInput, FormikTextarea, GlobalStyle, index as Icon, IconButton, Label, Legend, MultiCheckbox, MultiRadio, MultiSelect, MultiZoneEditor, NumberInput, OPENLAYERS_PROJECTION, OnlyFontGlobalStyle, Search, Select, SingleTag, Size, THEME, Tag, TagBullet, TagGroup, TextInput, Textarea, ThemeProvider, WSG84_PROJECTION, coordinatesAreDistinct, customDayjs, getCoordinates, getLocalizedDayjs, getPseudoRandomString, getUtcizedDayjs, isNumeric, noop, stopMouseEventPropagation, useClickOutsideEffect, useFieldControl, useForceUpdate, useKey, usePrevious };
32366
+ const NewWindowContext = createContext({
32367
+ newWindowContainerRef: {
32368
+ current: window.document.createElement('div')
32369
+ }
32370
+ });
32371
+
32372
+ function useNewWindow() {
32373
+ const contextValue = useContext(NewWindowContext);
32374
+ return contextValue;
32375
+ }
32376
+
32377
+ export { Accent, Button, Checkbox, CoordinatesFormat, CoordinatesInput, DatePicker, DateRangePicker, Dropdown, Field$2 as Field, Fieldset, FormikCheckbox, FormikCoordinatesInput, FormikDatePicker, FormikDateRangePicker, FormikEffect, FormikMultiCheckbox, FormikMultiRadio, FormikMultiSelect, FormikNumberInput, FormikSearch, FormikSelect, FormikTextInput, FormikTextarea, GlobalStyle, index as Icon, IconButton, Label, Legend, MultiCheckbox, MultiRadio, MultiSelect, MultiZoneEditor, NewWindow, NewWindowContext, NumberInput, OPENLAYERS_PROJECTION, OnlyFontGlobalStyle, Search, Select, SingleTag, Size, THEME, Tag, TagBullet, TagGroup, TextInput, Textarea, ThemeProvider, WSG84_PROJECTION, coordinatesAreDistinct, customDayjs, getCoordinates, getLocalizedDayjs, getPseudoRandomString, getUtcizedDayjs, isNumeric, noop, stopMouseEventPropagation, useClickOutsideEffect, useFieldControl, useForceUpdate, useKey, useNewWindow, usePrevious };
32075
32378
  //# sourceMappingURL=index.js.map