Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ serialisierbare Website-Konfiguration

Der Astro-Adapter montiert ein browserseitiges Custom Element. Das Panel verwendet Shadow DOM, damit Website-CSS und Tool-CSS einander nicht beeinflussen.

## V1
## V1.1

V1 liefert:
V1.1 liefert:

- Range-, Select- und Toggle-Controls;
- Range-, Select- und Toggle-Controls; optionale Target/Page-Map-Zuordnung (`global`/`section`) und Token-/Local-Klassifikation; labeled Select-Optionen;
- stabile Changes-/JSON-/Agent-Brief-Exporte gegenüber `initialState` (reiner Design-State, ohne Panel-UI-State);
- benannte Variant Families mit Defaults;
- URL-persistierten State und kopierbare Permalinks;
- Reset, JSON-/TypeScript-Rezept, Copy und Download;
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## Installation

```bash
npm install --save-dev https://github.com/cray-com/foundation-devtools/releases/download/v0.1.1/cray-com-foundation-devtools-0.1.1.tgz
npm install --save-dev https://github.com/cray-com/foundation-devtools/releases/download/v0.2.0/cray-com-foundation-devtools-0.2.0.tgz
```

## Astro
Expand Down Expand Up @@ -39,7 +39,23 @@ Website-Markup deklariert Scopes mit `data-fd-scope`. Effects setzen ausschließ
<section data-fd-scope="grid"><article data-fd-scope="card"></article></section>
```

Ohne eigenen Effect werden Familien als `data-fd-variant-card="default"` auf dem gleichnamigen Scope markiert. Ein Family-Effect kann stattdessen ein vorhandenes Website-Attribut wie `data-card-variant` setzen. Ein Control-Effect mit `attribute: 'card-density'` setzt entsprechend `data-card-density`.
Ohne eigenen Effect werden Familien als `data-fd-variant-card="default"` auf dem gleichnamigen Scope markiert.

### Targets und Changes

Optional ordnet `targets` Controls und Families einer Seite bzw. einem Bereich zu. Sections werden im Markup mit `data-fd-target` registriert; ungültige Zuordnungen werden verworfen. `kind: 'global'` bleibt seitenweit, `kind: 'section'` scoped auf das registrierte Element:

```ts
const config = defineDevtoolsConfig({ project: 'site', targets: [
{ key: 'hero', label: 'Hero', kind: 'section' },
], families: [{ key: 'card', label: 'Card', target: 'hero', variants: [{ name: 'default' }] }], controls: [{
type: 'select', key: 'density', label: 'Density', classification: 'token',
options: [{ value: 'comfortable', label: 'Comfortable' }, 'compact'], default: 'comfortable',
target: 'hero', effect: { scope: 'card', attribute: 'density' },
}] });
```

`changes(config, state)` und `changesJson` liefern ausschließlich geänderte Werte gegenüber `initialState`; `agentBrief` erzeugt eine knappe Markdown-Zusammenfassung. Panel-Auswahl und Vergleichsmodus sind UI-State und werden nicht exportiert. Ein Family-Effect kann stattdessen ein vorhandenes Website-Attribut wie `data-card-variant` setzen. Ein Control-Effect mit `attribute: 'card-density'` setzt entsprechend `data-card-density`.

Website-CSS kann Tailwind über semantische Layer verwenden. Dynamische Reglerwerte bleiben CSS Custom Properties:

Expand All @@ -62,7 +78,7 @@ Website-CSS kann Tailwind über semantische Layer verwenden. Dynamische Reglerwe

## Öffentliche Schnittstelle

`DevtoolsConfig`, `Metadata`, `Family`, `Variant`, `Range`, `Select`, `Toggle`, `DevtoolsState` sowie `defineDevtoolsConfig`, `validateConfig`, `initialState`, `validateState`, `encodeState`, `decodeState`, `stateUrl`, `applyEffects`, `recipe` und `typescriptRecipe` sind serialisierbar bzw. strict typisiert. Ungültige URL-Werte werden verworfen (fail-closed).
`DevtoolsConfig`, `Metadata`, `Target`, `Family`, `Variant`, `Range`, `Select`, `SelectOption`, `Toggle`, `DevtoolsState` sowie `defineDevtoolsConfig`, `validateConfig`, `initialState`, `validateState`, `encodeState`, `decodeState`, `stateUrl`, `applyEffects`, `changes`, `changesJson`, `agentBrief`, `resetBaseline`, `recipe` und `typescriptRecipe` sind serialisierbar bzw. strict typisiert. Nicht-kanonische Alias-Exporte werden nicht angeboten. Ungültige URL-Werte werden verworfen (fail-closed).

Das Panel startet platzsparend eingeklappt. Es bietet Reset, Permalink-, JSON- und TypeScript-Copy sowie JSON-Download. Es ist vollständig ausblendbar und per Cmd/Ctrl+Shift+D wiederherstellbar beziehungsweise umschaltbar. Tastaturfokus und Reduced Motion werden berücksichtigt. URL-State verwendet nur den Parameter `fd` und erhält vorhandene Parameter.

Expand Down
59 changes: 52 additions & 7 deletions fixture/smoke.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,27 @@
.grid { display: grid; gap: var(--fd-gap, 1rem); }
[data-fd-scope="card"] { color: rebeccapurple; }
</style>
<main class="grid" data-fd-scope="grid"><article data-fd-scope="card">Card</article><article data-fd-scope="card">Card</article></main>
<main class="grid" data-fd-target="hero" data-fd-scope="grid"><article data-fd-scope="card">Card</article><article data-fd-scope="card">Card</article></main>
<aside data-fd-target="hero" data-fd-scope="grid">Second registered hero target</aside>
<section data-fd-target="notes">Notes</section>
<script type="module">
window.addEventListener('error', (event) => { document.title = `FD error ${event.message}`; });
import { defineDevtoolsElement } from '../dist/element.js';
import { applyEffects, initialState } from '../dist/core.js';

localStorage.clear();
const config = {
project: 'fixture',
families: [{ key: 'card', label: 'Card', effect: { scope: 'card', attribute: 'card-variant' }, variants: [
targets: [{ key: 'global', label: 'Global', kind: 'global' }, { key: 'hero', label: 'Hero', kind: 'section' }, { key: 'notes', label: 'Notes', kind: 'section' }],
families: [{ key: 'card', label: 'Card', target: 'hero', effect: { scope: 'card', attribute: 'card-variant' }, variants: [
{ name: 'one' }, { name: 'two', defaults: { gap: 3 } },
] }],
controls: [
{ type: 'range', key: 'gap', label: 'Gap', min: 1, max: 4, default: 2,
{ type: 'range', key: 'gap', label: 'Gap', min: 1, max: 4, default: 2, target: 'hero',
unit: 'rem', effect: { scope: 'grid', variable: '--fd-gap' } },
{ type: 'select', key: 'density', label: 'Density', options: ['compact', 'roomy'], default: 'compact',
{ type: 'select', key: 'density', label: 'Density', options: ['compact', 'roomy'], default: 'compact', target: 'global',
effect: { scope: 'card', attribute: 'card-density' } },
{ type: 'toggle', key: 'featured', label: 'Featured', default: false,
{ type: 'toggle', key: 'featured', label: 'Featured', default: false, target: 'global',
effect: { scope: 'card', attribute: 'featured' } },
],
};
Expand All @@ -45,9 +49,17 @@
toggle.dispatchEvent(new Event('change', { bubbles: true }));
const card = document.querySelector('[data-fd-scope="card"]');
const grid = document.querySelector('[data-fd-scope="grid"]');
const map = root.querySelector('.map');
const mapCounts = map && map.textContent.includes('All 4 · Δ4') && map.textContent.includes('Global 2 · Δ2') && map.textContent.includes('Hero 2 · Δ2') && map.textContent.includes('Notes 0 · Δ0');
const isolatedRoot = document.querySelectorAll('[data-fd-target="hero"]')[1];
isolatedRoot.style.removeProperty('--fd-gap');
const isolatedState = initialState(config); isolatedState.values.gap = 3;
applyEffects(config, isolatedState, isolatedRoot);
const rootScopedEffect = isolatedRoot.style.getPropertyValue('--fd-gap') === '3rem';
applyEffects(config, { families: { card: 'two' }, values: { gap: 4, density: 'roomy', featured: true } });
const passed = root && card.getAttribute('data-card-variant') === 'two' &&
card.getAttribute('data-card-density') === 'roomy' && card.getAttribute('data-featured') === 'true' &&
grid.style.getPropertyValue('--fd-gap') === '4rem' &&
grid.style.getPropertyValue('--fd-gap') === '4rem' && rootScopedEffect && Array.from(document.querySelectorAll('[data-fd-target="hero"]')).every((element) => element.style.getPropertyValue('--fd-gap') === '4rem') && mapCounts &&
new URL(location.href).searchParams.has('fd');
root.querySelector('[data-action="hide"]').click();
const hidden = root.querySelector('.panel').classList.contains('hidden');
Expand All @@ -56,10 +68,43 @@
root.querySelector('.panel').classList.contains('collapsed');
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'd', ctrlKey: true, shiftKey: true }));
const shortcutOpened = !root.querySelector('.panel').classList.contains('collapsed');
root.querySelector('[data-action="changes"]').click();
await new Promise((resolve) => setTimeout(resolve, 50));
const copiedChanges = ['Copied', 'Copy unavailable'].includes(root.querySelector('.status').textContent);
root.querySelector('[data-action="brief"]').click();
await new Promise((resolve) => setTimeout(resolve, 50));
const copiedBrief = ['Copied', 'Copy unavailable'].includes(root.querySelector('.status').textContent);
root.querySelector('[data-action="compare-original"]').click();
const originalApplied = grid.style.getPropertyValue('--fd-gap') === '2rem' && root.querySelector('[data-action="compare-original"]').getAttribute('aria-pressed') === 'true';
root.querySelector('[data-action="compare-modified"]').click();
const modifiedRestored = grid.style.getPropertyValue('--fd-gap') === '4rem' && root.querySelector('[data-action="compare-modified"]').getAttribute('aria-pressed') === 'true';

const notesButton = root.querySelector('[data-map-target="notes"]');
notesButton.click();
const emptyTarget = root.querySelector('.body').textContent.includes('Keine Controls für dieses Target.');
root.querySelector('[data-map-target="all"]').click();
root.querySelector('[data-action="reset-control"][data-control="gap"]').click();
const controlReset = grid.style.getPropertyValue('--fd-gap') === '2rem';
range.value = '4'; range.dispatchEvent(new Event('input', { bubbles: true }));

root.querySelector('[data-action="pick"]').click();
const hero = document.querySelector('[data-fd-target="hero"]');
hero.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }));
const marked = hero.style.outline !== '';
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
const cleaned = hero.style.outline === '' && !hero.hasAttribute('tabindex');
root.querySelector('[data-action="pick"]').click();
hero.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
const picked = root.querySelector('[data-map-target="hero"]').getAttribute('aria-pressed') === 'true' && hero.style.outline === '' && !hero.hasAttribute('tabindex');
root.querySelector('[data-action="reset-target"]').click();
const targetReset = grid.style.getPropertyValue('--fd-gap') === '2rem' && card.getAttribute('data-card-variant') === 'one';
root.querySelector('[data-action="reset"]').click();
const fullReset = card.getAttribute('data-card-density') === 'compact' && card.getAttribute('data-featured') === 'false' && root.querySelector('[data-map-target="all"] .count').textContent.endsWith('Δ0');

root.querySelector('[data-action="json"]').click();
range.focus();
await new Promise((resolve) => setTimeout(resolve, 50));
const feedback = ['Copied', 'Copy unavailable'].includes(root.querySelector('.status').textContent);
document.title = passed && initiallyCollapsed && opened && hidden && restoredCollapsed && shortcutOpened && feedback
document.title = passed && initiallyCollapsed && opened && hidden && restoredCollapsed && shortcutOpened && feedback && copiedChanges && copiedBrief && originalApplied && modifiedRestored && emptyTarget && controlReset && marked && cleaned && picked && targetReset && fullReset
? 'FD browser PASS' : 'FD browser FAIL';
</script>
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cray-com/foundation-devtools",
"version": "0.1.1",
"version": "0.2.0",
"description": "Dense, design-neutral development tooling for website variants",
"type": "module",
"sideEffects": false,
Expand Down
Loading
Loading