PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.6
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.6
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / assets / js / widget-notes.js

widget-notes.js in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.9.6, at assets/js/widget-notes.js

1,425 lines 45.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function() {
2 "use strict";
3 function html(strings, ...values) {
4 return { __wpdHtml: true, strings, values };
5 }
6 function isTemplateResult(v) {
7 return !!v && v.__wpdHtml === true;
8 }
9 const MARKER_PREFIX = "$$wpd$$";
10 const MARKER_RE = /\$\$wpd\$\$(\d+)\$\$/g;
11 function joinWithMarkers(strings) {
12 let out = strings[0];
13 for (let i = 1; i < strings.length; i++) {
14 out += `${MARKER_PREFIX}${i - 1}$$` + strings[i];
15 }
16 return out;
17 }
18 const compiledCache = /* @__PURE__ */ new WeakMap();
19 function compile(strings) {
20 const cached = compiledCache.get(strings);
21 if (cached) {
22 return cached;
23 }
24 const template = document.createElement("template");
25 template.innerHTML = joinWithMarkers(strings);
26 const recipes = [];
27 const walk = (node, path) => {
28 if (node.nodeType === Node.ELEMENT_NODE) {
29 const el = node;
30 for (const attr of Array.from(el.attributes)) {
31 const rawName = attr.name;
32 const rawValue = attr.value;
33 const prefix = rawName[0];
34 if (MARKER_RE.test(rawValue)) {
35 MARKER_RE.lastIndex = 0;
36 if (prefix === "@") {
37 const match = MARKER_RE.exec(rawValue);
38 MARKER_RE.lastIndex = 0;
39 recipes.push({
40 path,
41 kind: "event",
42 name: rawName.slice(1),
43 valueIndex: match ? Number(match[1]) : 0
44 });
45 el.removeAttribute(rawName);
46 } else if (prefix === ".") {
47 const match = MARKER_RE.exec(rawValue);
48 MARKER_RE.lastIndex = 0;
49 recipes.push({
50 path,
51 kind: "prop",
52 name: rawName.slice(1),
53 valueIndex: match ? Number(match[1]) : 0
54 });
55 el.removeAttribute(rawName);
56 } else if (prefix === "?") {
57 const match = MARKER_RE.exec(rawValue);
58 MARKER_RE.lastIndex = 0;
59 recipes.push({
60 path,
61 kind: "bool",
62 name: rawName.slice(1),
63 valueIndex: match ? Number(match[1]) : 0
64 });
65 el.removeAttribute(rawName);
66 } else {
67 const fragments = [];
68 const indices = [];
69 let lastEnd = 0;
70 let m;
71 MARKER_RE.lastIndex = 0;
72 while ((m = MARKER_RE.exec(rawValue)) !== null) {
73 fragments.push(rawValue.slice(lastEnd, m.index));
74 indices.push(Number(m[1]));
75 lastEnd = m.index + m[0].length;
76 }
77 fragments.push(rawValue.slice(lastEnd));
78 recipes.push({
79 path,
80 kind: "attr",
81 name: rawName,
82 template: fragments,
83 valueIndices: indices
84 });
85 el.setAttribute(rawName, "");
86 }
87 }
88 }
89 }
90 const children = Array.from(node.childNodes);
91 let shift = 0;
92 for (let i = 0; i < children.length; i++) {
93 const child = children[i];
94 const liveIndex = i + shift;
95 if (child.nodeType === Node.TEXT_NODE) {
96 const text = child.textContent || "";
97 if (!MARKER_RE.test(text)) {
98 MARKER_RE.lastIndex = 0;
99 continue;
100 }
101 MARKER_RE.lastIndex = 0;
102 const parent = child.parentNode;
103 let lastEnd = 0;
104 let m;
105 const newNodes = [];
106 const newRecipes = [];
107 MARKER_RE.lastIndex = 0;
108 while ((m = MARKER_RE.exec(text)) !== null) {
109 if (m.index > lastEnd) {
110 newNodes.push(document.createTextNode(text.slice(lastEnd, m.index)));
111 }
112 const placeholder = document.createTextNode("");
113 newNodes.push(placeholder);
114 newRecipes.push({
115 path: [...path, liveIndex + newNodes.length - 1],
116 kind: "node",
117 valueIndex: Number(m[1])
118 });
119 lastEnd = m.index + m[0].length;
120 }
121 if (lastEnd < text.length) {
122 newNodes.push(document.createTextNode(text.slice(lastEnd)));
123 }
124 for (const nn of newNodes) {
125 parent.insertBefore(nn, child);
126 }
127 parent.removeChild(child);
128 shift += newNodes.length - 1;
129 recipes.push(...newRecipes);
130 } else {
131 walk(child, [...path, liveIndex]);
132 }
133 }
134 };
135 walk(template.content, []);
136 const buildParts = (fragment) => {
137 const out = [];
138 for (const r of recipes) {
139 let node = fragment;
140 for (const idx of r.path) {
141 node = node.childNodes[idx];
142 }
143 if (r.kind === "node") {
144 out.push({
145 kind: "node",
146 valueIndex: r.valueIndex,
147 child: {
148 anchor: node,
149 state: null
150 }
151 });
152 } else if (r.kind === "attr") {
153 out.push({
154 kind: "attr",
155 element: node,
156 name: r.name,
157 template: r.template,
158 valueIndices: r.valueIndices
159 });
160 } else if (r.kind === "event") {
161 out.push({
162 kind: "event",
163 valueIndex: r.valueIndex,
164 element: node,
165 name: r.name
166 });
167 } else if (r.kind === "prop") {
168 out.push({
169 kind: "prop",
170 valueIndex: r.valueIndex,
171 element: node,
172 name: r.name
173 });
174 } else if (r.kind === "bool") {
175 out.push({
176 kind: "bool",
177 valueIndex: r.valueIndex,
178 element: node,
179 name: r.name
180 });
181 }
182 }
183 return out;
184 };
185 const entry = { template, buildParts };
186 compiledCache.set(strings, entry);
187 return entry;
188 }
189 const mountState = /* @__PURE__ */ new WeakMap();
190 function mountIntact(state, container) {
191 for (const node of state.nodes) {
192 if (node.parentNode !== container) {
193 return false;
194 }
195 }
196 return true;
197 }
198 function render(result, container) {
199 const existing = mountState.get(container);
200 if (existing && existing.strings === result.strings && mountIntact(existing, container)) {
201 applyValues(existing.parts, result.values);
202 return;
203 }
204 const compiled = compile(result.strings);
205 const fragment = compiled.template.content.cloneNode(true);
206 const parts = compiled.buildParts(fragment);
207 const nodes = Array.from(fragment.childNodes);
208 while (container.firstChild) {
209 container.removeChild(container.firstChild);
210 }
211 container.appendChild(fragment);
212 applyValues(parts, result.values);
213 mountState.set(container, { strings: result.strings, parts, nodes });
214 }
215 function applyValues(parts, values) {
216 for (const part of parts) {
217 if (part.kind === "node") {
218 updateChildPart(part.child, values[part.valueIndex]);
219 } else if (part.kind === "attr") {
220 let composed = part.template[0];
221 for (let i = 0; i < part.valueIndices.length; i++) {
222 composed += formatText(values[part.valueIndices[i]]);
223 composed += part.template[i + 1];
224 }
225 if (composed !== part.last) {
226 part.last = composed;
227 if (composed === "") {
228 part.element.removeAttribute(part.name);
229 } else {
230 part.element.setAttribute(part.name, composed);
231 }
232 }
233 } else if (part.kind === "event") {
234 const next = values[part.valueIndex];
235 if (next !== part.current) {
236 if (part.current) {
237 part.element.removeEventListener(part.name, part.current);
238 }
239 if (next) {
240 part.element.addEventListener(part.name, next);
241 }
242 part.current = next;
243 }
244 } else if (part.kind === "prop") {
245 const next = values[part.valueIndex];
246 if (next !== part.last) {
247 part.last = next;
248 part.element[part.name] = next;
249 }
250 } else if (part.kind === "bool") {
251 const next = !!values[part.valueIndex];
252 if (next !== part.last) {
253 part.last = next;
254 if (next) {
255 part.element.setAttribute(part.name, "");
256 } else {
257 part.element.removeAttribute(part.name);
258 }
259 }
260 }
261 }
262 }
263 function updateChildPart(child, value) {
264 if (value === null || value === void 0 || value === false) {
265 if (child.state) {
266 disposeChildState(child.state);
267 child.state = null;
268 }
269 return;
270 }
271 if (Array.isArray(value)) {
272 updateArrayChild(child, value);
273 return;
274 }
275 if (isTemplateResult(value)) {
276 updateTemplateChild(child, value);
277 return;
278 }
279 if (value instanceof Node) {
280 updateNodeChild(child, value);
281 return;
282 }
283 updateTextChild(child, formatText(value));
284 }
285 function updateNodeChild(child, node) {
286 const old = child.state;
287 if (old?.shape === "node" && old.node === node) {
288 return;
289 }
290 if (old) {
291 disposeChildState(old);
292 }
293 insertBeforeAnchor(child, [node]);
294 child.state = { shape: "node", node };
295 }
296 function updateTextChild(child, text) {
297 const old = child.state;
298 if (old?.shape === "text") {
299 if (old.text !== text) {
300 old.node.textContent = text;
301 old.text = text;
302 }
303 return;
304 }
305 if (old) {
306 disposeChildState(old);
307 }
308 const node = document.createTextNode(text);
309 insertBeforeAnchor(child, [node]);
310 child.state = { shape: "text", node, text };
311 }
312 function updateTemplateChild(child, result) {
313 const old = child.state;
314 if (old?.shape === "template" && old.strings === result.strings) {
315 applyValues(old.parts, result.values);
316 return;
317 }
318 if (old) {
319 disposeChildState(old);
320 }
321 const compiled = compile(result.strings);
322 const fragment = compiled.template.content.cloneNode(true);
323 const parts = compiled.buildParts(fragment);
324 const topNodes = Array.from(fragment.childNodes);
325 insertBeforeAnchor(child, [fragment]);
326 applyValues(parts, result.values);
327 child.state = {
328 shape: "template",
329 strings: result.strings,
330 parts,
331 nodes: topNodes
332 };
333 }
334 function updateArrayChild(child, arr) {
335 const old = child.state;
336 if (old?.shape === "array" && old.entries.length === arr.length) {
337 for (let i = 0; i < arr.length; i++) {
338 updateChildPart(old.entries[i], arr[i]);
339 }
340 return;
341 }
342 if (old) {
343 disposeChildState(old);
344 }
345 const entries = [];
346 for (const v of arr) {
347 const entryAnchor = document.createTextNode("");
348 insertBeforeAnchor(child, [entryAnchor]);
349 const entry = { anchor: entryAnchor, state: null };
350 updateChildPart(entry, v);
351 entries.push(entry);
352 }
353 child.state = { shape: "array", entries };
354 }
355 function insertBeforeAnchor(child, nodes) {
356 const parent = child.anchor.parentNode;
357 if (!parent) {
358 return;
359 }
360 for (const node of nodes) {
361 parent.insertBefore(node, child.anchor);
362 }
363 }
364 function disposeChildState(state) {
365 if (state.shape === "text") {
366 state.node.remove();
367 return;
368 }
369 if (state.shape === "template") {
370 for (const node of state.nodes) {
371 if (node.parentNode) {
372 node.parentNode.removeChild(node);
373 }
374 }
375 return;
376 }
377 if (state.shape === "node") {
378 if (state.node.parentNode) {
379 state.node.parentNode.removeChild(state.node);
380 }
381 return;
382 }
383 for (const entry of state.entries) {
384 if (entry.state) {
385 disposeChildState(entry.state);
386 }
387 entry.anchor.remove();
388 }
389 }
390 function formatText(v) {
391 if (v === null || v === void 0 || v === false) {
392 return "";
393 }
394 return String(v);
395 }
396 const _Component = class _Component extends HTMLElement {
397 constructor() {
398 super();
399 this._renderScheduled = false;
400 this._propValues = {};
401 const ctor = this.constructor;
402 if (ctor.shadow) {
403 this.attachShadow({ mode: "open" });
404 this._renderRoot = this.shadowRoot;
405 } else {
406 this._renderRoot = this;
407 }
408 this._installPropAccessors();
409 }
410 static get observedAttributes() {
411 return this.props.map(kebab);
412 }
413 connectedCallback() {
414 this._adoptStyles();
415 this.requestUpdate();
416 }
417 attributeChangedCallback(name, oldValue, newValue) {
418 if (oldValue === newValue) {
419 return;
420 }
421 const prop = camel(name);
422 this._propValues[prop] = newValue;
423 this.requestUpdate();
424 }
425 /**
426 * Declarative class-name setter. Assign an array (or a
427 * space-separated string) and the host's `class` attribute is
428 * rewritten to match. Intended for programmatic styling — when
429 * a plugin has enqueued its own stylesheet and wants to apply
430 * one of those classes to a shell component:
431 *
432 * ```js
433 * element.classNames = [ 'my-plugin-brand', 'is-active' ];
434 * // → <wpd-select class="my-plugin-brand is-active">
435 * ```
436 *
437 * The plain HTML `class="…"` attribute works just the same and
438 * is always preferred when writing markup by hand — this setter
439 * exists for the JS-API case where the caller has an array of
440 * conditional classes in hand.
441 *
442 * Getter returns the current `classList` as a plain array for
443 * symmetric read/write.
444 *
445 * @since 0.5.0
446 */
447 get classNames() {
448 return Array.from(this.classList);
449 }
450 set classNames(next) {
451 if (next === null || next === void 0) {
452 this.removeAttribute("class");
453 return;
454 }
455 const list = Array.isArray(next) ? next : String(next).split(/\s+/);
456 const cleaned = list.map((s) => String(s).trim()).filter((s) => s !== "");
457 this.className = cleaned.join(" ");
458 }
459 /**
460 * Request a re-render explicitly. Components rarely need this —
461 * declare state via props + attribute observers and the render
462 * loop picks up changes automatically.
463 */
464 requestUpdate() {
465 this._scheduleRender();
466 }
467 /**
468 * Dispatch a `CustomEvent` with a `detail`. Bubbles + composed
469 * by default (matches typical WC UX — events cross shadow
470 * boundaries, parents can listen without knowing about internal
471 * structure).
472 */
473 emit(name, detail) {
474 return this.dispatchEvent(
475 new CustomEvent(name, {
476 detail,
477 bubbles: true,
478 composed: true
479 })
480 );
481 }
482 // ------------------------------------------------------------------
483 // Internals
484 // ------------------------------------------------------------------
485 /**
486 * Wire every `static props` entry to a matched property getter +
487 * setter on the element. Setting the property reflects into the
488 * attribute (so downstream observers + CSS selectors see it);
489 * reading the property falls back to the attribute.
490 */
491 _installPropAccessors() {
492 const ctor = this.constructor;
493 for (const prop of ctor.props) {
494 if (Object.getOwnPropertyDescriptor(this, prop)) {
495 continue;
496 }
497 const attr = kebab(prop);
498 Object.defineProperty(this, prop, {
499 get: () => {
500 if (prop in this._propValues) {
501 return this._propValues[prop];
502 }
503 return this.getAttribute(attr);
504 },
505 set: (value) => {
506 let str;
507 if (value === null || value === void 0 || value === false) {
508 str = null;
509 } else if (value === true) {
510 str = "";
511 } else {
512 str = String(value);
513 }
514 this._propValues[prop] = str;
515 if (str === null) {
516 this.removeAttribute(attr);
517 } else {
518 this.setAttribute(attr, str);
519 }
520 this.requestUpdate();
521 },
522 enumerable: true,
523 configurable: true
524 });
525 }
526 }
527 /**
528 * Schedule a render on the next microtask. Multiple property
529 * assignments in the same tick collapse into a single render.
530 */
531 _scheduleRender() {
532 if (this._renderScheduled || !this.isConnected) {
533 return;
534 }
535 this._renderScheduled = true;
536 queueMicrotask(() => {
537 this._renderScheduled = false;
538 if (!this.isConnected) {
539 return;
540 }
541 render(this.render(), this._renderRoot);
542 });
543 }
544 /**
545 * Mount adoptable stylesheets onto the shadow root (via
546 * `adoptedStyleSheets`) or the light DOM (via one `<style>`
547 * tag per def). No-op if `static styles` is empty.
548 */
549 _adoptStyles() {
550 const ctor = this.constructor;
551 if (ctor.styles.length === 0) {
552 return;
553 }
554 if (ctor.shadow && this.shadowRoot) {
555 const sheets = ctor.styles.map((s) => s.sheet).filter((s) => s !== null);
556 this.shadowRoot.adoptedStyleSheets = sheets;
557 if (sheets.length !== ctor.styles.length) {
558 for (const s of ctor.styles) {
559 if (!s.sheet) {
560 const tag = document.createElement("style");
561 tag.textContent = s.cssText;
562 this.shadowRoot.appendChild(tag);
563 }
564 }
565 }
566 } else {
567 this._adoptLightStyles(ctor);
568 }
569 }
570 _adoptLightStyles(ctor) {
571 if (_Component._lightStylesAdopted.has(ctor)) {
572 return;
573 }
574 _Component._lightStylesAdopted.add(ctor);
575 for (const s of ctor.styles) {
576 const tag = document.createElement("style");
577 tag.dataset.wpdUi = this.tagName.toLowerCase();
578 tag.textContent = s.cssText;
579 document.head.appendChild(tag);
580 }
581 }
582 };
583 _Component.props = [];
584 _Component.styles = [];
585 _Component.shadow = true;
586 _Component._lightStylesAdopted = /* @__PURE__ */ new WeakSet();
587 let Component = _Component;
588 function defineComponent(tag, ctor) {
589 if (customElements.get(tag)) {
590 return;
591 }
592 customElements.define(tag, ctor);
593 }
594 function kebab(s) {
595 return s.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
596 }
597 function camel(s) {
598 return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
599 }
600 const SUPPORTS_CONSTRUCTABLE_SHEETS = (() => {
601 try {
602 const s = new CSSStyleSheet();
603 return typeof s.replaceSync === "function";
604 } catch {
605 return false;
606 }
607 })();
608 function css(strings, ...values) {
609 let text = strings[0];
610 for (let i = 1; i < strings.length; i++) {
611 const v = values[i - 1];
612 if (typeof v === "string" || typeof v === "number") {
613 text += String(v);
614 } else if (v && v.__wpdCss) {
615 text += v.cssText;
616 } else {
617 throw new TypeError(
618 "[wpd-ui] css`` interpolations must be strings, numbers, or other css`` results. Got: " + typeof v
619 );
620 }
621 text += strings[i];
622 }
623 if (SUPPORTS_CONSTRUCTABLE_SHEETS) {
624 const sheet = new CSSStyleSheet();
625 sheet.replaceSync(text);
626 return { __wpdCss: true, sheet, cssText: text };
627 }
628 return { __wpdCss: true, sheet: null, cssText: text };
629 }
630 function computeAutoId(element) {
631 const parts = [];
632 const tabs = [];
633 let windowId = null;
634 let node = element.parentElement;
635 while (node) {
636 if (node === document.body || node === document.documentElement) {
637 break;
638 }
639 const id = node.id || "";
640 if (id.startsWith("wp-window-")) {
641 windowId = id.slice("wp-window-".length);
642 break;
643 }
644 if (node.tagName.toLowerCase() === "wpd-tabpanel") {
645 const forValue = node.getAttribute("for");
646 if (forValue) {
647 tabs.unshift(forValue);
648 }
649 }
650 node = node.parentElement;
651 }
652 if (windowId) {
653 parts.push(slugify(windowId));
654 }
655 for (const tab of tabs) {
656 parts.push("tab-" + slugify(tab));
657 }
658 const label = element.getAttribute("label");
659 if (label) {
660 parts.push(slugify(label));
661 }
662 if (parts.length === 0) {
663 return "wpd-unnamed";
664 }
665 return "wpd-" + parts.filter((p) => p !== "").join("-");
666 }
667 function slugify(s) {
668 return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
669 }
670 function ensureAutoId(element) {
671 if (element.id) {
672 return element.id;
673 }
674 const id = computeAutoId(element);
675 element.id = id;
676 return id;
677 }
678 const styles = css`:host{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var( --desktop-mode-text,#1d2327 );cursor:pointer}label{display:inline-flex;align-items:center;gap:6px;cursor:pointer}input[ type='checkbox' ]{accent-color:var( --wp-admin-theme-color,#2271b1 );cursor:pointer}:host( [ disabled ] ){opacity:0.5;cursor:not-allowed}:host( [ disabled ] ) label,:host( [ disabled ] ) input[ type='checkbox' ]{cursor:not-allowed}`;
679 const _WpdCheckboxLabel = class _WpdCheckboxLabel extends Component {
680 render() {
681 const label = this.label || "";
682 const checked = this.checked !== null;
683 const disabled = this.disabled !== null;
684 return html`
685 <label>
686 <input
687 type="checkbox"
688 ?checked=${checked}
689 ?disabled=${disabled}
690 @change=${(e) => this._onChange(e)}
691 />
692 <span class="wpd-checkbox-label__text">${label}</span>
693 </label>
694 `;
695 }
696 _onChange(e) {
697 if (this.disabled !== null) {
698 return;
699 }
700 const next = e.target.checked;
701 if (next) {
702 this.setAttribute("checked", "");
703 } else {
704 this.removeAttribute("checked");
705 }
706 this.emit("wpd-checkbox-change", { checked: next });
707 }
708 };
709 _WpdCheckboxLabel.props = ["label", "checked", "disabled"];
710 _WpdCheckboxLabel.styles = [styles];
711 _WpdCheckboxLabel.help = {
712 title: "Checkbox label",
713 summary: "Opinionated label-row variant of <wpd-checkbox>: label text + checkbox in a single aligned row. Use when you want the shipped layout without any layout work.",
714 status: "stable",
715 since: "0.9.0",
716 props: [
717 {
718 name: "label",
719 type: "string",
720 description: "Visible label text, paired with the checkbox via a native <label>."
721 },
722 {
723 name: "checked",
724 type: "boolean attribute",
725 description: "Reflects and controls the checked state."
726 },
727 {
728 name: "disabled",
729 type: "boolean attribute",
730 description: "When present, the checkbox is not interactive and dimmed."
731 }
732 ],
733 events: [
734 {
735 name: "wpd-checkbox-change",
736 description: "Fires when the user toggles the checkbox.",
737 detail: "{ checked: boolean }"
738 }
739 ],
740 cssProps: [
741 { name: "--desktop-mode-text", description: "Label colour." }
742 ],
743 example: html`
744 <wpd-checkbox-label label="Reduce motion" checked></wpd-checkbox-label>
745 `
746 };
747 let WpdCheckboxLabel = _WpdCheckboxLabel;
748 defineComponent("wpd-checkbox-label", WpdCheckboxLabel);
749 const textareaStyles = css`:host{display:flex;flex-direction:column;gap:4px;font-size:13px;color:var( --desktop-mode-text,#1d2327 );min-width:0}:host( [ hidden ] ){display:none}.wpd-textarea__label{font-size:12px;color:var( --desktop-mode-muted,#646970 )}textarea{appearance:none;-webkit-appearance:none;display:block;width:100%;min-width:0;box-sizing:border-box;padding:8px 10px;background:var( --desktop-mode-window-bg,#fff );border:1px solid var( --desktop-mode-border,#dcdcde );border-radius:6px;font:inherit;font-size:13px;line-height:1.45;color:var( --desktop-mode-text,#1d2327 );resize:vertical;transition:border-color 0.12s ease,box-shadow 0.12s ease}textarea:hover{border-color:var( --desktop-mode-muted,#8c8f94 )}textarea:focus-visible{outline:none;border-color:var( --wp-admin-theme-color,#2271b1 );box-shadow:0 0 0 1px var( --wp-admin-theme-color,#2271b1 )}textarea:disabled{opacity:0.55;cursor:not-allowed;background:rgba( 0,0,0,0.03 )}textarea[ aria-invalid='true' ]{border-color:#d63638}textarea[ aria-invalid='true' ]:focus-visible{box-shadow:0 0 0 1px #d63638}:host( [ auto-grow ] ) textarea{resize:none;overflow:hidden}`;
750 const _WpdTextarea = class _WpdTextarea extends Component {
751 constructor() {
752 super(...arguments);
753 this._textareaEl = null;
754 }
755 connectedCallback() {
756 super.connectedCallback();
757 ensureAutoId(this);
758 }
759 render() {
760 const label = this._attr("label") || "";
761 const value = this._attr("value") ?? "";
762 const placeholder = this._attr("placeholder") || "";
763 const disabled = this._boolAttr("disabled");
764 const readonly = this._boolAttr("readonly");
765 const ariaLabel = this._attr("aria-label") || label;
766 const name = this._attr("name") || "";
767 const rows = Number(this._attr("rows")) || 3;
768 const maxLength = this._attr("maxlength");
769 const minLength = this._attr("minlength");
770 const invalid = this._boolAttr("invalid");
771 const hostId = this.id || "wpd-unnamed";
772 const fieldId = `${hostId}__field`;
773 return html`
774 ${label ? html`<label class="wpd-textarea__label" for=${fieldId}>${label}</label>` : html``}
775 <textarea
776 id=${fieldId}
777 part="textarea"
778 .value=${value}
779 placeholder=${placeholder}
780 ?disabled=${disabled}
781 ?readonly=${readonly}
782 rows=${rows}
783 maxlength=${maxLength ?? ""}
784 minlength=${minLength ?? ""}
785 name=${name}
786 aria-invalid=${invalid ? "true" : "false"}
787 aria-label=${ariaLabel || ""}
788 @input=${(e) => this._onInput(e)}
789 @change=${(e) => this._onChange(e)}
790 @keydown=${(e) => this._onKeyDown(e)}
791 ></textarea>
792 `;
793 }
794 _attr(name) {
795 return this.getAttribute(name);
796 }
797 _boolAttr(name) {
798 return this.getAttribute(name) !== null;
799 }
800 _onInput(e) {
801 const ta = e.target;
802 this._textareaEl = ta;
803 this.setAttribute("value", ta.value);
804 this.emit("wpd-input-change", { value: ta.value });
805 if (this._boolAttr("auto-grow")) {
806 this._autosize(ta);
807 }
808 }
809 _onChange(e) {
810 const ta = e.target;
811 this.emit("wpd-input-commit", { value: ta.value });
812 }
813 _onKeyDown(e) {
814 if (!this._boolAttr("submit-on-enter")) {
815 return;
816 }
817 if (e.key === "Enter" && !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey) {
818 e.preventDefault();
819 const ta = e.target;
820 this.emit("wpd-submit", { value: ta.value });
821 }
822 }
823 /**
824 * Grow the textarea height to fit content, capped at `max-rows`.
825 * Resets to scroll-height each input then clamps; cheap because
826 * the browser caches layout.
827 */
828 _autosize(ta) {
829 const maxRows = Number(this._attr("max-rows")) || 8;
830 const cs = window.getComputedStyle(ta);
831 const fontSize = parseFloat(cs.fontSize) || 13;
832 const lineHeightRaw = cs.lineHeight;
833 const lineHeight = lineHeightRaw === "normal" ? fontSize * 1.45 : parseFloat(lineHeightRaw) || fontSize * 1.45;
834 const paddingTop = parseFloat(cs.paddingTop) || 0;
835 const paddingBottom = parseFloat(cs.paddingBottom) || 0;
836 const max = lineHeight * maxRows + paddingTop + paddingBottom;
837 ta.style.height = "auto";
838 const next = Math.min(ta.scrollHeight, max);
839 ta.style.height = `${next}px`;
840 }
841 /** Public helper for callers that programmatically set `.value` and want autosize to re-run. */
842 refreshAutosize() {
843 if (this._textareaEl && this._boolAttr("auto-grow")) {
844 this._autosize(this._textareaEl);
845 }
846 }
847 /** Imperatively focus the underlying textarea. */
848 focusInput() {
849 const root = this.shadowRoot ?? this;
850 const ta = root.querySelector("textarea");
851 ta?.focus();
852 }
853 /** Imperatively clear the value. */
854 clear() {
855 this.setAttribute("value", "");
856 const root = this.shadowRoot ?? this;
857 const ta = root.querySelector("textarea");
858 if (ta) {
859 ta.value = "";
860 if (this._boolAttr("auto-grow")) {
861 this._autosize(ta);
862 }
863 }
864 }
865 };
866 _WpdTextarea.props = [
867 "label",
868 "value",
869 "placeholder",
870 "disabled",
871 "readonly",
872 "ariaLabel",
873 "name",
874 "rows",
875 "maxlength",
876 "minlength",
877 "invalid",
878 "autoGrow",
879 "maxRows",
880 "submitOnEnter"
881 ];
882 _WpdTextarea.styles = [textareaStyles];
883 _WpdTextarea.help = {
884 title: "Textarea",
885 summary: "Multi-line text input. Same event shape as wpd-text-field. Optional auto-grow up to max-rows; optional submit-on-enter (Enter sends, Shift+Enter newlines).",
886 status: "stable",
887 since: "0.6.0",
888 props: [
889 { name: "label", type: "string", description: "Visible label above the textarea." },
890 { name: "value", type: "string", description: "Current value; reflected two-way." },
891 { name: "placeholder", type: "string", description: "Native placeholder." },
892 { name: "disabled", type: "boolean attribute" },
893 { name: "readonly", type: "boolean attribute" },
894 { name: "aria-label", type: "string", description: "Accessible label when no visible label is rendered." },
895 { name: "name", type: "string", description: "Forwarded to native textarea for form submission." },
896 { name: "rows", type: "integer (string)", default: "3", description: "Initial visible row count." },
897 { name: "maxlength", type: "integer (string)" },
898 { name: "minlength", type: "integer (string)" },
899 { name: "invalid", type: "boolean attribute", description: "Sets aria-invalid + error styling." },
900 { name: "auto-grow", type: "boolean attribute", description: "Grows up to max-rows as the user types." },
901 { name: "max-rows", type: "integer (string)", default: "8" },
902 {
903 name: "submit-on-enter",
904 type: "boolean attribute",
905 description: "Enter fires wpd-submit; Shift+Enter inserts a newline."
906 }
907 ],
908 events: [
909 { name: "wpd-input-change", description: "Fires on every keystroke.", detail: "{ value: string }" },
910 { name: "wpd-input-commit", description: "Fires on blur / native change.", detail: "{ value: string }" },
911 {
912 name: "wpd-submit",
913 description: "Fires on Enter (without Shift) when submit-on-enter is set.",
914 detail: "{ value: string }"
915 }
916 ],
917 example: html`
918 <wpd-textarea label="Message" rows="3" auto-grow max-rows="8" submit-on-enter></wpd-textarea>
919 `
920 };
921 let WpdTextarea = _WpdTextarea;
922 defineComponent("wpd-textarea", WpdTextarea);
923 const TEXT_DOMAIN = "desktop-mode";
924 function i18n() {
925 return window.wp?.i18n;
926 }
927 function __(text, domain = TEXT_DOMAIN) {
928 return i18n()?.__(text, domain) ?? text;
929 }
930 const NOTE_COLORS = [
931 "butter",
932 "blush",
933 "sky",
934 "mint",
935 "lilac",
936 "peach"
937 ];
938 function normalizeNoteColor(color) {
939 return NOTE_COLORS.includes(color) ? color : NOTE_COLORS[0];
940 }
941 function nextNoteColor(color) {
942 const index = NOTE_COLORS.indexOf(
943 normalizeNoteColor(color)
944 );
945 return NOTE_COLORS[(index + 1) % NOTE_COLORS.length];
946 }
947 function hashNoteSeed(text) {
948 let hash = 2166136261;
949 for (let i = 0; i < text.length; i++) {
950 hash ^= text.charCodeAt(i);
951 hash = Math.imul(hash, 16777619) >>> 0;
952 }
953 const seed = hash >>> 1 || 1;
954 return seed;
955 }
956 const PIN_WIDTH = 56;
957 const PIN_HEIGHT = 52;
958 function pushpinUrl(pluginUrl) {
959 return `${pluginUrl.replace(/\/$/, "")}/assets/images/pushpin.svg`;
960 }
961 function buildPinImage(pluginUrl) {
962 const img = document.createElement("img");
963 img.src = pushpinUrl(pluginUrl);
964 img.alt = "";
965 img.width = PIN_WIDTH;
966 img.height = PIN_HEIGHT;
967 img.draggable = false;
968 img.className = "desktop-mode-pinned-note__pin-img";
969 return img;
970 }
971 const NONCE_HEADER = "X-WP-Nonce";
972 function injectRestNonce(input, init) {
973 const nonce = readRestNonce();
974 if (!nonce) {
975 return init;
976 }
977 const url = resolveUrl(input);
978 if (!url || !isSameOriginRestUrl(url)) {
979 return init;
980 }
981 const baseHeaders = init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
982 const headers = new Headers(baseHeaders ?? {});
983 if (headers.has(NONCE_HEADER)) {
984 return init;
985 }
986 headers.set(NONCE_HEADER, nonce);
987 return { ...init ?? {}, headers };
988 }
989 function readRestNonce() {
990 if (typeof window === "undefined") {
991 return void 0;
992 }
993 const cfg = window.desktopModeConfig;
994 const value = cfg?.restNonce;
995 return typeof value === "string" && value.length > 0 ? value : void 0;
996 }
997 function resolveUrl(input) {
998 try {
999 const base = typeof window !== "undefined" && window.location ? window.location.href : void 0;
1000 if (typeof input === "string") {
1001 return new URL(input, base);
1002 }
1003 if (input instanceof URL) {
1004 return input;
1005 }
1006 if (typeof Request !== "undefined" && input instanceof Request) {
1007 return new URL(input.url, base);
1008 }
1009 return null;
1010 } catch {
1011 return null;
1012 }
1013 }
1014 function isSameOriginRestUrl(url) {
1015 if (typeof window === "undefined" || !window.location || url.origin !== window.location.origin) {
1016 return false;
1017 }
1018 if (url.pathname.includes("/wp-json/")) {
1019 return true;
1020 }
1021 if (url.searchParams.has("rest_route")) {
1022 return true;
1023 }
1024 return false;
1025 }
1026 function trackedFetch(input, init, opts = {}) {
1027 const fn = window.wp?.desktop?.fetch;
1028 if (typeof fn === "function") {
1029 return fn(input, init, opts);
1030 }
1031 const finalInit = injectRestNonce(input, init);
1032 return fetch(input, finalInit);
1033 }
1034 let deps = null;
1035 function installNotesRestDeps(next) {
1036 deps = next;
1037 }
1038 function ensureDeps() {
1039 if (!deps) {
1040 throw new Error(
1041 "[desktop-mode] notes REST client called before installNotesRestDeps()."
1042 );
1043 }
1044 return deps;
1045 }
1046 function liveNonce(installed) {
1047 const cfg = window.desktopModeConfig;
1048 return typeof cfg?.restNonce === "string" && cfg.restNonce ? cfg.restNonce : installed;
1049 }
1050 class NotesConflictError extends Error {
1051 constructor(current) {
1052 super("Note was changed by another session.");
1053 this.status = 409;
1054 this.name = "NotesConflictError";
1055 this.current = current;
1056 }
1057 }
1058 async function call(path, init) {
1059 const { baseUrl, nonce } = ensureDeps();
1060 const url = baseUrl;
1061 const headers = new Headers(init.headers ?? {});
1062 headers.set("X-WP-Nonce", liveNonce(nonce));
1063 if (init.body && !headers.has("Content-Type")) {
1064 headers.set("Content-Type", "application/json");
1065 }
1066 const res = await trackedFetch(
1067 url,
1068 { ...init, headers, credentials: "same-origin" },
1069 { source: "desktop-mode/notes" }
1070 );
1071 const text = await res.text();
1072 let body = null;
1073 if (text) {
1074 try {
1075 body = JSON.parse(text);
1076 } catch {
1077 body = null;
1078 }
1079 }
1080 if (!res.ok) {
1081 if (res.status === 409) {
1082 const current = body?.data?.current;
1083 throw new NotesConflictError(current ?? null);
1084 }
1085 const err = body;
1086 throw new Error(
1087 `[desktop-mode] notes REST ${res.status}: ${err?.code ?? ""} ${err?.message ?? ""}`.trim()
1088 );
1089 }
1090 if (null === body) {
1091 throw new Error(
1092 `[desktop-mode] notes REST ${res.status}: empty or unparseable body.`
1093 );
1094 }
1095 return body;
1096 }
1097 function createNote(body) {
1098 return call("", {
1099 method: "POST",
1100 body: JSON.stringify(body)
1101 });
1102 }
1103 const NOTE_DRAFT_PAYLOAD_TYPE = "note-draft";
1104 const NOTE_CREATED_EVENT = "desktop-mode-note-created";
1105 const WIDGET_ID = "desktop-mode/notes";
1106 function readShellConfig() {
1107 return window.desktopModeConfig ?? {};
1108 }
1109 function getDragManager() {
1110 return window.wp?.desktop?.dragManager ?? null;
1111 }
1112 const mount = (container, ctx) => {
1113 let destroyed = false;
1114 const config = readShellConfig();
1115 const canCreate = Boolean(config.notesUrl);
1116 if (canCreate) {
1117 installNotesRestDeps({
1118 baseUrl: config.notesUrl,
1119 nonce: config.restNonce ?? ""
1120 });
1121 }
1122 let color = normalizeNoteColor(
1123 ctx.storage.get("color") ?? NOTE_COLORS[0]
1124 );
1125 let isPublic = ctx.storage.get("public") ?? false;
1126 let text = "";
1127 const root = document.createElement("div");
1128 root.className = "dm-notes-pad";
1129 const stack = document.createElement("div");
1130 stack.className = "dm-notes-pad__stack";
1131 const under2 = document.createElement("div");
1132 under2.className = "dm-notes-pad__under dm-notes-pad__under--2";
1133 const under1 = document.createElement("div");
1134 under1.className = "dm-notes-pad__under dm-notes-pad__under--1";
1135 const sheet = document.createElement("div");
1136 sheet.className = "dm-notes-pad__sheet";
1137 const peel = document.createElement("div");
1138 peel.className = "dm-notes-pad__peel";
1139 peel.setAttribute("aria-hidden", "true");
1140 const peelHint = document.createElement("span");
1141 peelHint.className = "dm-notes-pad__peel-hint";
1142 peelHint.textContent = __("Drag to pin", "desktop-mode");
1143 peel.appendChild(peelHint);
1144 const editor = document.createElement("wpd-textarea");
1145 editor.className = "dm-notes-pad__editor";
1146 editor.setAttribute("aria-label", __("New note", "desktop-mode"));
1147 editor.setAttribute("placeholder", __("Write a note…", "desktop-mode"));
1148 editor.setAttribute("rows", "5");
1149 editor.setAttribute("auto-grow", "");
1150 editor.setAttribute("max-rows", "8");
1151 const corner = document.createElement("button");
1152 corner.type = "button";
1153 corner.className = "dm-notes-pad__corner";
1154 sheet.append(peel, editor, corner);
1155 stack.append(under2, under1, sheet);
1156 const footer = document.createElement("div");
1157 footer.className = "dm-notes-pad__footer";
1158 const swatches = document.createElement("div");
1159 swatches.className = "dm-notes-pad__swatches";
1160 swatches.setAttribute("role", "radiogroup");
1161 swatches.setAttribute("aria-label", __("Paper color", "desktop-mode"));
1162 const swatchButtons = /* @__PURE__ */ new Map();
1163 for (const slug of NOTE_COLORS) {
1164 const dot = document.createElement("button");
1165 dot.type = "button";
1166 dot.className = "dm-notes-pad__swatch";
1167 dot.dataset.noteColor = slug;
1168 dot.setAttribute("role", "radio");
1169 dot.setAttribute("aria-label", slug);
1170 dot.addEventListener("click", () => setColor(slug));
1171 swatchButtons.set(slug, dot);
1172 swatches.appendChild(dot);
1173 }
1174 const publicToggle = document.createElement("wpd-checkbox-label");
1175 publicToggle.className = "dm-notes-pad__public";
1176 publicToggle.setAttribute(
1177 "label",
1178 __("Public — visible to other desktop users", "desktop-mode")
1179 );
1180 if (isPublic) {
1181 publicToggle.setAttribute("checked", "");
1182 }
1183 publicToggle.addEventListener("wpd-checkbox-change", (ev) => {
1184 isPublic = ev.detail.checked;
1185 ctx.storage.set("public", isPublic);
1186 });
1187 const pinButton = document.createElement("button");
1188 pinButton.type = "button";
1189 pinButton.className = "dm-notes-pad__pin-btn";
1190 pinButton.textContent = __("Pin to desktop", "desktop-mode");
1191 pinButton.title = __(
1192 "Pin the note without dragging (Ctrl+Enter)",
1193 "desktop-mode"
1194 );
1195 footer.append(swatches, publicToggle, pinButton);
1196 root.append(stack, footer);
1197 container.appendChild(root);
1198 function refreshColors() {
1199 const next1 = nextNoteColor(color);
1200 const next2 = nextNoteColor(next1);
1201 sheet.dataset.noteColor = color;
1202 stack.dataset.noteColor = color;
1203 under1.dataset.noteColor = next1;
1204 under2.dataset.noteColor = next2;
1205 corner.dataset.noteColor = next1;
1206 corner.setAttribute(
1207 "aria-label",
1208 `${__("Next paper color", "desktop-mode")}: ${next1}`
1209 );
1210 for (const [slug, dot] of swatchButtons) {
1211 dot.setAttribute(
1212 "aria-checked",
1213 slug === color ? "true" : "false"
1214 );
1215 dot.classList.toggle("is-selected", slug === color);
1216 }
1217 }
1218 function setColor(slug) {
1219 color = normalizeNoteColor(slug);
1220 ctx.storage.set("color", color);
1221 refreshColors();
1222 }
1223 const onCornerClick = () => setColor(nextNoteColor(color));
1224 corner.addEventListener("click", onCornerClick);
1225 refreshColors();
1226 const onInput = (ev) => {
1227 text = ev.detail.value;
1228 };
1229 editor.addEventListener("wpd-input-change", onInput);
1230 const onEditorKeydown = (ev) => {
1231 const kev = ev;
1232 if (kev.key === "Enter" && (kev.ctrlKey || kev.metaKey)) {
1233 kev.preventDefault();
1234 void pinWithoutDrag();
1235 }
1236 kev.stopPropagation();
1237 };
1238 ["keydown", "keypress", "keyup"].forEach(
1239 (name) => editor.addEventListener(name, (ev) => {
1240 if (name === "keydown") {
1241 onEditorKeydown(ev);
1242 } else {
1243 ev.stopPropagation();
1244 }
1245 })
1246 );
1247 const clearDraft = () => {
1248 text = "";
1249 editor.setAttribute("value", "");
1250 };
1251 const shakeSheet = () => {
1252 if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
1253 return;
1254 }
1255 sheet.animate?.(
1256 [
1257 { transform: "translateX(0)" },
1258 { transform: "translateX(-4px)" },
1259 { transform: "translateX(4px)" },
1260 { transform: "translateX(0)" }
1261 ],
1262 { duration: 200, easing: "ease-out" }
1263 );
1264 };
1265 const buildDraftGhost = () => {
1266 const width = 208;
1267 const ghostRoot = document.createElement("div");
1268 ghostRoot.className = "desktop-mode-pinned-note-ghost";
1269 ghostRoot.dataset.noteColor = color;
1270 ghostRoot.style.width = `${width}px`;
1271 const swing = document.createElement("div");
1272 swing.className = "desktop-mode-pinned-note-ghost__swing";
1273 swing.dataset.noteColor = color;
1274 const tipX = width / 2;
1275 const tipY = 10;
1276 swing.style.transformOrigin = `${tipX}px ${tipY}px`;
1277 const pin = document.createElement("span");
1278 pin.className = "desktop-mode-pinned-note__pin";
1279 pin.style.setProperty("--dm-pin-dx", "0px");
1280 pin.style.setProperty("--dm-pin-rot", "0deg");
1281 pin.appendChild(buildPinImage(ctx.pluginUrl));
1282 const paper = document.createElement("div");
1283 paper.className = "desktop-mode-pinned-note__paper desktop-mode-pinned-note-ghost__paper";
1284 const body = document.createElement("div");
1285 body.className = "desktop-mode-pinned-note__body";
1286 body.textContent = text;
1287 paper.appendChild(body);
1288 swing.append(pin, paper);
1289 ghostRoot.appendChild(swing);
1290 return { root: ghostRoot, tipX, tipY };
1291 };
1292 const onSheetPointerDown = (ev) => {
1293 if (destroyed || !canCreate) {
1294 return;
1295 }
1296 const target = ev.target;
1297 if (target?.closest("wpd-textarea, .dm-notes-pad__corner")) {
1298 return;
1299 }
1300 if (!text.trim()) {
1301 shakeSheet();
1302 return;
1303 }
1304 const dragManager = getDragManager();
1305 if (!dragManager) {
1306 return;
1307 }
1308 ev.preventDefault();
1309 sheet.ownerDocument.defaultView?.getSelection()?.removeAllRanges();
1310 const ghost = buildDraftGhost();
1311 const data = {
1312 text,
1313 color,
1314 isPublic
1315 };
1316 dragManager.start({
1317 payload: {
1318 type: NOTE_DRAFT_PAYLOAD_TYPE,
1319 source: sheet,
1320 data,
1321 ghost: {
1322 element: ghost.root,
1323 offsetX: ghost.tipX,
1324 offsetY: ghost.tipY,
1325 hint: {
1326 neutral: __("Drop on the desktop to pin", "desktop-mode"),
1327 accept: __("Pin here", "desktop-mode"),
1328 reject: __("Can’t pin here", "desktop-mode")
1329 }
1330 }
1331 },
1332 origin: ev,
1333 onClickOnly: () => editor.focusInput?.(),
1334 onCommit: () => {
1335 clearDraft();
1336 playTearOffPromotion();
1337 }
1338 // onCancel: the sheet reappears untouched (the manager
1339 // removes its source-dragging class) — the draft survives.
1340 });
1341 };
1342 sheet.addEventListener("pointerdown", onSheetPointerDown);
1343 const playTearOffPromotion = () => {
1344 if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
1345 return;
1346 }
1347 sheet.animate?.(
1348 [
1349 {
1350 transform: "translate(3px, 4px) rotate(1.1deg)",
1351 opacity: 0.9
1352 },
1353 {
1354 transform: "translate(-1px, -2px) rotate(-0.4deg)",
1355 offset: 0.7
1356 },
1357 { transform: "translate(0, 0) rotate(0deg)", opacity: 1 }
1358 ],
1359 { duration: 260, easing: "cubic-bezier(0.2, 0.7, 0.2, 1)" }
1360 );
1361 };
1362 async function pinWithoutDrag() {
1363 if (destroyed || !canCreate) {
1364 return;
1365 }
1366 if (!text.trim()) {
1367 shakeSheet();
1368 editor.focusInput?.();
1369 return;
1370 }
1371 pinButton.disabled = true;
1372 try {
1373 const slot = Math.floor(Date.now() / 1e3) % 5;
1374 const note = await createNote({
1375 text,
1376 color,
1377 x: 0.55 + slot * 0.04,
1378 y: 0.12 + slot * 0.05,
1379 public: isPublic,
1380 seed: hashNoteSeed(text)
1381 });
1382 if (destroyed) {
1383 return;
1384 }
1385 clearDraft();
1386 playTearOffPromotion();
1387 document.dispatchEvent(
1388 new CustomEvent(NOTE_CREATED_EVENT, { detail: { note } })
1389 );
1390 } catch (err) {
1391 console.error("[desktop-mode] note pad: create failed:", err);
1392 shakeSheet();
1393 const toast = window.wp?.desktop?.showToast;
1394 toast?.({
1395 message: __(
1396 "Could not pin the note. Please try again.",
1397 "desktop-mode"
1398 ),
1399 duration: 5e3
1400 });
1401 } finally {
1402 pinButton.disabled = false;
1403 }
1404 }
1405 const onPinButton = () => {
1406 void pinWithoutDrag();
1407 };
1408 pinButton.addEventListener("click", onPinButton);
1409 if (!canCreate) {
1410 root.classList.add("dm-notes-pad--unavailable");
1411 editor.setAttribute("disabled", "");
1412 pinButton.disabled = true;
1413 }
1414 return () => {
1415 destroyed = true;
1416 sheet.removeEventListener("pointerdown", onSheetPointerDown);
1417 corner.removeEventListener("click", onCornerClick);
1418 pinButton.removeEventListener("click", onPinButton);
1419 };
1420 };
1421 const w = window;
1422 w.desktopModeWidgets = w.desktopModeWidgets ?? {};
1423 w.desktopModeWidgets[WIDGET_ID] = mount;
1424 })();
1425