@astral/ui 4.71.0 → 4.71.2

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.
@@ -4,13 +4,10 @@ export const useHover = (element, { enabled = true } = {}) => {
4
4
  const handleMouseEnter = () => setHover(true);
5
5
  const handleMouseLeave = () => setHover(false);
6
6
  useEffect(() => {
7
- if (!enabled) {
7
+ if (!enabled || !element) {
8
8
  setHover(false);
9
9
  return undefined;
10
10
  }
11
- if (!element) {
12
- return undefined;
13
- }
14
11
  element.addEventListener('mouseenter', handleMouseEnter);
15
12
  element.addEventListener('mouseleave', handleMouseLeave);
16
13
  return () => {
@@ -14,6 +14,9 @@ export const useLogic = ({ containerId: externalContainerId, }) => {
14
14
  const scrollContainer = document.querySelector(`#${externalContainerId} .Toastify__toast-container`);
15
15
  setContainer(scrollContainer);
16
16
  }
17
+ else {
18
+ setContainer(null);
19
+ }
17
20
  }, [externalContainerId, toasts]);
18
21
  // Ориентируемся на data-атрибут для отключении анимации стека при добавлении нового уведомления
19
22
  const handleAddNoTransitionAttr = () => {
@@ -40,7 +43,7 @@ export const useLogic = ({ containerId: externalContainerId, }) => {
40
43
  enabled: isMobile,
41
44
  hasOpenNotify,
42
45
  });
43
- const isStackExpanded = isMobile ? isTouchExpanded : isHovered;
46
+ const isStackExpanded = (isMobile ? isTouchExpanded : isHovered) && hasOpenNotify;
44
47
  const handleAddToast = ({ id, containerId }) => {
45
48
  if (Object.is(containerId, externalContainerId)) {
46
49
  setToasts((currentToasts) => [...currentToasts, id]);
@@ -1,9 +1,14 @@
1
1
  import { MimeTypeRegistry } from '../../services/MimeTypeRegistry';
2
2
  import { FILE_TYPE_ERROR_INFO, INCONSISTENCY_EXTENSION_AND_MIME_TYPE_ERROR_INFO, } from './constants';
3
3
  import { getFileMeta } from './utils';
4
- const getAcceptMimeTypes = (acceptMeta) => acceptMeta.mimeTypes.concat(...acceptMeta.extensions
5
- .map((extension) => MimeTypeRegistry.getInstance().getMimeTypeByExtension(extension))
6
- .filter((value) => typeof value === 'string'));
4
+ const getAcceptMimeTypes = (acceptMeta) => {
5
+ return [
6
+ ...new Set([
7
+ ...acceptMeta.mimeTypes,
8
+ ...acceptMeta.extensions.flatMap((extension) => MimeTypeRegistry.getInstance().getMimeTypesByExtension(extension)),
9
+ ]),
10
+ ];
11
+ };
7
12
  const checkEmptyAccept = (acceptMeta) => !acceptMeta.extensions.length &&
8
13
  !acceptMeta.mimeTypes.length &&
9
14
  !acceptMeta.generalMimeTypes.length;
@@ -20,9 +25,7 @@ export const restrictFileType = (acceptMeta) => (file) => {
20
25
  }
21
26
  return undefined;
22
27
  }
23
- const isInconsistencyExtensionAndMimeType = !MimeTypeRegistry.getInstance()
24
- .getExtensionsByMimeType(fileMeta.mimeType)
25
- ?.includes(fileMeta.extension);
28
+ const isInconsistencyExtensionAndMimeType = !MimeTypeRegistry.getInstance().isExtensionValidForMimeType(fileMeta.mimeType, fileMeta.extension);
26
29
  if (isInconsistencyExtensionAndMimeType) {
27
30
  return INCONSISTENCY_EXTENSION_AND_MIME_TYPE_ERROR_INFO;
28
31
  }
@@ -5,5 +5,13 @@ export declare class MimeTypeRegistry {
5
5
  private constructor();
6
6
  getMimeTypeByExtension(extension: string): string | null;
7
7
  getExtensionsByMimeType(mimeType: string): string[] | null;
8
+ /**
9
+ * Проверяет, что расширение файла допустимо для указанного MIME-типа.
10
+ * Учитывает wildcard-расширения из mime-db (например, `*xml` для `text/xml`).
11
+ */
12
+ isExtensionValidForMimeType(mimeType: string, extension: string): boolean;
13
+ getMimeTypesByExtension(extension: string): string[];
14
+ private normalizeExtension;
15
+ private matchExtension;
8
16
  static getInstance(): MimeTypeRegistry;
9
17
  }
@@ -10,11 +10,45 @@ export class MimeTypeRegistry {
10
10
  }
11
11
  }
12
12
  getMimeTypeByExtension(extension) {
13
- return (this.extensionsMap.get(extension.toLowerCase().replace('.', '')) || null);
13
+ return this.extensionsMap.get(this.normalizeExtension(extension)) || null;
14
14
  }
15
15
  getExtensionsByMimeType(mimeType) {
16
16
  return this.mimeTypesMap[mimeType] || null;
17
17
  }
18
+ /**
19
+ * Проверяет, что расширение файла допустимо для указанного MIME-типа.
20
+ * Учитывает wildcard-расширения из mime-db (например, `*xml` для `text/xml`).
21
+ */
22
+ isExtensionValidForMimeType(mimeType, extension) {
23
+ const extensions = this.getExtensionsByMimeType(mimeType);
24
+ if (!extensions) {
25
+ return false;
26
+ }
27
+ return extensions.some((registeredExtension) => this.matchExtension(registeredExtension, extension));
28
+ }
29
+ getMimeTypesByExtension(extension) {
30
+ const normalizedExtension = this.normalizeExtension(extension);
31
+ const mimeTypes = [];
32
+ for (const [mimeType, extensions] of Object.entries(this.mimeTypesMap)) {
33
+ if (extensions.some((registeredExtension) => this.matchExtension(registeredExtension, normalizedExtension))) {
34
+ mimeTypes.push(mimeType);
35
+ }
36
+ }
37
+ return mimeTypes;
38
+ }
39
+ normalizeExtension(extension) {
40
+ return extension.toLowerCase().replace(/^\./, '');
41
+ }
42
+ matchExtension(registeredExtension, fileExtension) {
43
+ const normalizedFile = this.normalizeExtension(fileExtension);
44
+ const normalizedRegistered = registeredExtension.toLowerCase();
45
+ // Отдельная обработка для wildcard mime types, например xml === *xml
46
+ if (normalizedRegistered.startsWith('*')) {
47
+ const suffix = normalizedRegistered.slice(1);
48
+ return normalizedFile === suffix || normalizedFile.endsWith(suffix);
49
+ }
50
+ return normalizedRegistered === normalizedFile;
51
+ }
18
52
  static getInstance() {
19
53
  const instance = this.instanceRef?.deref();
20
54
  if (instance) {
@@ -7,13 +7,10 @@ const useHover = (element, { enabled = true } = {}) => {
7
7
  const handleMouseEnter = () => setHover(true);
8
8
  const handleMouseLeave = () => setHover(false);
9
9
  (0, react_1.useEffect)(() => {
10
- if (!enabled) {
10
+ if (!enabled || !element) {
11
11
  setHover(false);
12
12
  return undefined;
13
13
  }
14
- if (!element) {
15
- return undefined;
16
- }
17
14
  element.addEventListener('mouseenter', handleMouseEnter);
18
15
  element.addEventListener('mouseleave', handleMouseLeave);
19
16
  return () => {
@@ -17,6 +17,9 @@ const useLogic = ({ containerId: externalContainerId, }) => {
17
17
  const scrollContainer = document.querySelector(`#${externalContainerId} .Toastify__toast-container`);
18
18
  setContainer(scrollContainer);
19
19
  }
20
+ else {
21
+ setContainer(null);
22
+ }
20
23
  }, [externalContainerId, toasts]);
21
24
  // Ориентируемся на data-атрибут для отключении анимации стека при добавлении нового уведомления
22
25
  const handleAddNoTransitionAttr = () => {
@@ -43,7 +46,7 @@ const useLogic = ({ containerId: externalContainerId, }) => {
43
46
  enabled: isMobile,
44
47
  hasOpenNotify,
45
48
  });
46
- const isStackExpanded = isMobile ? isTouchExpanded : isHovered;
49
+ const isStackExpanded = (isMobile ? isTouchExpanded : isHovered) && hasOpenNotify;
47
50
  const handleAddToast = ({ id, containerId }) => {
48
51
  if (Object.is(containerId, externalContainerId)) {
49
52
  setToasts((currentToasts) => [...currentToasts, id]);
@@ -4,9 +4,14 @@ exports.restrictFileType = void 0;
4
4
  const MimeTypeRegistry_1 = require("../../services/MimeTypeRegistry");
5
5
  const constants_1 = require("./constants");
6
6
  const utils_1 = require("./utils");
7
- const getAcceptMimeTypes = (acceptMeta) => acceptMeta.mimeTypes.concat(...acceptMeta.extensions
8
- .map((extension) => MimeTypeRegistry_1.MimeTypeRegistry.getInstance().getMimeTypeByExtension(extension))
9
- .filter((value) => typeof value === 'string'));
7
+ const getAcceptMimeTypes = (acceptMeta) => {
8
+ return [
9
+ ...new Set([
10
+ ...acceptMeta.mimeTypes,
11
+ ...acceptMeta.extensions.flatMap((extension) => MimeTypeRegistry_1.MimeTypeRegistry.getInstance().getMimeTypesByExtension(extension)),
12
+ ]),
13
+ ];
14
+ };
10
15
  const checkEmptyAccept = (acceptMeta) => !acceptMeta.extensions.length &&
11
16
  !acceptMeta.mimeTypes.length &&
12
17
  !acceptMeta.generalMimeTypes.length;
@@ -23,9 +28,7 @@ const restrictFileType = (acceptMeta) => (file) => {
23
28
  }
24
29
  return undefined;
25
30
  }
26
- const isInconsistencyExtensionAndMimeType = !MimeTypeRegistry_1.MimeTypeRegistry.getInstance()
27
- .getExtensionsByMimeType(fileMeta.mimeType)
28
- ?.includes(fileMeta.extension);
31
+ const isInconsistencyExtensionAndMimeType = !MimeTypeRegistry_1.MimeTypeRegistry.getInstance().isExtensionValidForMimeType(fileMeta.mimeType, fileMeta.extension);
29
32
  if (isInconsistencyExtensionAndMimeType) {
30
33
  return constants_1.INCONSISTENCY_EXTENSION_AND_MIME_TYPE_ERROR_INFO;
31
34
  }
@@ -5,5 +5,13 @@ export declare class MimeTypeRegistry {
5
5
  private constructor();
6
6
  getMimeTypeByExtension(extension: string): string | null;
7
7
  getExtensionsByMimeType(mimeType: string): string[] | null;
8
+ /**
9
+ * Проверяет, что расширение файла допустимо для указанного MIME-типа.
10
+ * Учитывает wildcard-расширения из mime-db (например, `*xml` для `text/xml`).
11
+ */
12
+ isExtensionValidForMimeType(mimeType: string, extension: string): boolean;
13
+ getMimeTypesByExtension(extension: string): string[];
14
+ private normalizeExtension;
15
+ private matchExtension;
8
16
  static getInstance(): MimeTypeRegistry;
9
17
  }
@@ -13,11 +13,45 @@ class MimeTypeRegistry {
13
13
  }
14
14
  }
15
15
  getMimeTypeByExtension(extension) {
16
- return (this.extensionsMap.get(extension.toLowerCase().replace('.', '')) || null);
16
+ return this.extensionsMap.get(this.normalizeExtension(extension)) || null;
17
17
  }
18
18
  getExtensionsByMimeType(mimeType) {
19
19
  return this.mimeTypesMap[mimeType] || null;
20
20
  }
21
+ /**
22
+ * Проверяет, что расширение файла допустимо для указанного MIME-типа.
23
+ * Учитывает wildcard-расширения из mime-db (например, `*xml` для `text/xml`).
24
+ */
25
+ isExtensionValidForMimeType(mimeType, extension) {
26
+ const extensions = this.getExtensionsByMimeType(mimeType);
27
+ if (!extensions) {
28
+ return false;
29
+ }
30
+ return extensions.some((registeredExtension) => this.matchExtension(registeredExtension, extension));
31
+ }
32
+ getMimeTypesByExtension(extension) {
33
+ const normalizedExtension = this.normalizeExtension(extension);
34
+ const mimeTypes = [];
35
+ for (const [mimeType, extensions] of Object.entries(this.mimeTypesMap)) {
36
+ if (extensions.some((registeredExtension) => this.matchExtension(registeredExtension, normalizedExtension))) {
37
+ mimeTypes.push(mimeType);
38
+ }
39
+ }
40
+ return mimeTypes;
41
+ }
42
+ normalizeExtension(extension) {
43
+ return extension.toLowerCase().replace(/^\./, '');
44
+ }
45
+ matchExtension(registeredExtension, fileExtension) {
46
+ const normalizedFile = this.normalizeExtension(fileExtension);
47
+ const normalizedRegistered = registeredExtension.toLowerCase();
48
+ // Отдельная обработка для wildcard mime types, например xml === *xml
49
+ if (normalizedRegistered.startsWith('*')) {
50
+ const suffix = normalizedRegistered.slice(1);
51
+ return normalizedFile === suffix || normalizedFile.endsWith(suffix);
52
+ }
53
+ return normalizedRegistered === normalizedFile;
54
+ }
21
55
  static getInstance() {
22
56
  const instance = this.instanceRef?.deref();
23
57
  if (instance) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astral/ui",
3
- "version": "4.71.0",
3
+ "version": "4.71.2",
4
4
  "browser": "./index.js",
5
5
  "main": "./node/index.js",
6
6
  "dependencies": {