@threadlabs/looma 0.35.0 → 0.35.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/components/ui-reel/ui-reel.html +2 -1
- package/components/ui-tabs/ui-tabs.html +2 -3
- package/components/ui-tabs/ui-tabs.js +0 -9
- package/components/ui-tooltip/ui-tooltip.html +2 -1
- package/dist/index.js +1 -1
- package/docs/README.md +3 -1
- package/docs/card.html +7 -6
- package/docs/catalog-view.mjs +2 -1
- package/docs/catalog.json +10 -10
- package/docs/customizer-render.mjs +13 -11
- package/docs/customizer.js +7 -4
- package/docs/site.mjs +4 -3
- package/docs/theme.mjs +2 -0
- package/package.json +2 -2
- package/styles/ui-tabs.css +2 -3
- package/styles/ui-tooltip.css +2 -1
- package/tokens.css +1 -1
- package/vue/UiReel.vue +1 -1
- package/vue/UiTabs.vue +2 -3
- package/vue/UiTooltip.vue +2 -1
- package/vue/chunks/UiReel.js +2 -2
- package/vue/chunks/UiTabs.js +1 -1
- package/vue/chunks/UiTooltip.js +1 -1
- package/vue/components.css +6 -5
package/docs/README.md
CHANGED
|
@@ -17,7 +17,7 @@ pnpm exec looma-docs init --title "The Fellowship"
|
|
|
17
17
|
pnpm exec looma-docs dev
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Use the tool versions accepted by Looma's optional peer dependencies. Regular component imports do not load the documentation server. Vue and Tiptap remain optional; install them when your own authored documentation mounts the Vue editor.
|
|
20
|
+
Use the tool versions accepted by Looma's optional peer dependencies (Markupress 0.8.5 or newer on the supported 0.8 line). Regular component imports do not load the documentation server. Vue and Tiptap remain optional; install them when your own authored documentation mounts the Vue editor.
|
|
21
21
|
|
|
22
22
|
Initialization creates three project-owned files:
|
|
23
23
|
|
|
@@ -46,6 +46,8 @@ The generated values follow rules. Small and large control heights follow a rati
|
|
|
46
46
|
|
|
47
47
|
Choose a coordinated palette to change related paper, ink and action seeds together without changing geometry or motion. Palette seeds remain available for precise edits; the light/dark range previews and resolved values explain readability adjustments.
|
|
48
48
|
|
|
49
|
+
The customizer uses Starting point, Palette and Material as its controls sidebar, with documentation navigation left out of this workspace. Detailed type, size and motion controls, example selection and exports open when needed.
|
|
50
|
+
|
|
49
51
|
The main customizer offers Material and Palette presets. **Refine material & light** opens a focused view with a movable light source, real component samples and bounded surface controls. **Refine palette** opens the coordinated light/dark ranges and their four color seeds. Both views edit the same theme as the main customizer. Returning to the overview, navigating to a component, or reloading keeps the result.
|
|
50
52
|
|
|
51
53
|
A refined preset keeps its name with “customized” beside it. **Restore material preset** resets only material and light; **Restore palette preset** resets only the palette. These family choices are recorded in the recipe's optional `presets` mapping. The `values` mapping remains the complete source of the generated theme; a preset is a named combination of those values.
|
package/docs/card.html
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<link rel="component" href="@threadlabs/looma/components/ui-stack/ui-stack.html">
|
|
1
2
|
<link rel="component" href="@threadlabs/looma/components/ui-form-field/ui-form-field.html">
|
|
2
3
|
<link rel="component" href="@threadlabs/looma/components/ui-input/ui-input.html">
|
|
3
4
|
<link rel="component" href="@threadlabs/looma/components/ui-button/ui-button.html">
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
<div class="preview"><iframe $ref="frame" from:title="$title" from:src="$src" sandbox="allow-scripts" from:loading="$loading" width="100%" height="208"></iframe></div>
|
|
24
25
|
<section $if="$reference" class="example-code"><h4>Code</h4><slot name="code"></slot></section>
|
|
25
26
|
<div $if="$resizable" class="preview-width"> <ui-form-field><label slot="label">Preview width</label><span slot="help">Explore the example at a narrower width.</span><ui-input $ref="width" type="number" min="240" max="1600" value="720" aria-label="Preview width in pixels" size="sm"></ui-input></ui-form-field></div>
|
|
26
|
-
<div $if="not $reference" class="body">
|
|
27
|
+
<div $if="not $reference" class="body"><ui-stack gap="xs">
|
|
27
28
|
<div class="meta"><span>{$category}</span><code>{$nativeRoot}</code></div>
|
|
28
29
|
<h3><a from:href="$href">{$title}</a></h3>
|
|
29
30
|
<p class="description">{$description}</p>
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
<p class="example-caption" $if="$exampleTitle">{$exampleTitle}</p>
|
|
32
33
|
|
|
33
34
|
<footer><ui-button $if="$href" as="a" from:href="$href" variant="link" size="sm">View component</ui-button><ui-button $if="$removable" $ref="remove" variant="ghost" size="sm">Remove</ui-button><slot name="actions"></slot></footer>
|
|
34
|
-
</div>
|
|
35
|
+
</ui-stack></div>
|
|
35
36
|
</article>
|
|
36
37
|
<style>
|
|
37
38
|
/* The existing documentation catalog card, shared by the directory, reference and customizer. */
|
|
@@ -65,13 +66,13 @@
|
|
|
65
66
|
footer { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: var(--ui-space-2) !important; }
|
|
66
67
|
.preview-width { padding: var(--ui-space-4) !important; }
|
|
67
68
|
.body { padding: var(--ui-space-5) !important; }
|
|
68
|
-
.meta { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: var(--ui-space-2) !important; margin
|
|
69
|
+
.meta { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: var(--ui-space-2) !important; margin: 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-xs); font-weight: var(--ui-font-bold); letter-spacing: .09em; text-transform: uppercase; }
|
|
69
70
|
.meta code { padding: 0 !important; background: transparent; color: inherit; text-transform: none; }
|
|
70
71
|
h3 { margin: 0 !important; padding: 0 !important; font-size: var(--ui-font-size-lg); letter-spacing: -.035em; }
|
|
71
72
|
h3 a { color: var(--ui-text); text-decoration: none; }
|
|
72
|
-
.description { margin:
|
|
73
|
-
.owner { margin:
|
|
74
|
-
.example-caption { margin:
|
|
73
|
+
.description { margin: 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-sm); line-height: var(--ui-line-height); }
|
|
74
|
+
.owner { margin: 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-xs); overflow-wrap: anywhere; }
|
|
75
|
+
.example-caption { margin: 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-xs); }
|
|
75
76
|
@media (prefers-reduced-motion: reduce) { :host { transition: none; } :host:hover { transform: none; } }
|
|
76
77
|
</style>
|
|
77
78
|
</template>
|
package/docs/catalog-view.mjs
CHANGED
|
@@ -6,6 +6,7 @@ export function catalogResource(groups) {
|
|
|
6
6
|
<link rel="component" href="@threadlabs/looma/components/ui-input/ui-input.html">
|
|
7
7
|
<link rel="component" href="@threadlabs/looma/components/ui-select/ui-select.html">
|
|
8
8
|
<link rel="component" href="@threadlabs/looma/components/ui-cluster/ui-cluster.html">
|
|
9
|
+
<link rel="component" href="@threadlabs/looma/components/ui-stack/ui-stack.html">
|
|
9
10
|
<template component="looma-catalog" module="./catalog-view.js"><defs></defs>
|
|
10
|
-
<
|
|
11
|
+
<ui-stack gap="xl"><ui-cluster align="end"><ui-form-field><label slot="label" for="catalog-query">Find a component</label><ui-input id="catalog-query" $ref="query" type="search" placeholder="Name, tag or purpose"></ui-input></ui-form-field><ui-form-field><label slot="label" for="catalog-category">Category</label><ui-select id="catalog-category" $ref="category"><option value="">All categories</option>${groups.map(group => `<option value="${escape(group.id)}">${escape(group.label)}</option>`).join('')}</ui-select></ui-form-field></ui-cluster><p $ref="count" role="status" aria-live="polite"></p><slot></slot></ui-stack></template>`;
|
|
11
12
|
}
|
package/docs/catalog.json
CHANGED
|
@@ -443,7 +443,7 @@
|
|
|
443
443
|
],
|
|
444
444
|
"schemaVersion": 1,
|
|
445
445
|
"package": "@threadlabs/looma",
|
|
446
|
-
"version": "0.35.
|
|
446
|
+
"version": "0.35.1",
|
|
447
447
|
"groups": [
|
|
448
448
|
{
|
|
449
449
|
"id": "layout",
|
|
@@ -17970,7 +17970,7 @@
|
|
|
17970
17970
|
{
|
|
17971
17971
|
"name": "snap",
|
|
17972
17972
|
"property": "snap",
|
|
17973
|
-
"description": "Snaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel scrolls freely.",
|
|
17973
|
+
"description": "Snaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel scrolls freely. Wrap a component item in a plain element when snapping: the wrapper receives snap alignment while the component keeps ownership of its root styles.",
|
|
17974
17974
|
"type": "keyword",
|
|
17975
17975
|
"options": [
|
|
17976
17976
|
"start",
|
|
@@ -18003,7 +18003,7 @@
|
|
|
18003
18003
|
},
|
|
18004
18004
|
{
|
|
18005
18005
|
"name": "snap",
|
|
18006
|
-
"description": "Snaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel scrolls freely.",
|
|
18006
|
+
"description": "Snaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel scrolls freely. Wrap a component item in a plain element when snapping: the wrapper receives snap alignment while the component keeps ownership of its root styles.",
|
|
18007
18007
|
"type": "\"start\" | \"center\"",
|
|
18008
18008
|
"options": [
|
|
18009
18009
|
"start",
|
|
@@ -18031,8 +18031,8 @@
|
|
|
18031
18031
|
"id": "@threadlabs/looma/ui-reel/01-default",
|
|
18032
18032
|
"name": "01-default",
|
|
18033
18033
|
"title": "Default",
|
|
18034
|
-
"description": "
|
|
18035
|
-
"markup": "<ui-reel item-width=\"sm\" aria-label=\"
|
|
18034
|
+
"description": "Scroll through the office desks. Small fixed-width items overflow the preview and scroll freely.",
|
|
18035
|
+
"markup": "<ui-reel item-width=\"sm\" aria-label=\"Fellowship office desks\">\n <div><ui-card padding=\"sm\">Frodo</ui-card></div>\n <div><ui-card padding=\"sm\">Sam</ui-card></div>\n <div><ui-card padding=\"sm\">Sméagol</ui-card></div>\n <div><ui-card padding=\"sm\">Merry</ui-card></div>\n <div><ui-card padding=\"sm\">Pippin</ui-card></div>\n <div><ui-card padding=\"sm\">Gandalf</ui-card></div>\n <div><ui-card padding=\"sm\">Aragorn</ui-card></div>\n <div><ui-card padding=\"sm\">Legolas</ui-card></div>\n</ui-reel>",
|
|
18036
18036
|
"resizable": false,
|
|
18037
18037
|
"bindings": "",
|
|
18038
18038
|
"frameworks": {}
|
|
@@ -18041,8 +18041,8 @@
|
|
|
18041
18041
|
"id": "@threadlabs/looma/ui-reel/02-gap-item-width-and-snap",
|
|
18042
18042
|
"name": "02-gap-item-width-and-snap",
|
|
18043
18043
|
"title": "Gap, item width, and snap",
|
|
18044
|
-
"description": "
|
|
18045
|
-
"markup": "<ui-reel gap=\"m\" item-width=\"md\" snap=\"center\" aria-label=\"
|
|
18044
|
+
"description": "Scroll the office desks sideways: medium-width items snap to the center when you release.",
|
|
18045
|
+
"markup": "<ui-reel gap=\"m\" item-width=\"md\" snap=\"center\" aria-label=\"Fellowship office desks\">\n <div><ui-card padding=\"sm\">Frodo</ui-card></div>\n <div><ui-card padding=\"sm\">Sam</ui-card></div>\n <div><ui-card padding=\"sm\">Sméagol</ui-card></div>\n <div><ui-card padding=\"sm\">Merry</ui-card></div>\n <div><ui-card padding=\"sm\">Pippin</ui-card></div>\n <div><ui-card padding=\"sm\">Gandalf</ui-card></div>\n <div><ui-card padding=\"sm\">Aragorn</ui-card></div>\n <div><ui-card padding=\"sm\">Legolas</ui-card></div>\n</ui-reel>",
|
|
18046
18046
|
"resizable": false,
|
|
18047
18047
|
"bindings": "",
|
|
18048
18048
|
"frameworks": {}
|
|
@@ -19473,8 +19473,8 @@
|
|
|
19473
19473
|
"id": "@threadlabs/looma/ui-separator/01-default-horizontal",
|
|
19474
19474
|
"name": "01-default-horizontal",
|
|
19475
19475
|
"title": "Default horizontal",
|
|
19476
|
-
"description": "
|
|
19477
|
-
"markup": "<
|
|
19476
|
+
"description": "Stack supplies breathing room around a horizontal divider.",
|
|
19477
|
+
"markup": "<ui-stack gap=\"s\">\n <span>Above</span>\n <ui-separator> </ui-separator>\n <span>Below</span>\n</ui-stack>",
|
|
19478
19478
|
"resizable": false,
|
|
19479
19479
|
"bindings": "",
|
|
19480
19480
|
"frameworks": {}
|
|
@@ -19484,7 +19484,7 @@
|
|
|
19484
19484
|
"name": "02-orientation-vertical",
|
|
19485
19485
|
"title": "orientation=\"vertical\"",
|
|
19486
19486
|
"description": "Vertical orientation divides inline content; the parent supplies the row layout and height.",
|
|
19487
|
-
"markup": "<
|
|
19487
|
+
"markup": "<ui-cluster gap=\"s\">\n <span>Before</span>\n <ui-separator orientation=\"vertical\"> </ui-separator>\n <span>After</span>\n</ui-cluster>",
|
|
19488
19488
|
"resizable": false,
|
|
19489
19489
|
"bindings": "",
|
|
19490
19490
|
"frameworks": {}
|
|
@@ -32,13 +32,13 @@ export function customizerResource(site, input = defaultRecipe()) {
|
|
|
32
32
|
};
|
|
33
33
|
const paletteDetails = `<ui-cluster>${['light', 'dark'].map(mode => `<ui-card data-theme="${mode}" padding="sm"><ui-text variant="label">${mode === 'light' ? 'Light' : 'Dark'} range</ui-text><div class="palette-range" role="img" aria-label="${mode} canvas, content and action range"><span class="palette-canvas"></span><span class="palette-content"></span><span class="palette-action"></span></div><ui-text variant="caption">Fellowship office</ui-text></ui-card>`).join('')}</ui-cluster><p data-palette-adjustments role="status" aria-live="polite"></p><ui-disclosure summary="Resolved palette values"><p data-palette-adjustment-details></p></ui-disclosure>`;
|
|
34
34
|
const supplements = {
|
|
35
|
-
'Colour': `${renderPreset('palette', 'overview')}<
|
|
36
|
-
'Material': `${renderPreset('material', 'overview')}<
|
|
35
|
+
'Colour': `${renderPreset('palette', 'overview')}<ui-text variant="caption">Coordinated light and dark ranges.</ui-text><ui-button data-action="refine-palette">Refine palette</ui-button>`,
|
|
36
|
+
'Material': `${renderPreset('material', 'overview')}<ui-text variant="caption">Material and light, shared everywhere.</ui-text><ui-button data-action="refine-material">Refine material & light</ui-button>`,
|
|
37
37
|
'Size & spacing': `<ui-cluster><ui-button size="sm">Small</ui-button><ui-button>Medium</ui-button><ui-button size="lg">Large</ui-button></ui-cluster><ui-disclosure summary="How the scale grows"><ui-stack><p>Control size anchors one shared height. Size ratio separates small from large. Density and spacing curve change breathing room independently.</p>${themeFields.filter(item => ['scale-contrast','space-curve'].includes(item.id)).map(renderField).join('')}</ui-stack></ui-disclosure>`,
|
|
38
38
|
};
|
|
39
|
-
const families = [...new Set(themeFields.map(field => field.family))].map(family => `<ui-disclosure summary="${family}"
|
|
39
|
+
const families = [...new Set(themeFields.map(field => field.family))].filter(family => !['Colour', 'Material'].includes(family)).map(family => `<ui-disclosure summary="${family}"><ui-stack>${themeFields.filter(item => item.family === family && !['scale-contrast', 'space-curve'].includes(item.id)).map(renderField).join('')}${supplements[family] ?? ''}</ui-stack></ui-disclosure>`).join('');
|
|
40
40
|
const refinement = (family, content) => `<section data-refinement-view="${family}" hidden tabindex="-1" aria-label="${family === 'material' ? 'Material and light refinement' : 'Palette refinement'}"><ui-stack gap="xl"><ui-cluster justify="between"><ui-button data-action="back">Back to customization</ui-button><ui-button data-action="restore-${family}">Restore ${family} preset</ui-button></ui-cluster><ui-text variant="heading" as="h2">${family === 'material' ? 'Refine material & light' : 'Refine your palette'}</ui-text><p>These are advanced refinements of the same persistent theme. Every change applies throughout your documentation.</p>${renderPreset(family, 'refinement')}${content}</ui-stack></section>`;
|
|
41
|
-
const materialView = refinement('material', `<div class="refinement-layout"><ui-stack><ui-light-editor data-light-editor x="${recipe.values['light-x']}" y="${recipe.values['light-y']}" label="Theme light position"><ui-stack gap="
|
|
41
|
+
const materialView = refinement('material', `<div class="refinement-layout"><ui-stack><ui-light-editor data-light-editor x="${recipe.values['light-x']}" y="${recipe.values['light-y']}" label="Theme light position"><ui-stack gap="s"><ui-cluster><ui-button preset="primary">Join briefing</ui-button><ui-button preset="standard">Read memo</ui-button></ui-cluster><ui-input label="Office search" placeholder="Search the Fellowship office"></ui-input></ui-stack></ui-light-editor><ui-card variant="elevated" treatment="expressive"><ui-text variant="eyebrow">Fellowship office</ui-text><ui-text variant="heading" as="h3">Second breakfast briefing</ui-text><ui-text>Sam has prepared the agenda. Frodo has the maps, and Sméagol is taking minutes.</ui-text><ui-cluster><ui-button preset="primary">Join briefing</ui-button><ui-button preset="quiet">Read agenda</ui-button></ui-cluster></ui-card><ui-card variant="elevated"><ui-text variant="label">Standard elevated surface</ui-text><ui-text>Subtle lift stays independent from expressive treatment.</ui-text></ui-card></ui-stack><ui-stack><ui-text variant="label">Surface response</ui-text>${themeFields.filter(field => ['finish','edge-light','glow'].includes(field.id)).map(renderField).join('')}<ui-text variant="label">Light source</ui-text>${themeFields.filter(field => ['light-spread','light-strength'].includes(field.id)).map(renderField).join('')}<ui-disclosure summary="Depth, glass and separation"><ui-stack>${themeFields.filter(field => ['outer-depth','inner-depth','glassiness','surface-separation','translucency'].includes(field.id)).map(renderField).join('')}</ui-stack></ui-disclosure></ui-stack></div>`);
|
|
42
42
|
const paletteView = refinement('palette', `<div class="refinement-layout"><ui-stack>${paletteDetails}<ui-card><ui-text variant="eyebrow">Fellowship office</ui-text><ui-text variant="heading" as="h3">There and back by Friday</ui-text><ui-text>Choose a coordinated range, then refine action, paper and ink. Readable roles follow automatically in both modes.</ui-text><ui-cluster><ui-button preset="primary">Share route</ui-button><ui-button preset="standard">Save draft</ui-button></ui-cluster></ui-card></ui-stack><ui-stack>${themeFields.filter(field => field.family === 'Colour').map(renderField).join('')}</ui-stack></div>`);
|
|
43
43
|
const options = site.catalog.groups.map(group => `<optgroup label="${escape(group.label)}">${examples.filter(example => example.category === group.id).map(example => `<option value="${escape(example.id)}" data-description="${escape(example.category + ' · ' + example.tag)}" data-example="${escape(JSON.stringify(example))}" data-search="${escape(`${example.title} ${example.tag} ${example.description} ${example.exampleTitle}`.toLocaleLowerCase())}">${escape(example.title)} · ${escape(example.exampleTitle)}</option>`).join('')}</optgroup>`).join('');
|
|
44
44
|
const links = ['ui-stack','ui-grid','ui-cluster','ui-form-field','ui-input','ui-select','ui-color-picker','ui-button','ui-disclosure','ui-textarea','ui-number-input','ui-slider','ui-curve-editor','ui-card','ui-text','ui-light-editor','ui-combobox'].map(tag => `<link rel="component" href="@threadlabs/looma/components/${tag}/${tag}.html">`).join('\n');
|
|
@@ -46,19 +46,21 @@ export function customizerResource(site, input = defaultRecipe()) {
|
|
|
46
46
|
<state name="cards" type="list" value="${escape(JSON.stringify(initial))}"><prop type="object">${cardFields.map(name => `<prop name="${name}" required type="${name === 'resizable' ? 'boolean' : 'string'}"></prop>`).join('')}</prop></state>
|
|
47
47
|
</defs>
|
|
48
48
|
<div data-looma-customizer data-recipe="${escape(JSON.stringify(recipe))}" data-base="${escape(site.base)}" data-mode="${site.config.local ? 'local' : 'public'}" data-revision="${escape(site.config.revision ?? '')}">
|
|
49
|
-
<ui-stack gap="
|
|
50
|
-
<
|
|
49
|
+
<ui-stack gap="l"><ui-text>Choose a starting point, refine shared decisions, and see them on real documentation examples.</ui-text>
|
|
50
|
+
<ui-text variant="caption" role="status" aria-live="polite" data-message>Changes are remembered across the documentation.</ui-text>
|
|
51
51
|
${materialView}${paletteView}
|
|
52
52
|
<div class="customizer-layout" data-customizer-overview>
|
|
53
|
-
<aside><ui-stack><ui-form-field><label slot="label" for="theme-start">Starting point</label><ui-select id="theme-start" data-preset value="${startingPointFor(recipe)}"><option value="custom" disabled>Your theme</option>${Object.entries(startingPointLabels).map(([id,label]) => `<option value="${id}">${label}</option>`).join('')}</ui-select></ui-form-field>
|
|
54
|
-
${
|
|
55
|
-
<ui-
|
|
53
|
+
<aside aria-label="Theme controls"><ui-stack><ui-form-field><label slot="label" for="theme-start">Starting point</label><ui-select id="theme-start" data-preset value="${startingPointFor(recipe)}"><option value="custom" disabled>Your theme</option>${Object.entries(startingPointLabels).map(([id,label]) => `<option value="${id}">${label}</option>`).join('')}</ui-select></ui-form-field>
|
|
54
|
+
${supplements.Colour}${supplements.Material}
|
|
55
|
+
<ui-disclosure summary="Type, size & motion"><ui-stack>${families}</ui-stack></ui-disclosure>
|
|
56
|
+
<ui-button data-action="reset" variant="ghost">Reset changes</ui-button>
|
|
57
|
+
<ui-disclosure summary="Export & save"><ui-stack><ui-cluster><ui-button data-action="css" variant="solid">Download CSS</ui-button><ui-button data-action="recipe">Download recipe</ui-button><ui-button data-action="import">Import recipe</ui-button></ui-cluster>
|
|
56
58
|
<input type="file" accept="application/json,.json" hidden data-recipe-file>
|
|
57
59
|
<ui-button data-action="save" variant="solid" hidden>Save to repository</ui-button>
|
|
58
60
|
<ui-disclosure summary="Use your theme"><p>Every export includes light and dark. Leave data-theme off the page to follow system preference, or set it to light or dark on the page or a section. <a href="${escape(site.base)}design-system/">Installation and mode guidance</a></p></ui-disclosure>
|
|
59
|
-
<ui-disclosure summary="Theme CSS"><ui-textarea data-css readonly rows="12" aria-label="Generated theme CSS" value="${escape(themeCSS(recipe))}"></ui-textarea></ui-disclosure>
|
|
61
|
+
<ui-disclosure summary="Theme CSS"><ui-textarea data-css readonly rows="12" aria-label="Generated theme CSS" value="${escape(themeCSS(recipe))}"></ui-textarea></ui-disclosure></ui-stack></ui-disclosure>
|
|
60
62
|
</ui-stack></aside>
|
|
61
|
-
<section class="examples"><ui-stack><ui-form-field><label slot="label" for="example-query">
|
|
63
|
+
<section class="examples"><ui-stack><ui-form-field><label slot="label" for="example-query">Filter previews</label><ui-input id="example-query" type="search" data-example-query placeholder="Name, tag or purpose"></ui-input></ui-form-field><ui-text variant="caption" role="status" aria-live="polite" data-example-count></ui-text><ui-disclosure summary="Add an example"><ui-stack><ui-combobox id="example-choice" data-add-example label="Choose a component example" placeholder="Search buttons, fields, dialogs…" disclosure clearable>${options}</ui-combobox><ui-button data-action="add" disabled>Add to preview</ui-button></ui-stack></ui-disclosure>
|
|
62
64
|
<ui-grid columns="2" min="sm"><section $each="card of $cards" $key="$card.id"><looma-example-card ${cardFields.filter(name => name !== 'id').map(name => `from:${name.replace(/[A-Z]/g, letter => '-' + letter.toLowerCase())}="$card.${name}"`).join(' ')} from:identity="$card.id" loading="eager" removable></looma-example-card></section></ui-grid>
|
|
63
65
|
<noscript>Enable JavaScript to edit the theme. The component reference and example previews remain available.</noscript>
|
|
64
66
|
</ui-stack></section></div></ui-stack></div>
|
package/docs/customizer.js
CHANGED
|
@@ -71,10 +71,13 @@ export function connect(host) {
|
|
|
71
71
|
};
|
|
72
72
|
const filterExamples = () => {
|
|
73
73
|
const words = root.querySelector('[data-example-query]').value.toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
74
|
-
|
|
75
|
-
for (const card of root.querySelectorAll('[data-example-card]'))
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
let visible = 0;
|
|
75
|
+
for (const card of root.querySelectorAll('[data-example-card]')) {
|
|
76
|
+
const matches = words.every(word => card.textContent.toLocaleLowerCase().includes(word) || card.dataset.componentCard.includes(word));
|
|
77
|
+
card.closest('section').hidden = !matches;
|
|
78
|
+
if (matches) visible++;
|
|
79
|
+
}
|
|
80
|
+
root.querySelector('[data-example-count]').textContent = visible ? `${visible} example${visible === 1 ? '' : 's'} in preview` : 'No previews match. Clear the filter or add another example.';
|
|
78
81
|
componentRegistry.update(root.querySelector('[data-action="add"]'), { disabled: !selectedExample || host.state.cards.some(item => item.id === selectedExample) });
|
|
79
82
|
};
|
|
80
83
|
const refinedRecipe = values => {
|
package/docs/site.mjs
CHANGED
|
@@ -120,11 +120,12 @@ export async function prepareDocumentation({ root, config = {}, catalog }) {
|
|
|
120
120
|
}
|
|
121
121
|
const gallery = combined.groups.map(group => {
|
|
122
122
|
const components = combined.components.filter(c => c.group === group.id && !c.navigationParent);
|
|
123
|
-
|
|
123
|
+
// A standalone opening tag makes this a Markdown HTML block, keeping native card wrappers inside Grid.
|
|
124
|
+
return components.length ? `<section data-catalog-group="${escapeHtml(group.id)}">\n\n## ${escapeHtml(group.label)}\n\n<ui-grid columns="3" min="sm">\n${components.map(component => `<div data-category="${escapeHtml(component.group)}" data-catalog-search="${escapeHtml(`${component.tag} ${component.title} ${component.description}`)}">${cardMarkup(component.examples[0], base, component)}</div>`).join('\n')}\n</ui-grid>\n\n</section>` : '';
|
|
124
125
|
}).join('\n\n');
|
|
125
126
|
await write(path.join(content, 'components/index.md'), `---\ntitle: Components\n---\n\n# Components\n\nExplore the live examples that also power your theme customizer.\n\n<link rel="component" href="${relativeHref(path.join(content, 'components/index.md'), card)}">\n<link rel="component" href="@threadlabs/looma/components/ui-grid/ui-grid.html">\n<link rel="component" href="${relativeHref(path.join(content, 'components/index.md'), path.join(generated, 'catalog-view.html'))}">\n\n<looma-catalog>\n\n${gallery}\n\n</looma-catalog>\n`);
|
|
126
127
|
await write(path.join(content, 'index.md'), `---\ntitle: ${JSON.stringify(config.title ?? 'Your design system')}\n---\n\n# ${config.title ?? 'Your design system'}\n\nWeave your design system from Looma and your own HTML Next components.\n\n[Explore components](/components/) · [Customize your theme](/customize/)\n`);
|
|
127
|
-
if (combined.components.some(component => component.group === 'editor')) await write(path.join(content, 'editor.md'), `---\ntitle: Editor\n---\n\n# Editor\n\nWriting tools and editor components, separate from the general component reference.\n\n${combined.components.filter(component => component.group === 'editor' && !component.navigationParent).map(component => `- [${component.title}](${componentPath(component)})`).join('\n')}\n`);
|
|
128
|
+
if (combined.components.some(component => component.group === 'editor')) await write(path.join(content, 'editor.md'), `---\ntitle: Editor\nsidebar: false\n---\n\n# Editor\n\nWriting tools and editor components, separate from the general component reference.\n\n${combined.components.filter(component => component.group === 'editor' && !component.navigationParent).map(component => `- [${component.title}](${componentPath(component)})`).join('\n')}\n`);
|
|
128
129
|
await write(path.join(content, 'design-system.md'), await readFile(own('README.md'), 'utf8'));
|
|
129
130
|
if (config.content) {
|
|
130
131
|
const authored = path.resolve(root, config.content);
|
|
@@ -143,7 +144,7 @@ export async function prepareDocumentation({ root, config = {}, catalog }) {
|
|
|
143
144
|
const customizer = path.join(generated, 'customizer.html');
|
|
144
145
|
await write(customizer, customizerResource({ catalog: combined, config, base }, config.recipe));
|
|
145
146
|
const customizerPage = path.join(content, 'customize.md');
|
|
146
|
-
await write(customizerPage, `---\ntitle: Customize\n---\n\n# Weave your design system\n\n<link rel="component" href="${relativeHref(customizerPage, customizer)}">\n<looma-customizer></looma-customizer>\n`);
|
|
147
|
+
await write(customizerPage, `---\ntitle: Customize\nsidebar: false\noutline: false\nprevious: false\nnext: false\n---\n\n# Weave your design system\n\n<link rel="component" href="${relativeHref(customizerPage, customizer)}">\n<looma-customizer></looma-customizer>\n`);
|
|
147
148
|
await write(path.join(generated, 'catalog.json'), JSON.stringify(combined));
|
|
148
149
|
const redirects = {};
|
|
149
150
|
for (const component of combined.components) {
|
package/docs/theme.mjs
CHANGED
|
@@ -227,6 +227,8 @@ export function deriveTheme(input, { mode } = {}) {
|
|
|
227
227
|
tokens['--ui-selection-text'] = tokens['--ui-accent-subtle-text'];
|
|
228
228
|
tokens['--ui-selection-border'] = mix(tokens['--ui-accent'], tokens['--ui-surface-raised'], .2);
|
|
229
229
|
tokens['--ui-control-surface'] = tokens['--ui-surface-raised'];
|
|
230
|
+
// A hint is quieter than body copy; resolve it against its field, not every tinted content surface.
|
|
231
|
+
tokens['--ui-control-placeholder'] = readableColour(mix(tokens['--ui-text'], tokens['--ui-control-surface'], .5), [tokens['--ui-control-surface']], 4.5, ink);
|
|
230
232
|
tokens['--ui-control-border'] = readableColour(mix(tokens['--ui-text'], tokens['--ui-surface-raised'], .43), readingSurfaces, 3, ink);
|
|
231
233
|
tokens['--ui-control-border-hover'] = tokens['--ui-control-border'];
|
|
232
234
|
tokens['--ui-border-strong'] = tokens['--ui-control-border'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threadlabs/looma",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"@tiptap/pm": ">=2 <3",
|
|
128
128
|
"@tiptap/vue-3": ">=2 <3",
|
|
129
129
|
"vue": "^3.5.43",
|
|
130
|
-
"markupress": "^0.8.
|
|
130
|
+
"markupress": "^0.8.5",
|
|
131
131
|
"@nextwebwg/htmlkit": "1.0.0-alpha.69",
|
|
132
132
|
"vite": "^8.3.4"
|
|
133
133
|
},
|
package/styles/ui-tabs.css
CHANGED
|
@@ -59,10 +59,9 @@
|
|
|
59
59
|
max-inline-size: 100%;
|
|
60
60
|
/* Tabs that do not fit scroll sideways rather than wrapping or squeezing. */
|
|
61
61
|
overflow-x: auto;
|
|
62
|
-
/*
|
|
63
|
-
strip or the page vertically. */
|
|
62
|
+
/* Contain sideways overscroll without trapping vertical page scrolling. */
|
|
64
63
|
overflow-y: hidden;
|
|
65
|
-
overscroll-behavior: contain;
|
|
64
|
+
overscroll-behavior-inline: contain;
|
|
66
65
|
scrollbar-width: thin;
|
|
67
66
|
border-block-end: var(--ui-border-width) solid var(--ui-border-divider);
|
|
68
67
|
}
|
package/styles/ui-tooltip.css
CHANGED
|
@@ -204,7 +204,8 @@
|
|
|
204
204
|
border-inline-end-width: var(--ui-border-width);
|
|
205
205
|
border-block-end-width: var(--ui-border-width);
|
|
206
206
|
border-end-end-radius: var(--ui-border-width);
|
|
207
|
-
|
|
207
|
+
/* Stretch in screen axes after rotation so both arrow shoulders stay level. */
|
|
208
|
+
transform: scale(1.25, 0.85) rotate(45deg);
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
:scope:where([data-component~="ui-tooltip"])[data-ui-actual-placement="bottom"] .surface:where(:not([data-component], [data-slotted]))::after {
|
package/tokens.css
CHANGED
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
--ui-control-surface: var(--ui-surface);
|
|
252
252
|
--ui-control-surface-disabled: var(--ui-surface-muted);
|
|
253
253
|
--ui-control-border-hover: color-mix(in oklab, var(--ui-text) 50%, var(--ui-surface));
|
|
254
|
-
--ui-control-placeholder: var(--ui-text-
|
|
254
|
+
--ui-control-placeholder: color-mix(in oklab, var(--ui-text) 58%, var(--ui-control-surface));
|
|
255
255
|
--ui-control-focus: var(--ui-focus-ring);
|
|
256
256
|
/* Derived from the ring, not a near-white tint, so the halo shows on any surface. */
|
|
257
257
|
--ui-control-focus-halo: color-mix(in srgb, var(--ui-focus-ring) 24%, transparent);
|
package/vue/UiReel.vue
CHANGED
|
@@ -40,7 +40,7 @@ const propValidityContract = {
|
|
|
40
40
|
required: false,
|
|
41
41
|
target: { attribute: 'snap' },
|
|
42
42
|
description:
|
|
43
|
-
'Snaps scrolling to items: start comes to rest with an item\'s start edge at the reel\'s start, and center with an item centred in the reel. Unset, the reel\n scrolls freely.',
|
|
43
|
+
'Snaps scrolling to items: start comes to rest with an item\'s start edge at the reel\'s start, and center with an item centred in the reel. Unset, the reel\n scrolls freely. Wrap a component item in a plain element when snapping: the wrapper receives snap alignment while the component keeps ownership of its\n root styles.',
|
|
44
44
|
values: ['start', 'center'],
|
|
45
45
|
},
|
|
46
46
|
},
|
package/vue/UiTabs.vue
CHANGED
|
@@ -395,10 +395,9 @@ useComponentHost(() => import('../components/ui-tabs/ui-tabs.js'), {
|
|
|
395
395
|
max-inline-size: 100%;
|
|
396
396
|
/* Tabs that do not fit scroll sideways rather than wrapping or squeezing. */
|
|
397
397
|
overflow-x: auto;
|
|
398
|
-
/*
|
|
399
|
-
strip or the page vertically. */
|
|
398
|
+
/* Contain sideways overscroll without trapping vertical page scrolling. */
|
|
400
399
|
overflow-y: hidden;
|
|
401
|
-
overscroll-behavior: contain;
|
|
400
|
+
overscroll-behavior-inline: contain;
|
|
402
401
|
scrollbar-width: thin;
|
|
403
402
|
border-block-end: var(--ui-border-width) solid var(--ui-border-divider);
|
|
404
403
|
}
|
package/vue/UiTooltip.vue
CHANGED
|
@@ -580,7 +580,8 @@ useComponentHost(() => import('../components/ui-tooltip/ui-tooltip.js'), {
|
|
|
580
580
|
border-inline-end-width: var(--ui-border-width);
|
|
581
581
|
border-block-end-width: var(--ui-border-width);
|
|
582
582
|
border-end-end-radius: var(--ui-border-width);
|
|
583
|
-
|
|
583
|
+
/* Stretch in screen axes after rotation so both arrow shoulders stay level. */
|
|
584
|
+
transform: scale(1.25, 0.85) rotate(45deg);
|
|
584
585
|
}
|
|
585
586
|
|
|
586
587
|
:global(:scope[data-ui-actual-placement="bottom"] .surface:where([data-hn-ui-tooltip])::after) {
|
package/vue/chunks/UiReel.js
CHANGED
|
@@ -58,7 +58,7 @@ var UiReel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE_
|
|
|
58
58
|
},
|
|
59
59
|
required: false,
|
|
60
60
|
target: { attribute: "snap" },
|
|
61
|
-
description: "Snaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel\n scrolls freely.",
|
|
61
|
+
description: "Snaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel\n scrolls freely. Wrap a component item in a plain element when snapping: the wrapper receives snap alignment while the component keeps ownership of its\n root styles.",
|
|
62
62
|
values: ["start", "center"]
|
|
63
63
|
}
|
|
64
64
|
} };
|
|
@@ -128,6 +128,6 @@ var UiReel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE_
|
|
|
128
128
|
}]]);
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
-
}), [["__scopeId", "data-v-
|
|
131
|
+
}), [["__scopeId", "data-v-fbdba47c"]]);
|
|
132
132
|
//#endregion
|
|
133
133
|
export { UiReel_default as t };
|
package/vue/chunks/UiTabs.js
CHANGED
package/vue/chunks/UiTooltip.js
CHANGED
package/vue/components.css
CHANGED
|
@@ -2726,7 +2726,8 @@ input[type="range"] {
|
|
|
2726
2726
|
border-inline-end-width: var(--ui-border-width);
|
|
2727
2727
|
border-block-end-width: var(--ui-border-width);
|
|
2728
2728
|
border-end-end-radius: var(--ui-border-width);
|
|
2729
|
-
|
|
2729
|
+
/* Stretch in screen axes after rotation so both arrow shoulders stay level. */
|
|
2730
|
+
transform: scale(1.25, 0.85) rotate(45deg);
|
|
2730
2731
|
}
|
|
2731
2732
|
:scope[data-ui-actual-placement="bottom"] .surface:where([data-hn-ui-tooltip])::after {
|
|
2732
2733
|
inset-block-start: calc(
|
|
@@ -10934,10 +10935,9 @@ input:where([data-hn-ui-switch]),input:where([data-hn-ui-switch])::after {
|
|
|
10934
10935
|
max-inline-size: 100%;
|
|
10935
10936
|
/* Tabs that do not fit scroll sideways rather than wrapping or squeezing. */
|
|
10936
10937
|
overflow-x: auto;
|
|
10937
|
-
/*
|
|
10938
|
-
strip or the page vertically. */
|
|
10938
|
+
/* Contain sideways overscroll without trapping vertical page scrolling. */
|
|
10939
10939
|
overflow-y: hidden;
|
|
10940
|
-
overscroll-behavior: contain;
|
|
10940
|
+
overscroll-behavior-inline: contain;
|
|
10941
10941
|
scrollbar-width: thin;
|
|
10942
10942
|
border-block-end: var(--ui-border-width) solid var(--ui-border-divider);
|
|
10943
10943
|
}
|
|
@@ -16093,7 +16093,8 @@ html[data-ui-input-modality="touch"]
|
|
|
16093
16093
|
border-inline-end-width: var(--ui-border-width);
|
|
16094
16094
|
border-block-end-width: var(--ui-border-width);
|
|
16095
16095
|
border-end-end-radius: var(--ui-border-width);
|
|
16096
|
-
|
|
16096
|
+
/* Stretch in screen axes after rotation so both arrow shoulders stay level. */
|
|
16097
|
+
transform: scale(1.25, 0.85) rotate(45deg);
|
|
16097
16098
|
}
|
|
16098
16099
|
|
|
16099
16100
|
:scope:where([data-component~="ui-tooltip"])[data-ui-actual-placement="bottom"] .surface:where(:not([data-component], [data-slotted]))::after {
|