Skip to content

Commit 1863fce

Browse files
committed
fix(tools): knobs improvements
1 parent 62816db commit 1863fce

File tree

3 files changed

+131
-65
lines changed

3 files changed

+131
-65
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type {
2+
Attribute,
3+
ClassField,
4+
ClassMember,
5+
CustomElementDeclaration,
6+
CustomElementField,
7+
Declaration,
8+
Slot,
9+
} from 'custom-elements-manifest';
10+
11+
import type { PftElementKnobs } from '../pft-element-knobs.js';
12+
13+
export interface KnobInfo<E> {
14+
element: E;
15+
knobId: string;
16+
}
17+
18+
export interface AttributeKnobInfo<E> extends KnobInfo<E> {
19+
isBoolean: boolean;
20+
isEnum: boolean;
21+
isNullable: boolean;
22+
isNumber: boolean;
23+
isOptional: boolean;
24+
values: string[];
25+
}
26+
27+
export type ContentKnobInfo<E> = KnobInfo<E>;
28+
29+
export type KnobRenderer<T, E extends HTMLElement = HTMLElement> = (
30+
this: PftElementKnobs<E>,
31+
member: T,
32+
info:
33+
T extends Attribute ? AttributeKnobInfo<E>
34+
: T extends Slot[] ? ContentKnobInfo<E>
35+
: KnobInfo<E>,
36+
) => unknown;
37+
38+
export type AttributeRenderer<E extends HTMLElement> = KnobRenderer<Attribute, E>;
39+
export type ContentRenderer<E extends HTMLElement> = KnobRenderer<Slot[], E>;
40+
41+
export const isAttributelessProperty = (x: ClassMember): x is CustomElementField =>
42+
x.kind === 'field'
43+
&& !x.privacy
44+
&& !('attribute' in x);
45+
46+
export const isCheckable = (el: HTMLElement): el is HTMLElement & { checked: boolean } =>
47+
'checked' in el;
48+
49+
export const isValue = (el: HTMLElement): el is HTMLElement & { value: string } =>
50+
'value' in el;
51+
52+
export const isCustomElementDecl = (decl: Declaration): decl is CustomElementDeclaration =>
53+
'customElement' in decl;
54+
55+
export const dedent = (str: string): string => {
56+
const stripped = str.replace(/^\n/, '');
57+
const match = stripped.match(/^\s+/);
58+
return match ? stripped.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str;
59+
};
60+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#knobs,
2+
#element {
3+
padding: 1em;
4+
}
5+
6+
#attributes,
7+
dl#slot-descriptions {
8+
display: grid;
9+
gap: 8px;
10+
grid-template-columns: max-content auto;
11+
& dd {
12+
margin-inline-start: 0;
13+
}
14+
& > h2 {
15+
grid-column: -1/1;
16+
}
17+
}
18+
19+
#knob-html-content {
20+
display: block;
21+
height: 200px;
22+
resize: vertical;
23+
overflow: scroll;
24+
background-color: var(--pf-global--BackgroundColor--200, #f0f0f0);
25+
}

tools/pfe-tools/elements/pft-element-knobs.ts

Lines changed: 46 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,39 @@
11
import type {
22
Attribute,
33
CustomElementDeclaration,
4-
Declaration,
54
Package,
65
Slot,
76
} from 'custom-elements-manifest';
87

9-
import { LitElement, css, html, type PropertyValues } from 'lit';
8+
import type {
9+
AttributeRenderer,
10+
AttributeKnobInfo,
11+
ContentKnobInfo,
12+
ContentRenderer,
13+
} from './lib/knobs.js';
14+
15+
import { LitElement, html, type PropertyValues } from 'lit';
1016
import { customElement } from 'lit/decorators/custom-element.js';
1117
import { property } from 'lit/decorators/property.js';
1218
import { ifDefined } from 'lit/directives/if-defined.js';
1319

20+
import {
21+
isCustomElementDecl,
22+
isCheckable,
23+
isValue,
24+
isAttributelessProperty,
25+
dedent,
26+
} from './lib/knobs.js';
27+
1428
import 'zero-md';
1529

1630
import './pft-html-editor.js';
1731

18-
interface KnobInfo<E> {
19-
element: E;
20-
knobId: string;
21-
}
22-
23-
interface AttributeKnobInfo<E> extends KnobInfo<E> {
24-
isBoolean: boolean;
25-
isEnum: boolean;
26-
isNullable: boolean;
27-
isNumber: boolean;
28-
isOptional: boolean;
29-
values: string[];
30-
}
31-
32-
type ContentKnobInfo<E> = KnobInfo<E>;
33-
34-
type KnobRenderer<T, E extends HTMLElement = HTMLElement> = (
35-
this: PftElementKnobs<E>,
36-
member: T,
37-
info:
38-
T extends Attribute ? AttributeKnobInfo<E>
39-
: T extends Slot[] ? ContentKnobInfo<E>
40-
: KnobInfo<E>,
41-
) => unknown;
42-
43-
export type AttributeRenderer<E extends HTMLElement> = KnobRenderer<Attribute, E>;
44-
export type ContentRenderer<E extends HTMLElement> = KnobRenderer<Slot[], E>;
45-
46-
const isCheckable = (el: HTMLElement): el is HTMLElement & { checked: boolean } =>
47-
'checked' in el;
48-
49-
const isValue = (el: HTMLElement): el is HTMLElement & { value: string } =>
50-
'value' in el;
51-
52-
const isCustomElementDecl = (decl: Declaration): decl is CustomElementDeclaration =>
53-
'customElement' in decl;
32+
import style from './pft-element-knobs.css';
5433

5534
@customElement('pft-element-knobs')
5635
export class PftElementKnobs<T extends HTMLElement> extends LitElement {
57-
static styles = [
58-
css`
59-
#element {
60-
padding: 1em;
61-
}
62-
63-
#attributes,
64-
dl#slot-descriptions {
65-
display: grid;
66-
gap: 8px;
67-
grid-template-columns: max-content auto;
68-
& dd {
69-
margin-inline-start: 0;
70-
}
71-
& > h2 {
72-
grid-column: -1/1;
73-
}
74-
}
75-
`,
76-
];
36+
static styles = [style];
7737

7838
@property() tag?: string;
7939

@@ -119,7 +79,7 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
11979
try {
12080
this.manifest = JSON.parse(script.textContent ?? '');
12181
} catch {
122-
null;
82+
void 0;
12383
}
12484
}
12585
if (this.#template && this.tag) {
@@ -165,7 +125,7 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
165125
<template>
166126
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown-dark.min.css">
167127
</template>
168-
<script type="text/markdown">${x}</script>
128+
${this.#renderMarkdown(x)}
169129
</zero-md>`)}
170130
</pf-tooltip>
171131
${isBoolean ? html`
@@ -186,6 +146,12 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
186146
`;
187147
}
188148

149+
#renderMarkdown(md?: string) {
150+
return !md ? '' : html`
151+
<script type="text/markdown">${md}</script>
152+
`;
153+
}
154+
189155
#renderContent(slots: Slot[], info: ContentKnobInfo<T>) {
190156
// todo : change listener is inflexible
191157
return html`
@@ -199,19 +165,34 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
199165
<zero-md><script type="text/markdown">${x.description ?? ''}</script></zero-md>
200166
</dd>`)}
201167
</dl>
202-
<pft-html-editor id="${info.knobId}"
203-
@input="${this.#onKnobChangedContent}"
204-
.value="${info.element.innerHTML}"></pft-html-editor>
168+
<pf-code-block>
169+
<pft-html-editor id="${info.knobId}"
170+
@input="${this.#onKnobChangedContent}"
171+
.value="${dedent(info.element.innerHTML)}"></pft-html-editor>
172+
</pf-code-block>
205173
`;
206174
}
207175

208176
#renderKnobs() {
209177
const decl = this.#elementDecl;
210178
const { element, tag, manifest } = this;
211179
if (element && decl && tag && manifest) {
212-
const { attributes, slots } = decl;
180+
const {
181+
summary,
182+
description,
183+
attributes,
184+
members,
185+
slots,
186+
} = decl;
187+
188+
const properties = members?.filter(isAttributelessProperty) ?? [];
189+
213190
return html`
214-
<form @submit="${(e: Event) => e.preventDefault()}">
191+
<hr>
192+
<form id="knobs" @submit="${(e: Event) => e.preventDefault()}">
193+
<h2><code>&lt;${tag}&gt;</code></h2>
194+
<zero-md>${this.#renderMarkdown(summary)}</zero-md>
195+
<zero-md>${this.#renderMarkdown(description)}</zero-md>
215196
${!attributes ? '' : html`
216197
<section id="attributes"
217198
@change="${this.#onKnobChangeAttribute}"

0 commit comments

Comments
 (0)