asljs-ui-debug 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +51 -0
- package/README.md +70 -2
- package/dist/component-tree.d.ts +25 -0
- package/dist/component-tree.js +91 -0
- package/dist/components-popup.d.ts +18 -0
- package/dist/components-popup.js +165 -0
- package/dist/cookie.d.ts +11 -0
- package/dist/cookie.js +20 -0
- package/dist/debug-menu.d.ts +25 -0
- package/dist/debug-menu.js +135 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +10 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.js +24 -0
- package/docs/Debug Menu.md +68 -0
- package/package.json +60 -4
package/LICENSE.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Permissive No-Derivatives License 1.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexandrite Software Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software (the "Software") to:
|
|
7
|
+
|
|
8
|
+
1. Use the Software for any purpose, including commercial purposes.
|
|
9
|
+
2. Copy and redistribute the Software, including as part of another application,
|
|
10
|
+
library, or package.
|
|
11
|
+
3. Modify the Software for private or internal use.
|
|
12
|
+
|
|
13
|
+
Subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
## 1. No Distribution of Modified Versions
|
|
16
|
+
|
|
17
|
+
Modified versions of the Software, or substantial portions thereof, must not be
|
|
18
|
+
distributed without prior written permission from the copyright holder.
|
|
19
|
+
|
|
20
|
+
Routine build transformations, including minification, transpilation, and
|
|
21
|
+
bundling, are permitted for distribution, provided they do not intentionally
|
|
22
|
+
alter the Software's functionality.
|
|
23
|
+
|
|
24
|
+
## 2. Licence and Copyright Preservation
|
|
25
|
+
|
|
26
|
+
All distributed copies of the Software must retain this copyright notice and
|
|
27
|
+
licence.
|
|
28
|
+
|
|
29
|
+
The Software itself, including copies incorporated into other applications,
|
|
30
|
+
libraries, or packages, remains subject to this licence.
|
|
31
|
+
|
|
32
|
+
The Software must not be sublicensed or redistributed under a different licence.
|
|
33
|
+
|
|
34
|
+
## 3. Use in Other Software
|
|
35
|
+
|
|
36
|
+
Applications and libraries that merely use, import, link to, or bundle the
|
|
37
|
+
Software are not considered modified versions of the Software and may be
|
|
38
|
+
distributed under any licence.
|
|
39
|
+
|
|
40
|
+
Such use, incorporation, or bundling does not grant permission to relicense the
|
|
41
|
+
Software or distribute modified versions of it.
|
|
42
|
+
|
|
43
|
+
### 4. No Warranty
|
|
44
|
+
|
|
45
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
46
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
47
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
48
|
+
|
|
49
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR
|
|
50
|
+
OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS
|
|
51
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,71 @@
|
|
|
1
|
-
#
|
|
1
|
+
# debug
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Part of [Alexandrite Software Library][#1] - a set of high-quality, performant
|
|
4
|
+
> JavaScript libraries and tools for everyday use.
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
`asljs-ui-debug` adds a debug menu to a page built with the ASLJS UI components.
|
|
9
|
+
A small `…` button in the corner of the page opens the menu; its Components item
|
|
10
|
+
opens a popup with the tree of the components on the page at that moment and the
|
|
11
|
+
values of their properties.
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
- The menu is off unless the `asljs-debug=1` cookie is set; importing the
|
|
16
|
+
package then adds it once the page has loaded.
|
|
17
|
+
- The tree lists each element with a registered model definition, or an `asljs-`
|
|
18
|
+
tag, and the properties its definition names.
|
|
19
|
+
- The menu and the popup render in their own shadow roots, so the page's styles
|
|
20
|
+
do not reach them.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install asljs-ui-debug
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
NPM package: [asljs-ui-debug][NPM]
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Import the package once, after the component packages the page uses:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import 'asljs-ui-button';
|
|
36
|
+
import 'asljs-ui-debug';
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Turn the menu on for the site, then reload the page:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { setDebugEnabled }
|
|
43
|
+
from 'asljs-ui-debug';
|
|
44
|
+
|
|
45
|
+
setDebugEnabled(true);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Setting the cookie by hand works the same, for example from the browser console:
|
|
49
|
+
`document.cookie = 'asljs-debug=1; path=/'`.
|
|
50
|
+
|
|
51
|
+
## Further reading
|
|
52
|
+
|
|
53
|
+
- [Debug Menu][DOC] - the cookie, the menu items and the component tree.
|
|
54
|
+
|
|
55
|
+
Questions and bugs: [asljs/issues][ISS].
|
|
56
|
+
|
|
57
|
+
## Related packages
|
|
58
|
+
|
|
59
|
+
- [ui][UI] - the shared theme, the component model registry and the other UI
|
|
60
|
+
packages.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT License. See [LICENSE][LIC] for details.
|
|
65
|
+
|
|
66
|
+
[#1]: https://github.com/AlexandriteSoftware/asljs
|
|
67
|
+
[DOC]: <docs/Debug Menu.md>
|
|
68
|
+
[ISS]: https://github.com/AlexandriteSoftware/asljs/issues
|
|
69
|
+
[LIC]: LICENSE.md
|
|
70
|
+
[NPM]: https://www.npmjs.com/package/asljs-ui-debug
|
|
71
|
+
[UI]: ../core/README.md
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ComponentModelPropertyType } from 'asljs-ui';
|
|
2
|
+
export interface ComponentPropertyNode {
|
|
3
|
+
name: string;
|
|
4
|
+
title: string;
|
|
5
|
+
type: ComponentModelPropertyType | 'unknown';
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ComponentNode {
|
|
9
|
+
element: Element;
|
|
10
|
+
tagName: string;
|
|
11
|
+
title: string;
|
|
12
|
+
properties: ComponentPropertyNode[];
|
|
13
|
+
children: ComponentNode[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The tree of the components under `root`: every element with a registered
|
|
17
|
+
* model definition or an `asljs-` tag, nested as they are nested in the DOM,
|
|
18
|
+
* open shadow roots included. Elements between two components are skipped,
|
|
19
|
+
* and the debug elements are left out.
|
|
20
|
+
*/
|
|
21
|
+
export declare function collectComponents(root: ParentNode): ComponentNode[];
|
|
22
|
+
/**
|
|
23
|
+
* A short, one-line text of `value` for the property list.
|
|
24
|
+
*/
|
|
25
|
+
export declare function describeValue(value: unknown): string;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getComponentModel } from 'asljs-ui';
|
|
2
|
+
const COMPONENT_TAG_PREFIX = 'asljs-';
|
|
3
|
+
const DEBUG_TAG_PREFIX = 'asljs-debug-';
|
|
4
|
+
const VALUE_LENGTH = 60;
|
|
5
|
+
/**
|
|
6
|
+
* The tree of the components under `root`: every element with a registered
|
|
7
|
+
* model definition or an `asljs-` tag, nested as they are nested in the DOM,
|
|
8
|
+
* open shadow roots included. Elements between two components are skipped,
|
|
9
|
+
* and the debug elements are left out.
|
|
10
|
+
*/
|
|
11
|
+
export function collectComponents(root) {
|
|
12
|
+
const nodes = [];
|
|
13
|
+
for (const child of childrenOf(root)) {
|
|
14
|
+
const tagName = child.tagName.toLowerCase();
|
|
15
|
+
if (tagName.startsWith(DEBUG_TAG_PREFIX)) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const definition = getComponentModel(tagName);
|
|
19
|
+
if (definition === null
|
|
20
|
+
&& !tagName.startsWith(COMPONENT_TAG_PREFIX)) {
|
|
21
|
+
nodes.push(...collectComponents(child));
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
nodes.push({ element: child,
|
|
25
|
+
tagName,
|
|
26
|
+
title: definition?.title ?? tagName,
|
|
27
|
+
properties: describeProperties(child, definition),
|
|
28
|
+
children: collectComponents(child) });
|
|
29
|
+
}
|
|
30
|
+
return nodes;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A short, one-line text of `value` for the property list.
|
|
34
|
+
*/
|
|
35
|
+
export function describeValue(value) {
|
|
36
|
+
if (value === null
|
|
37
|
+
|| value === undefined) {
|
|
38
|
+
return String(value);
|
|
39
|
+
}
|
|
40
|
+
if (typeof value
|
|
41
|
+
=== 'string') {
|
|
42
|
+
return JSON.stringify(shorten(value));
|
|
43
|
+
}
|
|
44
|
+
if (typeof value
|
|
45
|
+
=== 'function') {
|
|
46
|
+
return `ƒ ${value.name || 'anonymous'}()`;
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
return `Array(${value.length})`;
|
|
50
|
+
}
|
|
51
|
+
if (typeof value
|
|
52
|
+
=== 'object') {
|
|
53
|
+
const name = value.constructor?.name ?? 'Object';
|
|
54
|
+
const keys = Object.keys(value);
|
|
55
|
+
return keys.length === 0
|
|
56
|
+
? `${name} {}`
|
|
57
|
+
: `${name} { ${shorten(keys.join(', '))} }`;
|
|
58
|
+
}
|
|
59
|
+
return String(value);
|
|
60
|
+
}
|
|
61
|
+
function childrenOf(root) {
|
|
62
|
+
const children = [...root.children];
|
|
63
|
+
const shadowRoot = root.shadowRoot;
|
|
64
|
+
if (shadowRoot) {
|
|
65
|
+
children.push(...shadowRoot.children);
|
|
66
|
+
}
|
|
67
|
+
return children;
|
|
68
|
+
}
|
|
69
|
+
function describeProperties(element, definition) {
|
|
70
|
+
const values = element;
|
|
71
|
+
if (definition !== null) {
|
|
72
|
+
return definition.properties.map(property => ({ name: property.name,
|
|
73
|
+
title: property.title ?? property.name,
|
|
74
|
+
type: property.type,
|
|
75
|
+
value: describeValue(values[property.name]) }));
|
|
76
|
+
}
|
|
77
|
+
// Without a definition, a Lit element still lists its reactive properties.
|
|
78
|
+
const reactive = element.constructor
|
|
79
|
+
.elementProperties;
|
|
80
|
+
return [...reactive?.keys() ?? []]
|
|
81
|
+
.filter((key) => typeof key === 'string')
|
|
82
|
+
.map(name => ({ name,
|
|
83
|
+
title: name,
|
|
84
|
+
type: 'unknown',
|
|
85
|
+
value: describeValue(values[name]) }));
|
|
86
|
+
}
|
|
87
|
+
function shorten(text) {
|
|
88
|
+
return text.length > VALUE_LENGTH
|
|
89
|
+
? `${text.slice(0, VALUE_LENGTH - 1)}…`
|
|
90
|
+
: text;
|
|
91
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
+
import { type ComponentNode } from './component-tree.js';
|
|
3
|
+
/**
|
|
4
|
+
* A popup with the tree of the components under `root` and their properties.
|
|
5
|
+
* The tree is taken when the popup opens and again on Refresh; closing removes
|
|
6
|
+
* the popup from the page.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ComponentsPopup extends LitElement {
|
|
9
|
+
static styles: import("lit").CSSResult;
|
|
10
|
+
accessor root: ParentNode | null;
|
|
11
|
+
accessor components: ComponentNode[];
|
|
12
|
+
connectedCallback(): void;
|
|
13
|
+
/** Takes the tree of the components again. */
|
|
14
|
+
refresh(): void;
|
|
15
|
+
/** Removes the popup and dispatches `close`. */
|
|
16
|
+
close(): void;
|
|
17
|
+
render(): TemplateResult;
|
|
18
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
8
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
|
+
import { collectComponents } from './component-tree.js';
|
|
10
|
+
/**
|
|
11
|
+
* A popup with the tree of the components under `root` and their properties.
|
|
12
|
+
* The tree is taken when the popup opens and again on Refresh; closing removes
|
|
13
|
+
* the popup from the page.
|
|
14
|
+
*/
|
|
15
|
+
let ComponentsPopup = class ComponentsPopup extends LitElement {
|
|
16
|
+
static { this.styles = css `
|
|
17
|
+
:host {
|
|
18
|
+
position: fixed;
|
|
19
|
+
right: 1rem;
|
|
20
|
+
bottom: 3.5rem;
|
|
21
|
+
z-index: 2147483646;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
width: min(36rem, calc(100vw - 2rem));
|
|
25
|
+
max-height: min(32rem, calc(100vh - 5rem));
|
|
26
|
+
background: #fff;
|
|
27
|
+
color: #212529;
|
|
28
|
+
border: 1px solid #ced4da;
|
|
29
|
+
border-radius: 0.5rem;
|
|
30
|
+
box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 20%);
|
|
31
|
+
font: 13px/1.4 system-ui, sans-serif;
|
|
32
|
+
}
|
|
33
|
+
header {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: 0.5rem;
|
|
37
|
+
padding: 0.5rem 0.75rem;
|
|
38
|
+
border-bottom: 1px solid #dee2e6;
|
|
39
|
+
}
|
|
40
|
+
h2 {
|
|
41
|
+
flex: 1;
|
|
42
|
+
margin: 0;
|
|
43
|
+
font-size: 14px;
|
|
44
|
+
}
|
|
45
|
+
button {
|
|
46
|
+
font: inherit;
|
|
47
|
+
padding: 0.125rem 0.5rem;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
.tree {
|
|
51
|
+
overflow: auto;
|
|
52
|
+
padding: 0.5rem 0.75rem;
|
|
53
|
+
}
|
|
54
|
+
details {
|
|
55
|
+
margin-left: 1rem;
|
|
56
|
+
}
|
|
57
|
+
.tree > details {
|
|
58
|
+
margin-left: 0;
|
|
59
|
+
}
|
|
60
|
+
summary {
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
.tag {
|
|
64
|
+
font-family: ui-monospace, monospace;
|
|
65
|
+
color: #6f42c1;
|
|
66
|
+
}
|
|
67
|
+
.title {
|
|
68
|
+
color: #6c757d;
|
|
69
|
+
margin-left: 0.5rem;
|
|
70
|
+
}
|
|
71
|
+
table {
|
|
72
|
+
margin: 0.25rem 0 0.25rem 1rem;
|
|
73
|
+
border-collapse: collapse;
|
|
74
|
+
}
|
|
75
|
+
td {
|
|
76
|
+
padding: 0 0.75rem 0 0;
|
|
77
|
+
vertical-align: top;
|
|
78
|
+
}
|
|
79
|
+
.name {
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
}
|
|
82
|
+
.type {
|
|
83
|
+
color: #6c757d;
|
|
84
|
+
}
|
|
85
|
+
.value {
|
|
86
|
+
font-family: ui-monospace, monospace;
|
|
87
|
+
word-break: break-all;
|
|
88
|
+
}
|
|
89
|
+
.empty {
|
|
90
|
+
color: #6c757d;
|
|
91
|
+
}
|
|
92
|
+
`; }
|
|
93
|
+
#root_accessor_storage = null;
|
|
94
|
+
get root() { return this.#root_accessor_storage; }
|
|
95
|
+
set root(value) { this.#root_accessor_storage = value; }
|
|
96
|
+
#components_accessor_storage = [];
|
|
97
|
+
get components() { return this.#components_accessor_storage; }
|
|
98
|
+
set components(value) { this.#components_accessor_storage = value; }
|
|
99
|
+
connectedCallback() {
|
|
100
|
+
super.connectedCallback();
|
|
101
|
+
this.refresh();
|
|
102
|
+
}
|
|
103
|
+
/** Takes the tree of the components again. */
|
|
104
|
+
refresh() {
|
|
105
|
+
this.components =
|
|
106
|
+
collectComponents(this.root ?? this.ownerDocument.body);
|
|
107
|
+
}
|
|
108
|
+
/** Removes the popup and dispatches `close`. */
|
|
109
|
+
close() {
|
|
110
|
+
this.remove();
|
|
111
|
+
this.dispatchEvent(new Event('close'));
|
|
112
|
+
}
|
|
113
|
+
render() {
|
|
114
|
+
return html `
|
|
115
|
+
<header>
|
|
116
|
+
<h2>Components</h2>
|
|
117
|
+
<button type="button"
|
|
118
|
+
data-role="refresh"
|
|
119
|
+
@click=${() => this.refresh()}>Refresh</button>
|
|
120
|
+
<button type="button"
|
|
121
|
+
data-role="close"
|
|
122
|
+
aria-label="Close"
|
|
123
|
+
@click=${() => this.close()}>×</button>
|
|
124
|
+
</header>
|
|
125
|
+
<div class="tree">
|
|
126
|
+
${this.components.length === 0
|
|
127
|
+
? html `<p class="empty">No components on the page.</p>`
|
|
128
|
+
: this.components.map(node => renderNode(node))}
|
|
129
|
+
</div>
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
__decorate([
|
|
134
|
+
property({ attribute: false })
|
|
135
|
+
], ComponentsPopup.prototype, "root", null);
|
|
136
|
+
__decorate([
|
|
137
|
+
state()
|
|
138
|
+
], ComponentsPopup.prototype, "components", null);
|
|
139
|
+
ComponentsPopup = __decorate([
|
|
140
|
+
customElement('asljs-debug-components')
|
|
141
|
+
], ComponentsPopup);
|
|
142
|
+
export { ComponentsPopup };
|
|
143
|
+
function renderNode(node) {
|
|
144
|
+
const id = node.element.id === ''
|
|
145
|
+
? ''
|
|
146
|
+
: `#${node.element.id}`;
|
|
147
|
+
return html `
|
|
148
|
+
<details open>
|
|
149
|
+
<summary>
|
|
150
|
+
<span class="tag"><${node.tagName}${id}></span>${node.title === node.tagName
|
|
151
|
+
? nothing
|
|
152
|
+
: html `<span class="title">${node.title}</span>`}
|
|
153
|
+
</summary>
|
|
154
|
+
${node.properties.length === 0
|
|
155
|
+
? nothing
|
|
156
|
+
: html `<table>${node.properties.map(property => html `<tr>
|
|
157
|
+
<td class="name"
|
|
158
|
+
title=${property.title}>${property.name}</td>
|
|
159
|
+
<td class="type">${property.type}</td>
|
|
160
|
+
<td class="value">${property.value}</td>
|
|
161
|
+
</tr>`)}</table>`}
|
|
162
|
+
${node.children.map(child => renderNode(child))}
|
|
163
|
+
</details>
|
|
164
|
+
`;
|
|
165
|
+
}
|
package/dist/cookie.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const DEBUG_COOKIE_NAME = "asljs-debug";
|
|
2
|
+
/**
|
|
3
|
+
* Whether the cookie `asljs-debug=1` is among `cookie`, by default the
|
|
4
|
+
* cookies of the current document.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isDebugEnabled(cookie?: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Sets or removes the `asljs-debug` cookie of `document`, for the whole site.
|
|
9
|
+
* The change takes effect on the next page load.
|
|
10
|
+
*/
|
|
11
|
+
export declare function setDebugEnabled(enabled: boolean, document?: Document): void;
|
package/dist/cookie.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const DEBUG_COOKIE_NAME = 'asljs-debug';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the cookie `asljs-debug=1` is among `cookie`, by default the
|
|
4
|
+
* cookies of the current document.
|
|
5
|
+
*/
|
|
6
|
+
export function isDebugEnabled(cookie = globalThis.document?.cookie ?? '') {
|
|
7
|
+
return cookie
|
|
8
|
+
.split(';')
|
|
9
|
+
.some(entry => entry.trim() === `${DEBUG_COOKIE_NAME}=1`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sets or removes the `asljs-debug` cookie of `document`, for the whole site.
|
|
13
|
+
* The change takes effect on the next page load.
|
|
14
|
+
*/
|
|
15
|
+
export function setDebugEnabled(enabled, document = globalThis.document) {
|
|
16
|
+
document.cookie =
|
|
17
|
+
enabled
|
|
18
|
+
? `${DEBUG_COOKIE_NAME}=1; path=/; SameSite=Lax`
|
|
19
|
+
: `${DEBUG_COOKIE_NAME}=; path=/; max-age=0; SameSite=Lax`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
+
import './components-popup.js';
|
|
3
|
+
export interface DebugMenuItem {
|
|
4
|
+
label: string;
|
|
5
|
+
run(menu: DebugMenu): void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The item that opens the popup with the tree of the page's components, or
|
|
9
|
+
* closes it when it is open.
|
|
10
|
+
*/
|
|
11
|
+
export declare const COMPONENTS_MENU_ITEM: DebugMenuItem;
|
|
12
|
+
/**
|
|
13
|
+
* The item that removes the `asljs-debug` cookie and the menu.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DISABLE_MENU_ITEM: DebugMenuItem;
|
|
16
|
+
/**
|
|
17
|
+
* The `…` button in the corner of the page that opens the debug menu.
|
|
18
|
+
*/
|
|
19
|
+
export declare class DebugMenu extends LitElement {
|
|
20
|
+
#private;
|
|
21
|
+
static styles: import("lit").CSSResult;
|
|
22
|
+
accessor items: readonly DebugMenuItem[];
|
|
23
|
+
accessor expanded: boolean;
|
|
24
|
+
render(): TemplateResult;
|
|
25
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
8
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
|
+
import './components-popup.js';
|
|
10
|
+
import { setDebugEnabled } from './cookie.js';
|
|
11
|
+
/**
|
|
12
|
+
* The item that opens the popup with the tree of the page's components, or
|
|
13
|
+
* closes it when it is open.
|
|
14
|
+
*/
|
|
15
|
+
export const COMPONENTS_MENU_ITEM = Object.freeze({ label: 'Components',
|
|
16
|
+
run: toggleComponentsPopup });
|
|
17
|
+
/**
|
|
18
|
+
* The item that removes the `asljs-debug` cookie and the menu.
|
|
19
|
+
*/
|
|
20
|
+
export const DISABLE_MENU_ITEM = Object.freeze({ label: 'Disable debug menu',
|
|
21
|
+
run: disableDebugMenu });
|
|
22
|
+
/**
|
|
23
|
+
* The `…` button in the corner of the page that opens the debug menu.
|
|
24
|
+
*/
|
|
25
|
+
let DebugMenu = class DebugMenu extends LitElement {
|
|
26
|
+
static { this.styles = css `
|
|
27
|
+
:host {
|
|
28
|
+
position: fixed;
|
|
29
|
+
right: 1rem;
|
|
30
|
+
bottom: 1rem;
|
|
31
|
+
z-index: 2147483647;
|
|
32
|
+
font: 13px/1.4 system-ui, sans-serif;
|
|
33
|
+
}
|
|
34
|
+
.toggle {
|
|
35
|
+
width: 2rem;
|
|
36
|
+
height: 2rem;
|
|
37
|
+
border: 1px solid #ced4da;
|
|
38
|
+
border-radius: 50%;
|
|
39
|
+
background: #fff;
|
|
40
|
+
color: #212529;
|
|
41
|
+
box-shadow: 0 0.25rem 0.75rem rgb(0 0 0 / 15%);
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
font: inherit;
|
|
44
|
+
font-weight: 700;
|
|
45
|
+
opacity: 0.6;
|
|
46
|
+
}
|
|
47
|
+
.toggle:hover,
|
|
48
|
+
.toggle[aria-expanded="true"] {
|
|
49
|
+
opacity: 1;
|
|
50
|
+
}
|
|
51
|
+
ul {
|
|
52
|
+
position: absolute;
|
|
53
|
+
right: 0;
|
|
54
|
+
bottom: 2.5rem;
|
|
55
|
+
min-width: 12rem;
|
|
56
|
+
margin: 0;
|
|
57
|
+
padding: 0.25rem 0;
|
|
58
|
+
list-style: none;
|
|
59
|
+
background: #fff;
|
|
60
|
+
border: 1px solid #ced4da;
|
|
61
|
+
border-radius: 0.375rem;
|
|
62
|
+
box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 20%);
|
|
63
|
+
}
|
|
64
|
+
li button {
|
|
65
|
+
display: block;
|
|
66
|
+
width: 100%;
|
|
67
|
+
padding: 0.375rem 0.75rem;
|
|
68
|
+
border: 0;
|
|
69
|
+
background: none;
|
|
70
|
+
color: #212529;
|
|
71
|
+
text-align: left;
|
|
72
|
+
font: inherit;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
li button:hover {
|
|
76
|
+
background: #e9ecef;
|
|
77
|
+
}
|
|
78
|
+
`; }
|
|
79
|
+
#items_accessor_storage = [COMPONENTS_MENU_ITEM,
|
|
80
|
+
DISABLE_MENU_ITEM];
|
|
81
|
+
get items() { return this.#items_accessor_storage; }
|
|
82
|
+
set items(value) { this.#items_accessor_storage = value; }
|
|
83
|
+
#expanded_accessor_storage = false;
|
|
84
|
+
get expanded() { return this.#expanded_accessor_storage; }
|
|
85
|
+
set expanded(value) { this.#expanded_accessor_storage = value; }
|
|
86
|
+
render() {
|
|
87
|
+
return html `
|
|
88
|
+
<button type="button"
|
|
89
|
+
class="toggle"
|
|
90
|
+
aria-label="Debug menu"
|
|
91
|
+
aria-haspopup="menu"
|
|
92
|
+
aria-expanded=${this.expanded
|
|
93
|
+
? 'true'
|
|
94
|
+
: 'false'}
|
|
95
|
+
@click=${() => this.expanded = !this.expanded}>…</button>
|
|
96
|
+
${this.expanded
|
|
97
|
+
? html `<ul role="menu">${this.items.map(item => html `<li role="none">
|
|
98
|
+
<button type="button"
|
|
99
|
+
role="menuitem"
|
|
100
|
+
@click=${() => this.#run(item)}>${item.label}</button>
|
|
101
|
+
</li>`)}</ul>`
|
|
102
|
+
: nothing}
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
#run(item) {
|
|
106
|
+
this.expanded = false;
|
|
107
|
+
item.run(this);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
__decorate([
|
|
111
|
+
property({ attribute: false })
|
|
112
|
+
], DebugMenu.prototype, "items", null);
|
|
113
|
+
__decorate([
|
|
114
|
+
state()
|
|
115
|
+
], DebugMenu.prototype, "expanded", null);
|
|
116
|
+
DebugMenu = __decorate([
|
|
117
|
+
customElement('asljs-debug-menu')
|
|
118
|
+
], DebugMenu);
|
|
119
|
+
export { DebugMenu };
|
|
120
|
+
function toggleComponentsPopup(menu) {
|
|
121
|
+
const document = menu.ownerDocument;
|
|
122
|
+
const open = document.querySelector('asljs-debug-components');
|
|
123
|
+
if (open) {
|
|
124
|
+
open.remove();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
document.body.appendChild(document.createElement('asljs-debug-components'));
|
|
128
|
+
}
|
|
129
|
+
function disableDebugMenu(menu) {
|
|
130
|
+
setDebugEnabled(false, menu.ownerDocument);
|
|
131
|
+
menu.ownerDocument
|
|
132
|
+
.querySelector('asljs-debug-components')
|
|
133
|
+
?.remove();
|
|
134
|
+
menu.remove();
|
|
135
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { collectComponents, describeValue, type ComponentNode, type ComponentPropertyNode } from './component-tree.js';
|
|
2
|
+
export { ComponentsPopup } from './components-popup.js';
|
|
3
|
+
export { DEBUG_COOKIE_NAME, isDebugEnabled, setDebugEnabled } from './cookie.js';
|
|
4
|
+
export { COMPONENTS_MENU_ITEM, DebugMenu, DISABLE_MENU_ITEM, type DebugMenuItem } from './debug-menu.js';
|
|
5
|
+
export { initDebug } from './init.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { initDebug } from './init.js';
|
|
2
|
+
export { collectComponents, describeValue } from './component-tree.js';
|
|
3
|
+
export { ComponentsPopup } from './components-popup.js';
|
|
4
|
+
export { DEBUG_COOKIE_NAME, isDebugEnabled, setDebugEnabled } from './cookie.js';
|
|
5
|
+
export { COMPONENTS_MENU_ITEM, DebugMenu, DISABLE_MENU_ITEM } from './debug-menu.js';
|
|
6
|
+
export { initDebug } from './init.js';
|
|
7
|
+
// A page that imports the package gets the menu when the cookie is set.
|
|
8
|
+
if (globalThis.document) {
|
|
9
|
+
initDebug();
|
|
10
|
+
}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import './debug-menu.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adds `asljs-debug-menu` to the body of `document` when the `asljs-debug`
|
|
4
|
+
* cookie is set, once the document has loaded. Returns whether the menu is
|
|
5
|
+
* enabled; calling it again adds no second menu.
|
|
6
|
+
*/
|
|
7
|
+
export declare function initDebug(document?: Document): boolean;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isDebugEnabled } from './cookie.js';
|
|
2
|
+
import './debug-menu.js';
|
|
3
|
+
/**
|
|
4
|
+
* Adds `asljs-debug-menu` to the body of `document` when the `asljs-debug`
|
|
5
|
+
* cookie is set, once the document has loaded. Returns whether the menu is
|
|
6
|
+
* enabled; calling it again adds no second menu.
|
|
7
|
+
*/
|
|
8
|
+
export function initDebug(document = globalThis.document) {
|
|
9
|
+
if (!isDebugEnabled(document.cookie)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const addMenu = () => {
|
|
13
|
+
if (!document.querySelector('asljs-debug-menu')) {
|
|
14
|
+
document.body.appendChild(document.createElement('asljs-debug-menu'));
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
if (document.readyState === 'loading') {
|
|
18
|
+
document.addEventListener('DOMContentLoaded', addMenu, { once: true });
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
addMenu();
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Debug Menu
|
|
2
|
+
|
|
3
|
+
How the debug menu of `asljs-ui-debug` is turned on, what it holds, and how the
|
|
4
|
+
component tree is taken.
|
|
5
|
+
|
|
6
|
+
## Turning it on
|
|
7
|
+
|
|
8
|
+
The menu is controlled by the `asljs-debug` cookie:
|
|
9
|
+
|
|
10
|
+
- If the cookie is `asljs-debug=1`, then importing the package adds
|
|
11
|
+
`asljs-debug-menu` to the end of the body, once the document has loaded.
|
|
12
|
+
- If the cookie is absent or has any other value, then importing the package
|
|
13
|
+
adds nothing.
|
|
14
|
+
|
|
15
|
+
`initDebug(document?)` does the check and is what the import runs; calling it
|
|
16
|
+
again adds no second menu. `isDebugEnabled(cookie?)` reads the cookie, and
|
|
17
|
+
`setDebugEnabled(enabled, document?)` sets it for the whole site (`path=/`) or
|
|
18
|
+
removes it. A change takes effect on the next page load.
|
|
19
|
+
|
|
20
|
+
## The menu
|
|
21
|
+
|
|
22
|
+
`asljs-debug-menu` is a round `…` button fixed to the bottom right corner of the
|
|
23
|
+
page. Clicking it opens and closes the list of its `items`; choosing an item
|
|
24
|
+
closes the list and calls the item's `run(menu)`. The default items:
|
|
25
|
+
|
|
26
|
+
- `COMPONENTS_MENU_ITEM`, Components - opens `asljs-debug-components`, or
|
|
27
|
+
removes it when it is open.
|
|
28
|
+
- `DISABLE_MENU_ITEM`, Disable debug menu - removes the cookie, the popup and
|
|
29
|
+
the menu.
|
|
30
|
+
|
|
31
|
+
A page adds its own items by setting `items`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
menu.items =
|
|
35
|
+
[ ...menu.items,
|
|
36
|
+
{ label: 'Clear cache',
|
|
37
|
+
run: () => clearCache() } ];
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## The component tree
|
|
41
|
+
|
|
42
|
+
`asljs-debug-components` is a popup above the menu button. It takes the tree of
|
|
43
|
+
the components under its `root`, by default the body, when it is added to the
|
|
44
|
+
page and again on Refresh; the tree does not follow later changes on its own.
|
|
45
|
+
Closing it removes it and dispatches `close`.
|
|
46
|
+
|
|
47
|
+
`collectComponents(root)` builds the tree:
|
|
48
|
+
|
|
49
|
+
- An element is a component if `getComponentModel` of `asljs-ui` has a model
|
|
50
|
+
definition for its tag, or if its tag starts with `asljs-`.
|
|
51
|
+
- A component's children are the components inside it; the elements between two
|
|
52
|
+
components are skipped, and open shadow roots are walked as well.
|
|
53
|
+
- The `asljs-debug-` elements are left out.
|
|
54
|
+
|
|
55
|
+
Each component lists its properties:
|
|
56
|
+
|
|
57
|
+
- If it has a model definition, then the properties are the definition's, with
|
|
58
|
+
their title and type.
|
|
59
|
+
- If it has none but is a Lit element, then the properties are its reactive
|
|
60
|
+
properties, with the type `unknown`.
|
|
61
|
+
|
|
62
|
+
`describeValue(value)` gives the shown value: strings are quoted and cut at 60
|
|
63
|
+
characters, functions show their name, arrays their length, and objects their
|
|
64
|
+
constructor and keys.
|
|
65
|
+
|
|
66
|
+
A component package registers its definition with `defineComponentModel(tag,
|
|
67
|
+
definition)` of `asljs-ui` where it defines the element; every package of `ui/`
|
|
68
|
+
does.
|
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "asljs-ui-debug",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A debug menu for pages built with the ASLJS UI components: a tree of the components on the page and their properties.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"asljs": {
|
|
7
|
+
"type": "lib"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/**",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE.md",
|
|
21
|
+
"docs/**"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"flint": "toolkit flint",
|
|
25
|
+
"clean": "toolkit clean",
|
|
26
|
+
"build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
|
|
27
|
+
"build:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
|
|
28
|
+
"typecheck": "npm run codegen --if-present && tsc -p tsconfig.build.json --noEmit",
|
|
29
|
+
"guard:clean-git": "toolkit ensure-clean-working-directory",
|
|
30
|
+
"prepack": "npm run clean && npm run build:dist && npm run build && npm run test && npm run guard:clean-git",
|
|
31
|
+
"postpublish": "toolkit tag-release-revision",
|
|
32
|
+
"test": "node --test --enable-source-maps \"build/**/*.test.js\"",
|
|
33
|
+
"test:watch": "node --watch --test \"build/**/*.test.js\"",
|
|
34
|
+
"coverage": "npm run build && node --test --experimental-test-coverage --enable-source-maps --test-coverage-exclude=\"**/*.test.js\" \"build/**/*.test.js\"",
|
|
35
|
+
"release:patch": "toolkit release-patch",
|
|
36
|
+
"all": "npm run clean && npm run build:dist && npm run flint && npm run typecheck && npm run build && npm run test"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"web-components",
|
|
40
|
+
"lit",
|
|
41
|
+
"ui",
|
|
42
|
+
"debug"
|
|
43
|
+
],
|
|
44
|
+
"author": "\"Alex Netkachov\" <alex.netkachov@gmail.com>",
|
|
45
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
46
|
+
"homepage": "https://github.com/AlexandriteSoftware/asljs#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/AlexandriteSoftware/asljs/issues"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/AlexandriteSoftware/asljs.git"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"asljs-ui": "^0.1.1",
|
|
56
|
+
"lit": "^3.3.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"asljs-testing": "^0.1.1",
|
|
60
|
+
"asljs-toolkit": "^0.1.0"
|
|
61
|
+
}
|
|
62
|
+
}
|