Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@patternslib/patternslib

Package Overview
Dependencies
Maintainers
7
Versions
158
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@patternslib/patternslib - npm Package Compare versions

Comparing version 9.4.0 to 9.5.0

src/core/testmutationobserverdestroy.html

31

CHANGES.md

@@ -7,2 +7,33 @@ # Changelog

## [9.5.0](https://github.com/Patternslib/patterns/compare/9.4.0...9.5.0) (2022-09-27)
### Features
* **pat close panel:** Better close-panel support. ([fbc20a8](https://github.com/Patternslib/patterns/commit/fbc20a8616e29af2f783788c8c302b0529917cd6))- Do not close panels when the form is invalid and submitted.
- Simplify the close panel logic by switching to event based triggering.
- Allow to postpone close panel events by pat-inject until successful
injection.
- Simplify pat-modal's close-panel logic.
* **pat validation:** Add submit buttons to disable selector per default. ([e6f8ba3](https://github.com/Patternslib/patterns/commit/e6f8ba3afcee69cf68198a8b0ce9630c721c0144))
### Bug Fixes
* **core registry:** Always put pat-validation first in the pattern execution chain. ([27fb575](https://github.com/Patternslib/patterns/commit/27fb5755b86561f31ee50c2004bcc80a570d1bb4))
* **pat validation:** Fix problem with submitting invalid forms with pat-inject. ([b01819a](https://github.com/Patternslib/patterns/commit/b01819a294cfbc199f225a2cd3a40e2cbdb44b96))Stop submit event propagation if the form is invalid.
This fixes a problem where a invalid form could be submitted via pat-inject.
### Maintenance
* **pat navigation:** Remove console.log debug message from tests. ([4529761](https://github.com/Patternslib/patterns/commit/4529761f72a7c47b218b9258b2b1f2ee645429a6))
* **pat validation:** Add modal use case to demo. ([aa99e2e](https://github.com/Patternslib/patterns/commit/aa99e2e31d70d9a375135863d04c215362bec2e8))
## [9.4.0](https://github.com/Patternslib/patterns/compare/9.3.1...9.4.0) (2022-09-23)

@@ -9,0 +40,0 @@

2

package.json
{
"name": "@patternslib/patternslib",
"version": "9.4.0",
"version": "9.5.0",
"title": "Markup patterns to drive behaviour.",

@@ -5,0 +5,0 @@ "description": "Patternslib is a JavaScript library that enables designers to build rich interactive prototypes without the need for writing any Javascript. All events are triggered by classes and other attributes in the HTML, without abusing the HTML as a programming language. Accessibility, SEO and well structured HTML are core values of Patterns.",

@@ -131,8 +131,6 @@ /**

orderPatterns(patterns) {
// XXX: Bit of a hack. We need the validation pattern to be
// parsed and initiated before the inject pattern. So we make
// sure here, that it appears first. Not sure what would be
// the best solution. Perhaps some kind of way to register
// patterns "before" or "after" other patterns.
if (patterns.includes("validation") && patterns.includes("inject")) {
// Always add pat-validation as first pattern, so that it can prevent
// other patterns from reacting to submit events if form validation
// fails.
if (patterns.includes("validation")) {
patterns.splice(patterns.indexOf("validation"), 1);

@@ -139,0 +137,0 @@ patterns.unshift("validation");

import Base from "../../core/base";
import dom from "../../core/dom";
import events from "../../core/events";
import utils from "../../core/utils";

@@ -9,22 +10,32 @@ export default Base.extend({

init() {
this.el.addEventListener("click", (e) => {
// Find the first element which has a close-panel or is a dialog.
// This should the panel-root itself.
const panel = this.el.closest(".has-close-panel, dialog");
// Close panel support for dialog panels
// Other modals are handled in pat-modal.
const dialog_panel = this.el.closest("dialog");
if (dialog_panel) {
events.add_event_listener(
dialog_panel,
"close-panel",
"close-panel--dialog",
() => {
dialog_panel.close();
}
);
}
if (!panel) {
// Nothing to do. Exiting.
this.el.addEventListener("click", async (e) => {
await utils.timeout(0); // Wait for other patterns, like pat-validation.
if (
e.target.matches("[type=submit], button:not([type=button])") &&
this.el.closest("form")?.checkValidity() === false
) {
// Prevent closing an invalid form when submitting.
return;
} else if (panel.tagName === "DIALOG") {
// Close the dialog.
panel.close();
} else if (panel.classList.contains("has-close-panel")) {
// Get the close panel method.
const close_method = dom.get_data(panel, "close_panel");
}
// Now execute the method and close the panel.
close_method && close_method(e);
}
this.el.dispatchEvent(
new Event("close-panel", { bubbles: true, cancelable: true })
);
});
},
});
import utils from "../../core/utils";
import pat_modal from "../modal/modal";
import registry from "../../core/registry";
import "./close-panel";
import "../modal/modal";
import "../tooltip/tooltip";

@@ -31,7 +31,7 @@

expect(document.querySelectorAll(".tooltip-container").length).toBe(1);
expect(document.querySelectorAll(".tooltip-container.has-close-panel").length).toBe(1); // prettier-ignore
expect(document.querySelectorAll(".tooltip-container .pat-tooltip--close-button.close-panel").length).toBe(1); // prettier-ignore
document.querySelector(".pat-tooltip--close-button").click();
await utils.timeout(1); // wait a tick async to settle.
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // hide is async - wait for it.
expect(document.querySelectorAll(".tooltip-container").length).toBe(0);

@@ -41,7 +41,8 @@ expect(document.querySelectorAll(".tooltip-container .pat-tooltip--close-button").length).toBe(0); // prettier-ignore

document.querySelector("#close-modal").click();
await utils.timeout(1); // wait a tick async to settle.
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // destroy is async - wait for it.
expect(document.querySelectorAll(".pat-modal").length).toBe(0);
});
it("Closes a dialog's panel.", function () {
it("Closes a dialog's panel.", async function () {
document.body.innerHTML = `

@@ -60,5 +61,35 @@ <dialog open>

document.querySelector(".close-panel").click();
await utils.timeout(1); // close-button is async - wait for it.
expect(dialog.open).toBe(false);
});
it("Prevents closing a panel with an invalid form when submitting but allow to cancel and close.", async function () {
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
document.body.innerHTML = `
<div class="pat-modal">
<form action="." class="pat-validation">
<input name="ok" required />
<button class="close-panel submit">submit</button>
<button class="close-panel cancel" type="button">cancel</button>
</form>
</div>
`;
const el = document.querySelector("form");
registry.scan(document.body);
await utils.timeout(1); // wait a tick for async to settle.
el.querySelector("button.submit").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(spy_destroy_modal).not.toHaveBeenCalled();
// A non-submit close-panel button does not check for validity.
el.querySelector("button.cancel").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(spy_destroy_modal).toHaveBeenCalled();
});
});

@@ -5,2 +5,3 @@ import "../../core/jquery-ext"; // for :scrollable for autoLoading-visible

import dom from "../../core/dom";
import events from "../../core/events";
import logging from "../../core/logging";

@@ -582,3 +583,3 @@ import Parser from "../../core/parser";

async _onInjectSuccess($el, cfgs, ev) {
let data = ev && ev.jqxhr && ev.jqxhr.responseText;
let data = ev?.jqxhr?.responseText;
if (!data) {

@@ -756,2 +757,30 @@ log.warn("No response content, aborting", ev);

// Prevent closing the panel while injection is in progress.
let do_close_panel = false;
events.add_event_listener(
$el[0],
"close-panel",
"pat-inject--close-panel",
(e) => {
e.stopPropagation();
e.stopImmediatePropagation();
do_close_panel = true;
}
);
$el.on("pat-ajax-success.pat-inject", async () => {
// Wait for the next tick to ensure that the close-panel listener
// is called before this one, even for non-async local injects.
await utils.timeout(1);
// Only close the panel if a close-panel event was catched previously.
if (do_close_panel) {
do_close_panel = false;
// Remove the close-panel event listener.
events.remove_event_listener($el[0], "pat-inject--close-panel");
// Re-trigger close-panel event if it was caught while injection was in progress.
$el[0].dispatchEvent(
new Event("close-panel", { bubbles: true, cancelable: true })
);
}
});
if (cfgs[0].url.length) {

@@ -758,0 +787,0 @@ ajax.request($el, {

import $ from "jquery";
import Base from "../../core/base";
import Parser from "../../core/parser";
import dom from "../../core/dom";
import events from "../../core/events";

@@ -121,4 +120,5 @@ import inject from "../inject/inject";

_init_handlers() {
this.el.classList.add("has-close-panel");
dom.set_data(this.el, "close_panel", this._close_handler.bind(this));
events.add_event_listener(this.el, "close-panel", "pat-modal--close-panel", () =>
this.destroy()
);

@@ -162,12 +162,2 @@ $(document).on("keyup.pat-modal", this._onKeyUp.bind(this));

_close_handler(e) {
if (e.target.matches("[type=submit], button:not([type=button])")) {
// submit + close
this.destroy_inject(e);
} else {
// close only
this.destroy();
}
},
getTallestChild() {

@@ -211,25 +201,2 @@ let $tallest_child;

},
destroy_inject(e) {
const button = e.target;
const form = button.form;
if (form && form.classList.contains("pat-inject")) {
// if the modal contains a form with pat-inject, wait for injection
// to be finished and then destroy the modal.
const destroy_handler = () => {
this.destroy();
events.remove_event_listener(form, "pat-modal--destroy-inject");
};
events.add_event_listener(
form,
"pat-inject-success",
"pat-modal--destroy-inject",
destroy_handler.bind(this)
);
} else {
// if working without form injection, just destroy.
this.destroy();
}
},
});

@@ -201,2 +201,3 @@ import $ from "jquery";

document.querySelector("button.close-panel[type=submit]").click();
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // wait a tick for pat-inject to settle.

@@ -240,2 +241,3 @@ await utils.timeout(1); // wait a tick for pat-modal destroy to settle.

document.querySelector(".form2 button.close-panel[type=submit]").click();
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // wait a tick for pat-inject to settle.

@@ -294,3 +296,2 @@ await utils.timeout(1); // wait a tick for pat-modal destroy to settle.

const spy_destroy = jest.spyOn(instance, "destroy");
const spy_destroy_inject = jest.spyOn(instance, "destroy_inject");

@@ -302,5 +303,5 @@ // ``destroy`` was already initialized with instantiating the pattern above.

document.querySelector("#close-modal").click();
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // wait a tick for pat-inject to settle.
expect(spy_destroy_inject).toHaveBeenCalledTimes(1);
expect(spy_destroy).toHaveBeenCalledTimes(1);

@@ -314,6 +315,6 @@

document.querySelector("#close-modal").click();
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // wait a tick for pat-inject to settle.
// Previous mocks still active.
// Handlers executed exactly once.
expect(spy_destroy_inject).toHaveBeenCalledTimes(1);
expect(spy_destroy).toHaveBeenCalledTimes(1);

@@ -320,0 +321,0 @@ });

@@ -215,4 +215,2 @@ import "../inject/inject";

console.log(document.body.innerHTML);
expect(w1.classList.contains("current")).toBeFalsy();

@@ -219,0 +217,0 @@ expect(w1.classList.contains("navigation-in-path")).toBeTruthy();

@@ -92,4 +92,8 @@ // Patterns notification - Display (self-healing) notifications.

if (wrapper.querySelector(".close-panel")) {
wrapper.classList.add("has-close-panel");
dom.set_data(wrapper, "close_panel", this.onClick.bind(this));
events.add_event_listener(
wrapper,
"close-panel",
"pat-notification--close-panel",
this.onClick.bind(this)
);
registry.scan(wrapper, ["close-panel"]);

@@ -96,0 +100,0 @@ } else {

import Pattern from "./notification";
import utils from "../../core/utils";

@@ -8,3 +9,3 @@ describe("pat notification", function () {

it("Gets initialized and will disappear as per default settings.", function () {
it("Gets initialized and will disappear as per default settings.", async function () {
document.body.innerHTML = `

@@ -19,11 +20,16 @@ <p class="pat-notification">

new Pattern(el);
const instance = new Pattern(el);
const spy_remove = jest.spyOn(instance, "remove");
expect(el.parentNode).not.toBe(document.body);
expect(el.parentNode.classList.contains("pat-notification-panel")).toBe(true);
expect(el.parentNode.classList.contains("has-close-panel")).toBe(true);
// With jQuery animation, we cannot easily test closing the panel yet.
// TODO: Fix when jQuery animation was removed.
const btn_close = el.parentNode.querySelector(".close-panel");
expect(btn_close).not.toBe(null);
btn_close.click();
await utils.timeout(1); // close-button is async - wait for it.
expect(spy_remove).toHaveBeenCalled();
});
});
import $ from "jquery";
import Base from "../../core/base";
import dom from "../../core/dom";
import logging from "../../core/logging";

@@ -293,4 +292,8 @@ import Parser from "../../core/parser";

// Store reference to method for closing panels on the tooltip element instance.
tippy_classes.push("has-close-panel");
dom.set_data(this.tippy.popper, "close_panel", this.hide.bind(this));
events.add_event_listener(
this.tippy.popper,
"close-panel",
"pat-tooltip--close-panel",
() => this.hide()
);

@@ -297,0 +300,0 @@ this.tippy.popper.classList.add(...tippy_classes);

@@ -365,3 +365,4 @@ import $ from "jquery";

closebutton.click();
await utils.timeout(1);
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // hide is async - wait for it.
expect(spy_hide).toHaveBeenCalled();

@@ -399,2 +400,3 @@

closebutton.click();
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // hide is async - wait for it.

@@ -436,3 +438,4 @@ expect(spy_hide).toHaveBeenCalled();

btn_submit.click();
await utils.timeout(1);
await utils.timeout(1); // close-button is async - wait for it.
await utils.timeout(1); // hide is async - wait for it.

@@ -439,0 +442,0 @@ expect(mock_listener).toHaveBeenCalledTimes(1);

@@ -14,3 +14,3 @@ // Patterns validate - Form vlidation

export const parser = new Parser("validation");
parser.addArgument("disable-selector", null); // Elements which must be disabled if there are errors
parser.addArgument("disable-selector", "[type=submit], button:not([type=button])"); // Elements which must be disabled if there are errors
parser.addArgument("message-date", ""); // "This value must be a valid date");

@@ -259,4 +259,6 @@ parser.addArgument("message-datetime", ""); // "This value must be a valid date and time");

if (event?.type === "submit") {
// Do not submit in error case.
// Do not submit in error case and prevent other handlers to take action.
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
}

@@ -263,0 +265,0 @@ this.set_error_message(input, input_options);

import Pattern, { parser } from "./validation";
import events from "../../core/events";
import utils from "../../core/utils";
import events from "../../core/events";
import { jest } from "@jest/globals";

@@ -290,2 +291,149 @@ describe("pat-validation", function () {

it("1.13 - Prevents other event handlers when invalid.", async function () {
document.body.innerHTML = `
<form class="pat-validation">
<input name="ok" required />
<button>submit</button>
</form>
`;
const el = document.querySelector(".pat-validation");
new Pattern(el);
let submit_called = false;
let click_called = false;
// Note: the handlers must be registered after Pattern initialization.
// Otherwise the pattern will not be able to prevent the event.
// In case of other patterns, the validation pattern will be reordered
// first and submit prevention does work.
el.addEventListener("submit", () => (submit_called = true));
el.addEventListener("click", () => (click_called = true));
el.querySelector("button").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(el.querySelectorAll("em.warning").length).toBe(1);
expect(submit_called).toBe(false);
expect(click_called).toBe(true);
});
it("1.14 - Prevents pat-inject form submission when invalid.", async function () {
const pat_inject = (await import("../inject/inject")).default;
const registry = (await import("../../core/registry")).default;
document.body.innerHTML = `
<form action="." class="pat-inject pat-validation">
<input name="ok" required />
<button>submit</button>
</form>
`;
const el = document.querySelector(".pat-validation");
const spy_inject_submit = jest.spyOn(pat_inject, "onTrigger");
registry.scan(document.body);
await utils.timeout(1); // wait a tick for async to settle.
el.querySelector("button").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(el.querySelectorAll("em.warning").length).toBe(1);
expect(spy_inject_submit).not.toHaveBeenCalled();
});
it("1.15 - Prevents pat-modal closing with a pat-inject when invalid.", async function () {
await import("../close-panel/close-panel");
const pat_inject = (await import("../inject/inject")).default;
const pat_modal = (await import("../modal/modal")).default;
const registry = (await import("../../core/registry")).default;
document.body.innerHTML = `
<div class="pat-modal">
<form action="." class="pat-inject pat-validation">
<input name="ok" required />
<button class="close-panel">submit</button>
</form>
</div>
`;
const el = document.querySelector("form");
const spy_inject_submit = jest.spyOn(pat_inject, "onTrigger");
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
registry.scan(document.body);
await utils.timeout(1); // wait a tick for async to settle.
el.querySelector("button").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(el.querySelectorAll("em.warning").length).toBe(1);
expect(spy_inject_submit).not.toHaveBeenCalled();
expect(spy_destroy_modal).not.toHaveBeenCalled();
});
it("1.16 - Prevents pat-modal closing when invalid.", async function () {
await import("../close-panel/close-panel");
const pat_modal = (await import("../modal/modal")).default;
const registry = (await import("../../core/registry")).default;
document.body.innerHTML = `
<div class="pat-modal">
<form action="." class="pat-validation">
<input name="ok" required />
<button class="close-panel">submit</button>
</form>
</div>
`;
const el = document.querySelector("form");
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
registry.scan(document.body);
await utils.timeout(1); // wait a tick for async to settle.
el.querySelector("button").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(el.querySelectorAll("em.warning").length).toBe(1);
expect(spy_destroy_modal).not.toHaveBeenCalled();
});
it("1.17 - Prevents pat-modal closing when invalid with custom validation rule.", async function () {
await import("../close-panel/close-panel");
const pat_modal = (await import("../modal/modal")).default;
const registry = (await import("../../core/registry")).default;
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
document.body.innerHTML = `
<div class="pat-modal">
<form action="." class="pat-validation">
<input name="ok" />
<input name="nok" data-pat-validation="equality: ok" />
<button class="close-panel submit">submit</button>
<button class="close-panel cancel" type="button">cancel</button>
</form>
</div>
`;
const el = document.querySelector("form");
const inp_ok = document.querySelector("input[name=ok]");
inp_ok.value = "foo";
registry.scan(document.body);
await utils.timeout(1); // wait a tick for async to settle.
el.querySelector("button.submit").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(el.querySelectorAll("em.warning").length).toBe(1);
expect(spy_destroy_modal).not.toHaveBeenCalled();
// A non-submit close-panel button does not check for validity.
el.querySelector("button.cancel").click();
await utils.timeout(1); // wait a tick for async to settle.
expect(spy_destroy_modal).toHaveBeenCalled();
});
it("2.1 - validates required inputs", async function () {

@@ -292,0 +440,0 @@ document.body.innerHTML = `

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc