diff --git a/CHANGELOG.md b/CHANGELOG.md
index 392f4abbe..303eee5d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
+## [26.2.0] - 2026-10-05
+
### Added
- ``
@@ -13,16 +15,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- shows a compact keyboard navigation hint in a bottom CodeMirror panel when Tab is configured to indent
- `keyboardHint` accepts a custom string or element for localized instructions, the default instruction is marked as English
- ``
- - `noDrag` parameter: Add the `nodrag` class to the Switch element. Default: `true`
+ - `noDrag` property: adds the `nodrag` class to the Switch element. Default: `true`
- ``
- - Added `cutOff` property to set maximum number of raw Markdown characters to render
+ - `cutOff` property: sets the maximum number of raw Markdown characters to render
- ``
- `hidden` property: label is not displayed but stays accessible for screen readers and keyboard navigation
- ``
- label, input element, helper text and message are connected to each other automatically now
- each part without an own `id` gets one based on a unique ID of the field item
- the label refers to the input element via `for`, or via `aria-labelledby` on the input element if the label is not displayed as `label` element
- - an already set `for` is only kept if it refers to the ID of the input element of the field item
+ - an already set `for` is only kept if it refers to the ID of an input element of the field item, e.g. to a second input element
- helper text and message are referred by the input element via `aria-describedby`
- `input`, `textarea`, `select`, the toggle button of `` and the editable area of `` are supported as input element
- input elements that cannot be referenced by `for`, e.g. the editable area of the code editor, are connected via `aria-labelledby`
@@ -32,7 +34,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `preventAriaAttribution` property: prevents this automatic connection of the field item parts
- ``
- `role`, `aria-label`, `aria-labelledby` and `aria-describedby` properties: they are set on the dialog element inside the modal overlay
- - `role` is `dialog` by default, but it is removed again if neither a label nor a description is available; a console warning points this out when the modal is opened
+ - `role` is `dialog` by default, but it is removed again if neither `aria-label` nor `aria-labelledby` is available; a console warning points this out when the modal is opened
- `aria-modal` is set together with the `role`, so it is left out as well if the `role` was removed
- it is `true` for the modal that was opened last according to the `ModalContext`, otherwise it is `false`
- if no `ModalContext` is provided, then the modals cannot know about each other, so each of them claims modality
@@ -43,7 +45,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- explicitly given values are never overwritten
- if neither `title`, `aria-label` nor `aria-labelledby` is given for an alert, then the `intent` level is used as fallback for `aria-label`, so the alert dialog always has an accessible name
- new `utils` methods:
- - `truncateMarkdownDisplay`: helper function to iterate over `Markdown` renderings to improve the experienced `cutOff` value
+ - `truncateMarkdownDisplay`: helper function to iterate over `Markdown` renderings to find a `cutOff` value that matches the visible text length
+- `ClassNames`
+ - `ReactFlow`: helper classes for react-flow, e.g. `preventAllActions` to prevent drag, pan and wheel actions
- new icons:
- `toggler-treenode-closed`
- `toggler-treenode-expanded`
@@ -54,6 +58,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Carbon, Codemirror, React-Flow
- minimum node version (`engines.node`) is `18.19.0` now
- the build of the ESM distribution needs a synchronous `import.meta.resolve`, which is only available since this version
+- ``
+ - the `nodrag` class is added by default now, use `noDrag={false}` to prevent this
- ``
- the used `Label` element gets the `eccgui-fielditem__label` class now
- ``
@@ -64,6 +70,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- before only an internal reference was updated, which never triggered any re-render
- the component that provides the context via `useModalContext` is re-rendered on every change of the stack, but not if a change does not affect it, e.g. when a modal is closed that was never registered as open
- `openModalStack()` still returns the current stack synchronously, also directly after `setModalOpen()` was called
+ - closing a modal only removes this modal from the stack, modals that were opened after it stay in the stack as long as they are open
+ - before they were considered as closed, too, so the still visible top most modal was not known as the top most one anymore
- ``
- `enableTab` overrides the Tab behaviour derived from `mode` and `tabIntentStyle` now, if it is set
- `enableTab={false}` lets Tab always move the focus, even for modes with tab indentation
@@ -71,15 +79,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- ``
- Tab is only handled in the editor if `useTabForCompletions` is enabled, as side effect it currently also enables tab indentation
- ``
- - `allowedHtmlElementsInPreview` option is set to inline elements on default
- - uses now the `Markdown.cutOff` property
- - this enables Markdown rendering even if the preview need to be shortened
+ - `allowedHtmlElementsInPreview` option is set to inline elements by default
+ - now uses the `Markdown.cutOff` property
+ - this enables Markdown rendering even if the preview needs to be shortened
- this may lead to slightly different preview lengths
+### Deprecated
+
+- `preventReactFlowActionsClasses`: use `ClassNames.ReactFlow.preventAllActions`, it will be removed in v27
+
### Fixed
- ``
- - fix description and story to point out that `PropertyValueList` need always to be used as wrapper
+ - fix description and story to point out that `PropertyValueList` always needs to be used as wrapper
- ``
- `show={"print"}` and `hide={"screen"}` content is still accessible by screen readers
- ``
@@ -88,18 +100,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- fix color of first action button in info card
- BOM issue on compressed stylesheet
- first rule `selector` becomes `BOM:selector` that is valid but will never apply
- - we fixed this problem by adding a dummy rule as first rule
+ - fixed by adding a dummy rule as first rule
- ESM distribution
- the imports of the `@codemirror/legacy-modes` modes were written with an additional `.js` suffix, but the `exports` map of this package only provides the extension-less sub paths, so they were expanded to unresolvable paths like `mode/jinja2.js.js`
- Storybook
- the `` story imported the package root directory, this way the `exports` field of our own `package.json` pulled the built `dist/esm/` output into the preview bundle instead of the sources, and the Storybook build failed as soon as `dist/` existed
- the webpack configuration excludes `dist/` from module resolution now, so the sources are always used even if a story references the package root
-- Added explicitly `assert` dependency
- - the linter of `` uses `jshint`, which imports `console-browserify`, and this package requires the node core modules `assert` and `util` without declaring them; bundlers based on webpack 5 do not provide shims for node core modules anymore, so the polyfill (and `util` together with it) is part of the delivery now
-
-### Deprecated
-
-- `preventReactFlowActionsClasses`: use `ClassNames.ReactFlow.preventAllActions`
+- Explicitly added `assert` and `util` dependencies
+ - the linter of `` uses `jshint`, which imports `console-browserify`, and this package requires the node core modules `assert` and `util` without declaring them; bundlers based on webpack 5 do not provide shims for node core modules anymore, so both polyfills are part of the delivery now
## [26.1.0] - 2026-08-20
diff --git a/package.json b/package.json
index 1320a32ca..eb952efb2 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@eccenca/gui-elements",
"description": "GUI elements based on other libraries, usable in React application, written in Typescript.",
- "version": "26.1.0",
+ "version": "26.2.0",
"license": "Apache-2.0",
"homepage": "https://github.com/eccenca/gui-elements",
"bugs": "https://github.com/eccenca/gui-elements/issues",
@@ -182,7 +182,16 @@
"**/lodash": "^4.18.1",
"**/minimatch": "^3.1.4",
"**/postcss": "^8.5.18",
- "**/serialize-javascript": "^7.0.5"
+ "**/serialize-javascript": "^7.0.5",
+ "**/brace-expansion": "^1.1.20",
+ "jest/**/js-yaml": "^3.15.2",
+ "babel-jest/**/js-yaml": "^3.15.2",
+ "stylelint/**/js-yaml": "^4.3.2",
+ "@storybook/react-webpack5/**/js-yaml": "^4.3.2",
+ "@eslint/eslintrc/**/js-yaml": "^4.3.2",
+ "**/fast-uri": "^3.1.6",
+ "**/browserslist": "^4.28.7",
+ "@storybook/react-webpack5/**/webpack-dev-middleware": "^7.4.6"
},
"husky": {
"hooks": {
diff --git a/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx b/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx
index 6a2321fbe..67d3e207a 100644
--- a/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx
+++ b/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx
@@ -63,35 +63,54 @@ export function StringPreviewContentBlobToggler({
previewString = useOnlyPart(content, regexFirstMarkdownSection);
}
- let enableToggler = previewString !== content;
+ // Measuring and truncating a Markdown preview renders the Markdown multiple times to static markup, so it is only
+ // re-calculated if one of its inputs changes, and not on every re-render, e.g. of a long list containing togglers.
+ const { truncatedPreviewContent, isTruncated } = React.useMemo((): {
+ truncatedPreviewContent: React.JSX.Element | string;
+ isTruncated: boolean;
+ } => {
+ if (!renderPreviewAsMarkdown) {
+ if (previewMaxLength) {
+ const previewText = utils.reduceToText(previewString, { decodeHtmlEntities: true });
+ if (previewText.length > previewMaxLength) {
+ return { truncatedPreviewContent: previewText.slice(0, previewMaxLength), isTruncated: true };
+ }
+ }
+ return { truncatedPreviewContent: previewString, isTruncated: false };
+ }
- let previewContent = renderPreviewAsMarkdown ? (
-
- {previewString}
-
- ) : (
- previewString
- );
+ if (!previewMaxLength) {
+ return {
+ truncatedPreviewContent: (
+
+ {previewString}
+
+ ),
+ isTruncated: false,
+ };
+ }
- if (
- previewMaxLength &&
- utils.reduceToText(previewContent, { decodeHtmlEntities: true }).length > previewMaxLength
- ) {
- previewContent = renderPreviewAsMarkdown
- ? utils.truncateMarkdownDisplay(
-
- {previewString}
- ,
- { decodeHtmlEntities: true },
- )
- : utils.reduceToText(previewContent, { decodeHtmlEntities: true }).slice(0, previewMaxLength);
- enableToggler = true;
- }
+ // `truncateMarkdownDisplay` already measures the complete Markdown display first and returns it without
+ // `cutOff` if it is short enough, so it is not measured here a second time.
+ const markdownPreview = utils.truncateMarkdownDisplay(
+
+ {previewString}
+ ,
+ { decodeHtmlEntities: true },
+ );
+ return {
+ truncatedPreviewContent: markdownPreview,
+ isTruncated: markdownPreview.props.cutOff !== undefined,
+ };
+ }, [previewString, previewMaxLength, renderPreviewAsMarkdown, allowedHtmlElementsInPreview]);
+
+ const enableToggler = previewString !== content || isTruncated;
+ let previewContent = truncatedPreviewContent;
if (!enableToggler && noTogglerContentSuffix) {
previewContent = (
diff --git a/src/cmem/ContentBlobToggler/tests/StringPreviewContentBlobToggler.test.tsx b/src/cmem/ContentBlobToggler/tests/StringPreviewContentBlobToggler.test.tsx
index 3f7368f50..b17cf4e12 100644
--- a/src/cmem/ContentBlobToggler/tests/StringPreviewContentBlobToggler.test.tsx
+++ b/src/cmem/ContentBlobToggler/tests/StringPreviewContentBlobToggler.test.tsx
@@ -3,6 +3,7 @@ import { render, RenderResult } from "@testing-library/react";
import "@testing-library/jest-dom";
+import { utils } from "../../../common";
import {
StringPreviewContentBlobToggler,
StringPreviewContentBlobTogglerProps,
@@ -72,6 +73,22 @@ describe("StringPreviewContentBlobToggler", () => {
textMustNotExist(queryByText, "https://github.com/"); // test if Markdown was rendered
textMustNotExist(queryByText, "show more");
});
+ it("should not truncate the Markdown preview again when re-rendered with unchanged content", () => {
+ const truncateSpy = jest.spyOn(utils, "truncateMarkdownDisplay");
+ const props = StringPreviewContentBlobTogglerStory.args as StringPreviewContentBlobTogglerProps;
+ try {
+ const { rerender, queryByText } = render();
+ rerender();
+ expect(truncateSpy).toHaveBeenCalledTimes(1);
+ textMustExist(queryByText, "show more");
+
+ rerender();
+ expect(truncateSpy).toHaveBeenCalledTimes(2);
+ textMustNotExist(queryByText, "show more");
+ } finally {
+ truncateSpy.mockRestore();
+ }
+ });
it("should not use Markdown rendering on `renderPreviewAsMarkdown={false}`", () => {
const { queryByText } = render(
{
expect(result).toBe("```\nsome code\n```\n\n...");
});
+ it("keeps the opening fence line complete when cutOff falls inside its info string", () => {
+ const content = "```javascript\nconst a = 1;\nconst b = 2;\n```";
+ expect(truncateMarkdown(content, 5, "...")).toBe("```javascript\nconst\n```\n\n...");
+ });
+
+ it("keeps the opening fence marker complete when cutOff falls inside it", () => {
+ const content = "~~~~\nsome code line here\n~~~~";
+ expect(truncateMarkdown(content, 2, "...")).toBe("~~~~\nsome\n~~~~\n\n...");
+ });
+
+ it("keeps an empty fence complete when cutOff falls inside its opening line", () => {
+ const content = "```javascript\n\n```\n\nSome text after the empty code block.";
+ expect(truncateMarkdown(content, 5, "...")).toBe("```javascript\n```\n\n...");
+ });
+
it("cuts a table only at complete rows", () => {
const content = ["| Name | Value |", "| --- | --- |", "| first | row |", "| second | row |"].join("\n");
const result = truncateMarkdown(content, content.indexOf("second") + 3, "...");
@@ -233,6 +248,25 @@ describe("truncateMarkdown", () => {
expect(truncateMarkdown(content, 32, "...")).toBe("Some intro words and\n\n...");
});
+ it("keeps a leading bold span complete when it is followed by punctuation instead of a space", () => {
+ const content = "**Important note**: this is a long text that goes on and on.";
+ expect(truncateMarkdown(content, 5, "...")).toBe("**Important note**\n\n...");
+ });
+
+ it("does not fall back to a word boundary inside a span that ends right before the cutOff", () => {
+ const content = "Some intro text and then **an important warning**, and more text afterwards here.";
+ expect(truncateMarkdown(content, content.indexOf(","), "...")).toBe(
+ "Some intro text and then **an important warning**\n\n...",
+ );
+ });
+
+ it("does not fall back to a word boundary inside a link that ends right before the cutOff", () => {
+ const content = "Some text (see [the docs](https://example.com)), and more text afterwards here.";
+ expect(truncateMarkdown(content, content.indexOf(","), "...")).toBe(
+ "Some text (see [the docs](https://example.com)\n\n...",
+ );
+ });
+
it("does not treat intra-word underscores as emphasis", () => {
const content = "Some intro words and snake_case_name_here plus more words to cut here somewhere.";
expect(truncateMarkdown(content, 30, "...")).toBe("Some intro words and\n\n...");
diff --git a/src/cmem/markdown/truncateMarkdown.ts b/src/cmem/markdown/truncateMarkdown.ts
index 4f8c716a4..821319c48 100644
--- a/src/cmem/markdown/truncateMarkdown.ts
+++ b/src/cmem/markdown/truncateMarkdown.ts
@@ -55,6 +55,17 @@ const isInsideFence = (fenceRanges: FenceRange[], pos: number): boolean =>
fenceRanges.some(({ start, end }) => pos > start && pos < end);
const truncateActiveFence = (content: string, cutOff: number, activeFence: FenceRange): string => {
+ if (cutOff < activeFence.contentStart) {
+ // Cutting inside the opening fence line would garble its marker or info string. The opening line is kept
+ // completely, together with the first word of the fenced content, so the code block is not displayed empty.
+ const fencedContent = content.slice(activeFence.contentStart, activeFence.contentEnd);
+ const firstWord = /\S+/.exec(fencedContent);
+ const firstWordEnd = firstWord
+ ? activeFence.contentStart + firstWord.index + firstWord[0].length
+ : activeFence.contentEnd;
+ return `${content.slice(0, firstWordEnd).trimEnd()}\n${activeFence.marker}`;
+ }
+
const desiredEnd = Math.min(cutOff, activeFence.contentEnd);
const lastSpace = content.lastIndexOf(" ", desiredEnd);
const lastLineBreak = content.lastIndexOf("\n", desiredEnd);
@@ -232,6 +243,25 @@ const getLastListItemRangeEndBeforeCutOff = (listItemRanges: ListItemRange[], cu
return cutPoint;
};
+/**
+ * The word boundary search only looks for the last space, it does not know about inline markup. If a link or span ends
+ * right before the cutoff and is not followed by a space, e.g. `**note**:`, then the last space lies inside that
+ * markup and cutting there would strand its opening marker. The complete link or span is kept instead, or it is cut
+ * off completely if it does not end before the cutoff.
+ */
+const moveWordBoundaryOutsideMarkup = (
+ boundary: number,
+ safeCutOff: number,
+ markupRanges: (LinkRange | InlineSpanRange)[],
+): number => {
+ const enclosingRange = markupRanges.find(({ start, end }) => boundary > start && boundary < end);
+ if (!enclosingRange) {
+ return boundary;
+ }
+
+ return enclosingRange.end <= safeCutOff ? enclosingRange.end : enclosingRange.start;
+};
+
/**
* Truncates a Markdown string at a safe raw boundary.
* It keeps links atomic, prefers boundaries outside structured blocks, and closes a partial fenced code block only
@@ -298,7 +328,10 @@ export const truncateMarkdown = (content: string, cutOff: number, suffix?: strin
if (cutPoint === -1) {
const lastSpace = content.lastIndexOf(" ", safeCutOff);
- cutPoint = lastSpace > 0 ? lastSpace : safeCutOff;
+ cutPoint =
+ lastSpace > 0
+ ? moveWordBoundaryOutsideMarkup(lastSpace, safeCutOff, [...linkRanges, ...inlineSpanRanges])
+ : safeCutOff;
}
if (cutPoint <= 0) {
diff --git a/src/components/Dialog/Modal.tsx b/src/components/Dialog/Modal.tsx
index 67ba2b1ae..5c6160f74 100644
--- a/src/components/Dialog/Modal.tsx
+++ b/src/components/Dialog/Modal.tsx
@@ -117,7 +117,9 @@ export const Modal = ({
React.useEffect(() => {
if (!modalRole && otherProps.isOpen) {
// eslint-disable-next-line no-console
- console.warn(`role=${role} removed from modal because aria-label nor aria-labelledby is available.`);
+ console.warn(
+ `role=${role} removed from modal because neither aria-label nor aria-labelledby is available.`,
+ );
}
modalContext.setModalOpen(uniqueModalId.current, otherProps.isOpen);
}, [otherProps.isOpen]);
diff --git a/src/components/Dialog/ModalContext.tsx b/src/components/Dialog/ModalContext.tsx
index e254f6ca0..9c725f062 100644
--- a/src/components/Dialog/ModalContext.tsx
+++ b/src/components/Dialog/ModalContext.tsx
@@ -29,18 +29,19 @@ export const isModalContextProvided = (modalContext: ModalContextProps): boolean
const updatedOpenModalStack = (stack: string[], modalId: string, isOpen: boolean): string[] => {
if (isOpen) {
// an already registered modal must not be added twice, otherwise closing it would
- // consider modals as closed that are still open
+ // leave a stale entry in the stack
return stack.includes(modalId) ? stack : [...stack, modalId];
}
- const idx = stack.findIndex((id) => modalId === id);
- if (idx === -1) {
+ if (!stack.includes(modalId)) {
// Trying to close modal that has not been registered as open!
return stack;
}
- // If a modal in between is closed, then all modals after it are considered as closed, too.
- return stack.slice(0, idx);
+ // Only the closed modal is removed. Modals that were opened after it are still open, e.g. a dialog that
+ // opened a follow-up dialog and closed itself afterwards. They report their own closing, also when they
+ // are unmounted. Removing them here would let the visible top most modal lose `aria-modal`.
+ return stack.filter((id) => id !== modalId);
};
/** Default implementation for modal context props.
diff --git a/src/components/Dialog/tests/Modal.test.tsx b/src/components/Dialog/tests/Modal.test.tsx
index 29cd3a70f..557baa1a3 100644
--- a/src/components/Dialog/tests/Modal.test.tsx
+++ b/src/components/Dialog/tests/Modal.test.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { fireEvent, render, screen } from "@testing-library/react";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
@@ -300,6 +300,61 @@ describe("Modal", () => {
expect(modalOnTop).toHaveAttribute("aria-modal", "true");
expect(modalBelow).toHaveAttribute("aria-modal", "false");
});
+ it("should keep modality for the modal on top if a modal below it is closed", () => {
+ const modalStack = (belowOpen: boolean) => (
+
+
+ below content
+
+
+ content on top
+
+
+ );
+ const { container, rerender } = render(modalStack(true));
+ const modalOnTop = container.querySelector(`.${dialogWrapper}[aria-label='ontop']`) as HTMLElement;
+ expect(modalOnTop).toHaveAttribute("aria-modal", "true");
+
+ rerender(modalStack(false));
+ expect(modalOnTop).toHaveAttribute("aria-modal", "true");
+ });
+ it("should remove a nested modal from the stack when its parent modal is closed", async () => {
+ let trackedContext: ModalContextProps | undefined;
+ const ContextProbe = () => {
+ trackedContext = React.useContext(ModalContext);
+ return null;
+ };
+ // the nested modal is never closed by itself, it is only removed together with the content of its parent
+ const modalStack = (parentOpen: boolean) => (
+
+
+
+ base content
+
+
+ parent content
+
+ child content
+
+
+
+ );
+ const { container, rerender } = render(modalStack(true));
+ const modalBase = container.querySelector(`.${dialogWrapper}[aria-label='base']`) as HTMLElement;
+ expect(trackedContext?.openModalStack()).toEqual(["base", "parent", "child"]);
+ expect(modalBase).toHaveAttribute("aria-modal", "false");
+
+ rerender(modalStack(false));
+ await waitFor(() => expect(screen.queryByText("child content")).toBeNull());
+ expect(trackedContext?.openModalStack()).toEqual(["base"]);
+ expect(modalBase).toHaveAttribute("aria-modal", "true");
+ });
it("should not claim modality if the role was removed", () => {
const { container } = render(
diff --git a/src/components/Dialog/tests/ModalContext.test.tsx b/src/components/Dialog/tests/ModalContext.test.tsx
index e0a288b2d..7d8d601e7 100644
--- a/src/components/Dialog/tests/ModalContext.test.tsx
+++ b/src/components/Dialog/tests/ModalContext.test.tsx
@@ -25,12 +25,16 @@ describe("useModalContext", () => {
});
expect(result.current.openModalStack()).toEqual(["first", "second", "third"]);
});
- it("should consider modals as closed that were opened after a closed modal", () => {
+ it("should keep modals that were opened after a closed modal in the stack", () => {
const { result } = renderHook(() => useModalContext());
act(() => {
["first", "second", "third"].forEach((modalId) => result.current.setModalOpen(modalId, true));
result.current.setModalOpen("second", false);
});
+ expect(result.current.openModalStack()).toEqual(["first", "third"]);
+ act(() => {
+ result.current.setModalOpen("third", false);
+ });
expect(result.current.openModalStack()).toEqual(["first"]);
});
it("should not register the same modal twice", () => {
diff --git a/src/components/Form/FieldItem.tsx b/src/components/Form/FieldItem.tsx
index 338b36158..410d52753 100644
--- a/src/components/Form/FieldItem.tsx
+++ b/src/components/Form/FieldItem.tsx
@@ -90,15 +90,17 @@ export const FieldItem = ({
}
/** nested field items manage the connections of their own parts */
- const ownPart = (candidates: NodeListOf): T | undefined =>
- Array.from(candidates).find((candidate) => candidate.closest(`.${eccgui}-fielditem`) === fieldItem);
+ const ownParts = (candidates: NodeListOf): T[] =>
+ Array.from(candidates).filter((candidate) => candidate.closest(`.${eccgui}-fielditem`) === fieldItem);
+ const ownPart = (candidates: NodeListOf): T | undefined => ownParts(candidates)[0];
const labelElement = ownPart(fieldItem.querySelectorAll(`.${eccgui}-fielditem__label`));
- const inputElement = ownPart(
+ const inputElements = ownParts(
fieldItem.querySelectorAll(
connectableInputSelectors.map((selector) => `.${eccgui}-fielditem__inputfields ${selector}`).join(", "),
),
);
+ const inputElement = inputElements[0];
const helpElement = ownPart(fieldItem.querySelectorAll(`.${eccgui}-fielditem__helpertext`));
const messageElement = ownPart(fieldItem.querySelectorAll(`.${eccgui}-fielditem__message`));
@@ -139,8 +141,12 @@ export const FieldItem = ({
};
if (labelElement instanceof HTMLLabelElement && labelableElements.includes(inputElement.tagName)) {
- // an already set `for` is only kept if it refers to the ID of the input element of this field item
- if (labelElement.getAttribute("for") !== inputElement.id) {
+ // an already set `for` is only kept if it refers to a labelable input element of this field item
+ const labelTarget = labelElement.getAttribute("for");
+ const refersToOwnInput = inputElements.some(
+ (element) => element.id === labelTarget && labelableElements.includes(element.tagName),
+ );
+ if (!refersToOwnInput) {
labelElement.setAttribute("for", inputElement.id);
}
} else if (labelElement) {
diff --git a/src/components/Form/tests/FieldItem.test.tsx b/src/components/Form/tests/FieldItem.test.tsx
index 08239e299..1ea4f683e 100644
--- a/src/components/Form/tests/FieldItem.test.tsx
+++ b/src/components/Form/tests/FieldItem.test.tsx
@@ -70,8 +70,8 @@ describe("FieldItem", () => {
expect(input.id).toMatch(/^input_/);
expect(label).toHaveAttribute("for", input.id);
});
- it("should replace a `for` that refers to another element than the input element", () => {
- const { label, input } = renderFieldItem({
+ it("should keep a `for` that refers to another input element of the field item", () => {
+ const { label } = renderFieldItem({
labelProps: { text: "Label text", htmlFor: "otherinput" },
children: (
<>
@@ -80,9 +80,32 @@ describe("FieldItem", () => {
>
),
});
- expect(input).toHaveAttribute("id", "custominput");
+ expect(label).toHaveAttribute("for", "otherinput");
+ });
+ it("should replace a `for` that refers to an input element outside of the field item", () => {
+ const { container } = render(
+ <>
+
+
+
+
+ >,
+ );
+ const label = container.getElementsByClassName(`${eccgui}-fielditem__label`)[0];
expect(label).toHaveAttribute("for", "custominput");
});
+ it("should replace a `for` that refers to an input element of a nested field item", () => {
+ const { container } = render(
+
+
+
+
+
+ ,
+ );
+ const outerLabel = container.getElementsByClassName(`${eccgui}-fielditem__label`)[0];
+ expect(outerLabel).toHaveAttribute("for", "outerinput");
+ });
it("should connect label and input element via `aria-labelledby` if the label is no `label` element", () => {
const { label, input } = renderFieldItem({
disabled: true,
diff --git a/yarn.lock b/yarn.lock
index d00ef687d..ed8d92fe7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2942,6 +2942,167 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@jsonjoy.com/base64@17.67.0":
+ version "17.67.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-17.67.0.tgz#7eeda3cb41138d77a90408fd2e42b2aba10576d7"
+ integrity sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==
+
+"@jsonjoy.com/base64@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578"
+ integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==
+
+"@jsonjoy.com/buffers@17.67.0", "@jsonjoy.com/buffers@^17.65.0":
+ version "17.67.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz#5c58dbcdeea8824ce296bd1cfce006c2eb167b3d"
+ integrity sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==
+
+"@jsonjoy.com/buffers@^1.0.0", "@jsonjoy.com/buffers@^1.2.0":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz#8d99c7f67eaf724d3428dfd9826c6455266a5c83"
+ integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==
+
+"@jsonjoy.com/codegen@17.67.0":
+ version "17.67.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz#3635fd8769d77e19b75dc5574bc9756019b2e591"
+ integrity sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==
+
+"@jsonjoy.com/codegen@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz#5c23f796c47675f166d23b948cdb889184b93207"
+ integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==
+
+"@jsonjoy.com/fs-core@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-core/-/fs-core-4.80.0.tgz#6dc91c945dcc25657b9a9839580ac243a4405fef"
+ integrity sha512-qMKWshnyjbyhm+NbzqGE3w5y1mckFFEMj0aDPU2/JBNh1BzIjVy9esq9lBmRnNVmhMEkxwDzLZ8Gqh5G1P5Dag==
+ dependencies:
+ "@jsonjoy.com/fs-node-builtins" "4.80.0"
+ "@jsonjoy.com/fs-node-utils" "4.80.0"
+ thingies "^2.5.0"
+
+"@jsonjoy.com/fs-fsa@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-fsa/-/fs-fsa-4.80.0.tgz#f22b948d3fd2fb199b2fd4b9c22c60fd7f9b0fe4"
+ integrity sha512-ZBEKl7J6dbkRCfmrFV48Re211A/FHqniTUhsWCsaRteNSOqMgkX3qF1UreVgwB9oSPm81JLAwhIi6Lb/NH/PoA==
+ dependencies:
+ "@jsonjoy.com/fs-core" "4.80.0"
+ "@jsonjoy.com/fs-node-builtins" "4.80.0"
+ "@jsonjoy.com/fs-node-utils" "4.80.0"
+ thingies "^2.5.0"
+
+"@jsonjoy.com/fs-node-builtins@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.80.0.tgz#cfcffaae4acae2c1219b42bf222b2f1876141d1b"
+ integrity sha512-OIOIhqaWiwUySFMny4epIAoviXsyMaQQ63PTc3zH+5HEOvarl5hpUmZIhp2m5+OfyKAh5ylxXnQIgew1OgJDVw==
+
+"@jsonjoy.com/fs-node-to-fsa@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.80.0.tgz#4c8278a06de7e3c4f1fe626ef351474a6e3cd142"
+ integrity sha512-ljeaR5xwtX1EyRmB0wvXANUMQZ3mjKcm7z8s2N8D8SLQ04CcAGtUhHFRZQXFb9mhgFyEVMydQGQtSOFS7pcrFQ==
+ dependencies:
+ "@jsonjoy.com/fs-fsa" "4.80.0"
+ "@jsonjoy.com/fs-node-builtins" "4.80.0"
+ "@jsonjoy.com/fs-node-utils" "4.80.0"
+
+"@jsonjoy.com/fs-node-utils@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.80.0.tgz#4e9d978a172227201ed06723444c9c1658c1df34"
+ integrity sha512-xwHeVVi3f+Khg/Fmb8i5qoYVmQ08QznE5TWhQokUmIZjz4KBPzU+zt/+bahBI6G1ghtqajt27lWuAciIxApuzA==
+ dependencies:
+ "@jsonjoy.com/fs-node-builtins" "4.80.0"
+ glob-to-regex.js "^1.3.1"
+
+"@jsonjoy.com/fs-node@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node/-/fs-node-4.80.0.tgz#916f29ce9377d1bf2c7ac5a0747f704ab117dbf7"
+ integrity sha512-yiBlUfkMMRFFGy8CD/h2zd5rPnrM6bjlF+nx2LRAxNV6hOSiuObQIUEE7m7y2VTMisjxCRtzINH94bFGySWL7g==
+ dependencies:
+ "@jsonjoy.com/fs-core" "4.80.0"
+ "@jsonjoy.com/fs-node-builtins" "4.80.0"
+ "@jsonjoy.com/fs-node-utils" "4.80.0"
+ "@jsonjoy.com/fs-print" "4.80.0"
+ "@jsonjoy.com/fs-snapshot" "4.80.0"
+ glob-to-regex.js "^1.3.1"
+ thingies "^2.5.0"
+
+"@jsonjoy.com/fs-print@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-print/-/fs-print-4.80.0.tgz#5668fa62a84a0c9383b3d3adcd0379702725ef79"
+ integrity sha512-1ocZLJw/m/xlq0moaIHO7HV9nHRVBUvdc2zMzhsJGkFOrqfwWvexfCv5cYCPPLRhP+9p3yunoi4Ep5YhRORC9Q==
+ dependencies:
+ "@jsonjoy.com/fs-node-utils" "4.80.0"
+ tree-dump "^1.1.0"
+
+"@jsonjoy.com/fs-snapshot@4.80.0":
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.80.0.tgz#a7ab1eea3addd6c1e23b247100819fd19a92d3c7"
+ integrity sha512-3AV23Q1H228KTtJKTUE0nNq8FCtxSvtENOLrEat80wkAJCKHuqQEv8yjhpS8Bei2OEFJAnluGXp9Ri2SWBlROw==
+ dependencies:
+ "@jsonjoy.com/buffers" "^17.65.0"
+ "@jsonjoy.com/fs-node-utils" "4.80.0"
+ "@jsonjoy.com/json-pack" "^17.65.0"
+ "@jsonjoy.com/util" "^17.65.0"
+
+"@jsonjoy.com/json-pack@^1.11.0":
+ version "1.21.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz#93f8dd57fe3a3a92132b33d1eb182dcd9e7629fa"
+ integrity sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==
+ dependencies:
+ "@jsonjoy.com/base64" "^1.1.2"
+ "@jsonjoy.com/buffers" "^1.2.0"
+ "@jsonjoy.com/codegen" "^1.0.0"
+ "@jsonjoy.com/json-pointer" "^1.0.2"
+ "@jsonjoy.com/util" "^1.9.0"
+ hyperdyperid "^1.2.0"
+ thingies "^2.5.0"
+ tree-dump "^1.1.0"
+
+"@jsonjoy.com/json-pack@^17.65.0":
+ version "17.67.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz#8dd8ff65dd999c5d4d26df46c63915c7bdec093a"
+ integrity sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==
+ dependencies:
+ "@jsonjoy.com/base64" "17.67.0"
+ "@jsonjoy.com/buffers" "17.67.0"
+ "@jsonjoy.com/codegen" "17.67.0"
+ "@jsonjoy.com/json-pointer" "17.67.0"
+ "@jsonjoy.com/util" "17.67.0"
+ hyperdyperid "^1.2.0"
+ thingies "^2.5.0"
+ tree-dump "^1.1.0"
+
+"@jsonjoy.com/json-pointer@17.67.0":
+ version "17.67.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz#74439573dc046e0c9a3a552fb94b391bc75313b8"
+ integrity sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==
+ dependencies:
+ "@jsonjoy.com/util" "17.67.0"
+
+"@jsonjoy.com/json-pointer@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz#049cb530ac24e84cba08590c5e36b431c4843408"
+ integrity sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==
+ dependencies:
+ "@jsonjoy.com/codegen" "^1.0.0"
+ "@jsonjoy.com/util" "^1.9.0"
+
+"@jsonjoy.com/util@17.67.0", "@jsonjoy.com/util@^17.65.0":
+ version "17.67.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-17.67.0.tgz#7c4288fc3808233e55c7610101e7bb4590cddd3f"
+ integrity sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==
+ dependencies:
+ "@jsonjoy.com/buffers" "17.67.0"
+ "@jsonjoy.com/codegen" "17.67.0"
+
+"@jsonjoy.com/util@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.9.0.tgz#7ee95586aed0a766b746cd8d8363e336c3c47c46"
+ integrity sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==
+ dependencies:
+ "@jsonjoy.com/buffers" "^1.0.0"
+ "@jsonjoy.com/codegen" "^1.0.0"
+
"@keyv/bigmap@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@keyv/bigmap/-/bigmap-1.3.1.tgz#fc82fa83947e7ff68c6798d08907db842771ef2c"
@@ -4878,20 +5039,20 @@ base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-baseline-browser-mapping@^2.10.12:
- version "2.10.29"
- resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz#47bdc13027af28d341f367a4f35a07ce872e27b4"
- integrity sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==
+baseline-browser-mapping@^2.11.26:
+ version "2.11.26"
+ resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz#01bf6774efb3fbb9de20afc6b923ab85c7d230a6"
+ integrity sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
-brace-expansion@^1.1.7:
- version "1.1.18"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.18.tgz#3ce74d89885136be1535341f8c3d4425c29a5cab"
- integrity sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==
+brace-expansion@^1.1.20, brace-expansion@^1.1.7:
+ version "1.1.21"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.21.tgz#edf4fab5c64d051aea5a8def49aba1c7522279f3"
+ integrity sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
@@ -4903,26 +5064,16 @@ braces@^3.0.3:
dependencies:
fill-range "^7.1.1"
-browserslist@^4.24.0:
- version "4.24.4"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
- integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
+browserslist@^4.24.0, browserslist@^4.28.1, browserslist@^4.28.7:
+ version "4.29.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.29.3.tgz#9c8dafe96ab1c8e40e21c1274bdfa05aaee46476"
+ integrity sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==
dependencies:
- caniuse-lite "^1.0.30001688"
- electron-to-chromium "^1.5.73"
- node-releases "^2.0.19"
- update-browserslist-db "^1.1.1"
-
-browserslist@^4.28.1:
- version "4.28.2"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2"
- integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
- dependencies:
- baseline-browser-mapping "^2.10.12"
- caniuse-lite "^1.0.30001782"
- electron-to-chromium "^1.5.328"
- node-releases "^2.0.36"
- update-browserslist-db "^1.2.3"
+ baseline-browser-mapping "^2.11.26"
+ caniuse-lite "^1.0.30001813"
+ electron-to-chromium "^1.5.439"
+ node-releases "^2.0.57"
+ update-browserslist-db "^1.3.3"
bser@2.1.1:
version "2.1.1"
@@ -5029,15 +5180,10 @@ camelcase@^6.3.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
-caniuse-lite@^1.0.30001688:
- version "1.0.30001715"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz#bd325a37ad366e3fe90827d74062807a34fbaeb2"
- integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==
-
-caniuse-lite@^1.0.30001782:
- version "1.0.30001792"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz#ca8bb9be244835a335e2018272ce7223691873c5"
- integrity sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==
+caniuse-lite@^1.0.30001813:
+ version "1.0.30001814"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz#7a9e1d107dd60f6ad3dacc74df6f6d53a9dbcf9b"
+ integrity sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==
capital-case@^1.0.4:
version "1.0.4"
@@ -5978,15 +6124,15 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
-electron-to-chromium@^1.5.328:
- version "1.5.354"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.354.tgz#a155a4ff0009ff225d46ea2ef7037ba48e6601fb"
- integrity sha512-JaBHwWcfIdmSAfWM5l3uwjGd431j8YEMikZ+K/2nXVuBqJKyZ0f+2h4n4JY5AyNiZmnY9qQr2RU3v9DxDmHMNg==
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
-electron-to-chromium@^1.5.73:
- version "1.5.144"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.144.tgz#9804fd6c342802e1d4b4fe57babbe8b64ca0b46c"
- integrity sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ==
+electron-to-chromium@^1.5.439:
+ version "1.5.443"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.443.tgz#192b675e73bd809480cb9dc98babf3d370f1ac39"
+ integrity sha512-TDJG36L9A3CWWwZ97HKaE+Iz1sW80pNp6sAU0owzCIV3Zc9eVyRqZK8Vz6NKuMIU8WjP0pIkoqCa0VRf/yFD0Q==
emittery@^0.13.1:
version "0.13.1"
@@ -6547,10 +6693,10 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
-fast-uri@^3.0.1:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.5.tgz#610f37419a030270430cecd68d74e3d4d96725d0"
- integrity sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==
+fast-uri@^3.0.1, fast-uri@^3.1.6:
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.8.tgz#f7db8d942e20eead3cfe93b0beeb4eb0169e938b"
+ integrity sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==
fastest-levenshtein@^1.0.16:
version "1.0.16"
@@ -6892,6 +7038,11 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
+glob-to-regex.js@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regex.js/-/glob-to-regex.js-1.3.1.tgz#2f9fe8821218b18e6dcced9bcb4413932162db4b"
+ integrity sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==
+
glob-to-regexp@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
@@ -7471,6 +7622,11 @@ husky@^4.3.8:
slash "^3.0.0"
which-pm-runs "^1.0.0"
+hyperdyperid@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b"
+ integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==
+
iconv-lite@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
@@ -8452,18 +8608,18 @@ jest@^30.4.2:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-js-yaml@^3.13.1:
- version "3.15.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.15.1.tgz#24bc95028f361cdaaa84745b06a109c4486773c0"
- integrity sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==
+js-yaml@^3.13.1, js-yaml@^3.15.2:
+ version "3.15.2"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.15.2.tgz#3f83823ac6be17f570f23b2ecdef3777ff5ea364"
+ integrity sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
-js-yaml@^4.1.0, js-yaml@^4.3.0:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.1.tgz#01216c001d67f48e2cd560d708c7af21090a3848"
- integrity sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==
+js-yaml@^4.1.0, js-yaml@^4.3.0, js-yaml@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.2.tgz#8e44fb14a2643c59726bb15787b5f1512cb3d3fb"
+ integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==
dependencies:
argparse "^2.0.1"
@@ -9059,13 +9215,33 @@ mdn-data@2.27.1, mdn-data@^2.25.0:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.27.1.tgz#e37b9c50880b75366c4d40ac63d9bbcacdb61f0e"
integrity sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==
-memfs@^3.4.1, memfs@^3.4.12:
+memfs@^3.4.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6"
integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ==
dependencies:
fs-monkey "^1.0.4"
+memfs@^4.43.1:
+ version "4.80.0"
+ resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.80.0.tgz#aff9c653b1de1a5f21cfc35a420d2b90fb507ae1"
+ integrity sha512-N70W2isi44XdGn7uPo9NJ4zRukcdkwSFHlnYx42SWOH/+hzqNc8P7+tVoA2up4z7faHvNi1RSNCFjxoYQ71VPQ==
+ dependencies:
+ "@jsonjoy.com/fs-core" "4.80.0"
+ "@jsonjoy.com/fs-fsa" "4.80.0"
+ "@jsonjoy.com/fs-node" "4.80.0"
+ "@jsonjoy.com/fs-node-builtins" "4.80.0"
+ "@jsonjoy.com/fs-node-to-fsa" "4.80.0"
+ "@jsonjoy.com/fs-node-utils" "4.80.0"
+ "@jsonjoy.com/fs-print" "4.80.0"
+ "@jsonjoy.com/fs-snapshot" "4.80.0"
+ "@jsonjoy.com/json-pack" "^1.11.0"
+ "@jsonjoy.com/util" "^1.9.0"
+ glob-to-regex.js "^1.3.1"
+ thingies "^2.5.0"
+ tree-dump "^1.0.3"
+ tslib "^2.0.0"
+
meow@^14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-14.1.0.tgz#3cd2d16ad534829ab12fcb5010fc2fdb89facd31"
@@ -9385,13 +9561,20 @@ mime-db@^1.54.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
-mime-types@^2.1.27, mime-types@^2.1.31:
+mime-types@^2.1.27:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
+mime-types@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab"
+ integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==
+ dependencies:
+ mime-db "^1.54.0"
+
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@@ -9539,15 +9722,10 @@ node-int64@^0.4.0:
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
-node-releases@^2.0.19:
- version "2.0.19"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
- integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
-
-node-releases@^2.0.36:
- version "2.0.44"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.44.tgz#212c9b983f5bb70d311dd68c27d55dd0e65d1ca7"
- integrity sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==
+node-releases@^2.0.57:
+ version "2.0.57"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.57.tgz#47827d34b86a98b4b7afacc1222878c286b6ef2d"
+ integrity sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==
node-sass-magic-importer@^5.3.3:
version "5.3.3"
@@ -9680,6 +9858,13 @@ objectorarray@^1.0.5:
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5"
integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==
+on-finished@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -11719,6 +11904,11 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
+thingies@^2.5.0:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/thingies/-/thingies-2.6.1.tgz#4eb28e2585a75288f1765a0b08f1dfa020357a6f"
+ integrity sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==
+
tiny-invariant@^1.3.1, tiny-invariant@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
@@ -11788,6 +11978,11 @@ tr46@^5.1.0:
dependencies:
punycode "^2.3.1"
+tree-dump@^1.0.3, tree-dump@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4"
+ integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==
+
trim-lines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
@@ -12118,18 +12313,10 @@ unrs-resolver@^1.7.11:
"@unrs/resolver-binding-win32-ia32-msvc" "1.11.1"
"@unrs/resolver-binding-win32-x64-msvc" "1.11.1"
-update-browserslist-db@^1.1.1:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
- integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
- dependencies:
- escalade "^3.2.0"
- picocolors "^1.1.1"
-
-update-browserslist-db@^1.2.3:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
- integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==
+update-browserslist-db@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz#197e21fb2561fa89f8b94fad605a2b296a18993a"
+ integrity sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==
dependencies:
escalade "^3.2.0"
picocolors "^1.1.1"
@@ -12294,14 +12481,15 @@ webidl-conversions@^7.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
-webpack-dev-middleware@^6.1.2:
- version "6.1.3"
- resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-6.1.3.tgz#79f4103f8c898564c9e96c3a9c2422de50f249bc"
- integrity sha512-A4ChP0Qj8oGociTs6UdlRUGANIGrCDL3y+pmQMc+dSsraXHCatFpmMey4mYELA+juqwUqwQsUgJJISXl1KWmiw==
+webpack-dev-middleware@^6.1.2, webpack-dev-middleware@^7.4.6:
+ version "7.4.6"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.6.tgz#c27229cb157842882732ee5125fa217a2c6466dc"
+ integrity sha512-yBWCMvIfUmuhAE8vdqUKzH0vg9kuWN0KeG4vBnqRplUFHRU7lMQjkiJWxVQzvo2BTewqhPhDlMB41rAt2jVA9A==
dependencies:
colorette "^2.0.10"
- memfs "^3.4.12"
- mime-types "^2.1.31"
+ memfs "^4.43.1"
+ mime-types "^3.0.1"
+ on-finished "^2.4.1"
range-parser "^1.2.1"
schema-utils "^4.0.0"