@threadlabs/looma 0.33.1 → 0.35.0

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.
Files changed (215) hide show
  1. package/components/shared/overlay.js +8 -2
  2. package/components/shared/surface-material.js +21 -0
  3. package/components/ui-badge/ui-badge.html +11 -3
  4. package/components/ui-button/ui-button.html +92 -51
  5. package/components/ui-card/ui-card.html +50 -6
  6. package/components/ui-checkbox/ui-checkbox.html +4 -2
  7. package/components/ui-curve-editor/curve.js +10 -0
  8. package/components/ui-curve-editor/ui-curve-editor.html +181 -0
  9. package/components/ui-curve-editor/ui-curve-editor.js +51 -0
  10. package/components/ui-dialog/ui-dialog.html +64 -1
  11. package/components/ui-dialog/ui-dialog.js +9 -1
  12. package/components/ui-icon-button/ui-icon-button.html +97 -31
  13. package/components/ui-input/ui-input.html +2 -1
  14. package/components/ui-input-group/ui-input-group.html +4 -1
  15. package/components/ui-light-editor/ui-light-editor.html +45 -0
  16. package/components/ui-light-editor/ui-light-editor.js +53 -0
  17. package/components/ui-menu/ui-menu.html +62 -0
  18. package/components/ui-menu/ui-menu.js +1 -1
  19. package/components/ui-number-input/arithmetic.js +47 -0
  20. package/components/ui-number-input/ui-number-input.html +63 -0
  21. package/components/ui-number-input/ui-number-input.js +47 -0
  22. package/components/ui-popover/ui-popover.html +66 -1
  23. package/components/ui-popover/ui-popover.js +1 -1
  24. package/components/ui-select/ui-select.html +9 -7
  25. package/components/ui-slider/ui-slider.html +84 -0
  26. package/components/ui-slider/ui-slider.js +29 -0
  27. package/components/ui-switch/ui-switch.html +3 -0
  28. package/components/ui-tabs/ui-tabs.html +6 -1
  29. package/components/ui-text/ui-text.html +31 -6
  30. package/components/ui-textarea/ui-textarea.html +2 -1
  31. package/components/ui-tooltip/ui-tooltip.html +60 -0
  32. package/components/ui-tooltip/ui-tooltip.js +1 -1
  33. package/dist/index.js +47 -43
  34. package/docs/README.md +59 -5
  35. package/docs/card.html +16 -2
  36. package/docs/card.js +17 -2
  37. package/docs/catalog.json +1640 -108
  38. package/docs/customizer-render.mjs +50 -11
  39. package/docs/customizer.js +137 -24
  40. package/docs/documentation.css +9 -1
  41. package/docs/index.d.ts +8 -3
  42. package/docs/index.mjs +1 -1
  43. package/docs/preview.js +2 -0
  44. package/docs/render.mjs +16 -7
  45. package/docs/site.mjs +78 -27
  46. package/docs/theme-session.mjs +53 -0
  47. package/docs/theme.mjs +189 -18
  48. package/docs/value-editors.mjs +33 -0
  49. package/package.json +2 -2
  50. package/styles/ui-badge.css +11 -6
  51. package/styles/ui-button.css +74 -49
  52. package/styles/ui-card.css +51 -4
  53. package/styles/ui-checkbox.css +4 -2
  54. package/styles/ui-curve-editor.css +109 -0
  55. package/styles/ui-dialog.css +136 -0
  56. package/styles/ui-icon-button.css +85 -28
  57. package/styles/ui-input-group.css +4 -1
  58. package/styles/ui-input.css +2 -1
  59. package/styles/ui-light-editor.css +28 -0
  60. package/styles/ui-menu.css +74 -0
  61. package/styles/ui-number-input.css +34 -0
  62. package/styles/ui-popover.css +76 -0
  63. package/styles/ui-select.css +9 -7
  64. package/styles/ui-slider.css +53 -0
  65. package/styles/ui-switch.css +3 -0
  66. package/styles/ui-tabs.css +6 -1
  67. package/styles/ui-text.css +23 -1
  68. package/styles/ui-textarea.css +2 -1
  69. package/styles/ui-tooltip.css +72 -0
  70. package/theme-light.css +1 -0
  71. package/theme-system.css +69 -17
  72. package/tokens.css +12 -2
  73. package/vanilla/UiButton.d.ts +1 -0
  74. package/vanilla/UiButton.js +195 -130
  75. package/vanilla/UiCard.d.ts +1 -0
  76. package/vanilla/UiCard.js +12 -12
  77. package/vanilla/UiCurveEditor.d.ts +28 -0
  78. package/vanilla/UiCurveEditor.js +134 -0
  79. package/vanilla/UiIconButton.d.ts +2 -1
  80. package/vanilla/UiIconButton.js +58 -31
  81. package/vanilla/UiLightEditor.d.ts +29 -0
  82. package/vanilla/UiLightEditor.js +102 -0
  83. package/vanilla/UiNumberInput.d.ts +33 -0
  84. package/vanilla/UiNumberInput.js +86 -0
  85. package/vanilla/UiSlider.d.ts +33 -0
  86. package/vanilla/UiSlider.js +125 -0
  87. package/vanilla/UiText.d.ts +2 -2
  88. package/vanilla/UiText.js +45 -11
  89. package/vanilla/index.js +4 -0
  90. package/vue/UiBadge.vue +29 -4
  91. package/vue/UiButton.d.ts +6 -0
  92. package/vue/UiButton.vue +188 -60
  93. package/vue/UiCard.d.ts +6 -0
  94. package/vue/UiCard.vue +80 -6
  95. package/vue/UiCheckbox.vue +4 -2
  96. package/vue/UiCurveEditor.d.ts +53 -0
  97. package/vue/UiCurveEditor.js +2 -0
  98. package/vue/UiCurveEditor.vue +427 -0
  99. package/vue/UiDialog.vue +181 -1
  100. package/vue/UiIconButton.d.ts +8 -2
  101. package/vue/UiIconButton.vue +190 -45
  102. package/vue/UiInput.vue +2 -1
  103. package/vue/UiInputGroup.vue +4 -1
  104. package/vue/UiLightEditor.d.ts +75 -0
  105. package/vue/UiLightEditor.js +2 -0
  106. package/vue/UiLightEditor.vue +402 -0
  107. package/vue/UiMenu.vue +91 -0
  108. package/vue/UiNumberInput.d.ts +83 -0
  109. package/vue/UiNumberInput.js +2 -0
  110. package/vue/UiNumberInput.vue +399 -0
  111. package/vue/UiPopover.vue +94 -1
  112. package/vue/UiSelect.vue +16 -10
  113. package/vue/UiSlider.d.ts +83 -0
  114. package/vue/UiSlider.js +2 -0
  115. package/vue/UiSlider.vue +412 -0
  116. package/vue/UiSwitch.vue +3 -0
  117. package/vue/UiTabs.vue +6 -1
  118. package/vue/UiText.d.ts +7 -5
  119. package/vue/UiText.vue +104 -22
  120. package/vue/UiTextarea.vue +2 -1
  121. package/vue/UiTooltip.vue +89 -0
  122. package/vue/chunks/UiActionBar.js +1 -1
  123. package/vue/chunks/UiAffordanceScope.js +2 -2
  124. package/vue/chunks/UiAvatar.js +2 -2
  125. package/vue/chunks/UiAvatarGroup.js +2 -2
  126. package/vue/chunks/UiBadge.js +3 -3
  127. package/vue/chunks/UiBreadcrumbItem.js +2 -2
  128. package/vue/chunks/UiBreadcrumbs.js +2 -2
  129. package/vue/chunks/UiButton.js +94 -11
  130. package/vue/chunks/UiCallout.js +2 -2
  131. package/vue/chunks/UiCard.js +34 -5
  132. package/vue/chunks/UiCheckbox.js +3 -3
  133. package/vue/chunks/UiCluster.js +2 -2
  134. package/vue/chunks/UiColorPicker.js +2 -2
  135. package/vue/chunks/UiCombobox.js +2 -2
  136. package/vue/chunks/UiContainer.js +2 -2
  137. package/vue/chunks/UiContextMenu.js +2 -2
  138. package/vue/chunks/UiCover.js +2 -2
  139. package/vue/chunks/UiCurveEditor.js +413 -0
  140. package/vue/chunks/UiDescriptionItem.js +2 -2
  141. package/vue/chunks/UiDescriptionList.js +2 -2
  142. package/vue/chunks/UiDialog.js +4 -4
  143. package/vue/chunks/UiDisclosure.js +2 -2
  144. package/vue/chunks/UiEditable.js +2 -2
  145. package/vue/chunks/UiEditorBlockActions.js +2 -2
  146. package/vue/chunks/UiEditorChipEditor.js +2 -2
  147. package/vue/chunks/UiEditorCodeLanguage.js +2 -2
  148. package/vue/chunks/UiEditorFootnoteEditor.js +2 -2
  149. package/vue/chunks/UiEditorImageToolbar.js +3 -3
  150. package/vue/chunks/UiEditorInsertTableGrid.js +2 -2
  151. package/vue/chunks/UiEditorLinkCard.js +3 -3
  152. package/vue/chunks/UiEditorLinkForm.js +3 -3
  153. package/vue/chunks/UiEditorMentionMenu.js +2 -2
  154. package/vue/chunks/UiEditorMentionMenuItem.js +2 -2
  155. package/vue/chunks/UiEditorSlashMenu.js +2 -2
  156. package/vue/chunks/UiEditorSlashMenuGroup.js +2 -2
  157. package/vue/chunks/UiEditorSlashMenuItem.js +2 -2
  158. package/vue/chunks/UiEditorSurface.js +2 -2
  159. package/vue/chunks/UiEditorTableContextMenu.js +2 -2
  160. package/vue/chunks/UiEditorTableOverlay.js +2 -2
  161. package/vue/chunks/UiEditorTableToolbar.js +2 -2
  162. package/vue/chunks/UiEditorToolbar.js +2 -2
  163. package/vue/chunks/UiFloatingActionButton.js +2 -2
  164. package/vue/chunks/UiFormField.js +2 -2
  165. package/vue/chunks/UiGrid.js +2 -2
  166. package/vue/chunks/UiIcon.js +2 -2
  167. package/vue/chunks/UiIconButton.js +77 -11
  168. package/vue/chunks/UiImage.js +2 -2
  169. package/vue/chunks/UiInput.js +3 -3
  170. package/vue/chunks/UiInputGroup.js +3 -3
  171. package/vue/chunks/UiLightEditor.js +347 -0
  172. package/vue/chunks/UiList.js +2 -2
  173. package/vue/chunks/UiListItem.js +2 -2
  174. package/vue/chunks/UiListbox.js +2 -2
  175. package/vue/chunks/UiMenu.js +3 -3
  176. package/vue/chunks/UiMenuGroup.js +2 -2
  177. package/vue/chunks/UiMenuItem.js +2 -2
  178. package/vue/chunks/UiMeter.js +2 -2
  179. package/vue/chunks/UiNavItem.js +2 -2
  180. package/vue/chunks/UiNumberInput.js +403 -0
  181. package/vue/chunks/UiPageHeader.js +2 -2
  182. package/vue/chunks/UiPopover.js +4 -4
  183. package/vue/chunks/UiRadio.js +2 -2
  184. package/vue/chunks/UiRadioGroup.js +2 -2
  185. package/vue/chunks/UiReel.js +2 -2
  186. package/vue/chunks/UiScrollArea.js +2 -2
  187. package/vue/chunks/UiSearchResultRow.js +2 -2
  188. package/vue/chunks/UiSearchShell.js +2 -2
  189. package/vue/chunks/UiSection.js +2 -2
  190. package/vue/chunks/UiSelect.js +3 -3
  191. package/vue/chunks/UiSeparator.js +2 -2
  192. package/vue/chunks/UiSidebar.js +2 -2
  193. package/vue/chunks/UiSlider.js +396 -0
  194. package/vue/chunks/UiSpinner.js +2 -2
  195. package/vue/chunks/UiStack.js +2 -2
  196. package/vue/chunks/UiStatusMessage.js +2 -2
  197. package/vue/chunks/UiSwatch.js +2 -2
  198. package/vue/chunks/UiSwitch.js +3 -3
  199. package/vue/chunks/UiSwitcher.js +2 -2
  200. package/vue/chunks/UiTable.js +2 -2
  201. package/vue/chunks/UiTabs.js +3 -3
  202. package/vue/chunks/UiText.js +53 -12
  203. package/vue/chunks/UiTextarea.js +3 -3
  204. package/vue/chunks/UiToast.js +2 -2
  205. package/vue/chunks/UiToastRegion.js +2 -2
  206. package/vue/chunks/UiTooltip.js +3 -3
  207. package/vue/chunks/UiTopBar.js +1 -1
  208. package/vue/chunks/UiTree.js +2 -2
  209. package/vue/chunks/UiTreeItem.js +2 -2
  210. package/vue/chunks/_plugin-vue_export-helper.js +68 -2
  211. package/vue/chunks/props.js +24 -1
  212. package/vue/components.css +3391 -2139
  213. package/vue/editor/index.js +623 -211
  214. package/vue/index.d.ts +4 -0
  215. package/vue/index.js +6 -2
package/docs/README.md CHANGED
@@ -40,16 +40,68 @@ pnpm exec looma-docs preview
40
40
 
41
41
  ## Change the rule, keep the family consistent
42
42
 
43
- The authoring model has twenty shared decisions, grouped as Colour, Type, Size & spacing, Material and Motion. The controls expose shared intent: a control size, spaciousness, corners, inner and outer depth, and response pace.
43
+ The authoring model groups shared decisions as Colour, Type, Size & spacing, Material and Motion. The controls expose shared intent: a control size, spaciousness, corners, inner and outer depth, and response pace.
44
44
 
45
- The generated values follow rules. Small and large control heights follow a ratio around the shared control size. Spacing follows an independent power curve: linear growth at 1, quadratic at 2. Depth generates families of shadows; it does not add motion amplitude. Fields and buttons with the same size setting use the same box height, while content can still grow when it needs more room.
45
+ The generated values follow rules. Small and large control heights follow a ratio around the shared control size. Spacing follows an independent curve: linear at 1, increasingly curved toward 2, with stable endpoints so the largest gaps do not grow without bound. Depth generates families of shadows; it does not add motion amplitude. Fields and buttons with the same size setting use the same box height, while content can still grow when it needs more room.
46
+
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
+
49
+ 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
+
51
+ 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.
52
+
53
+ Number controls accept arithmetic such as `42px / 1.2` or `150ms * 2`, calculate on entry and show the result. Unit changes preserve the quantity. Sliders include exact entry, and curve handles support dragging and arrow keys alongside numeric coordinates.
46
54
 
47
55
  Lengths retain their CSS units, such as `rem`; times retain `ms` or `s`. The downloaded CSS includes the canonical shared recipe as well as your inputs. Import a downloaded JSON recipe to continue editing it. Keep hand-written CSS in a separate overrides file so regenerating the theme does not replace it.
48
56
 
49
- Changes remain a preview until saved. Starting points update all selected cards. Reset returns to the loaded recipe. Invalid input keeps the last valid preview and explains which decision needs attention.
57
+ Changes apply throughout the documentation and remain in this browser across navigation and reload until reset. Repository files change only when you save locally. Starting points update the shell and all selected cards. Reset returns to the repository baseline. Invalid input keeps the last valid preview and explains which decision needs attention.
58
+
59
+ Text offers body, display, heading, label, caption, eyebrow and code roles. Choose a heading's document level with `as` independently from its visual role. Button and IconButton keep appearance and tone independent, with four optional shortcuts: primary (solid accent), standard (solid neutral), quiet (ghost neutral) and destructive (solid danger). A named preset takes precedence over the separate choices; `preset="none"` preserves their existing defaults.
60
+
61
+ ## Describe material and light
62
+
63
+ Light position is relative to each surface: horizontal and vertical positions run from 0 to 1, with 0.5, 0 at top centre. Drag the source, use its arrow keys (Shift moves faster), or enter exact percentages. Home returns it to top centre. Spread runs from a concentrated spot to broad illumination; strength controls its intensity. The source influences reflections, illuminated edges and the direction of depth shadows throughout the theme.
64
+
65
+ Finish controls how strongly a material catches a reflection, from matte to polished. Edge light catches its boundary; glow spills softly beyond it. Glassiness and surface separation govern the decorative material around floating content, scaled by popup purpose and rendered size. The content plane stays opaque. Reflection peaks are bounded for readable text, and reduced transparency disables the ornamental reflection and glow as well as backdrop blur.
66
+
67
+ Card keeps surface structure and decorative treatment independent:
68
+
69
+ ```html
70
+ <ui-card variant="elevated">Standard subtle lift</ui-card>
71
+ <ui-card variant="elevated" treatment="expressive">Lift with the theme's material</ui-card>
72
+ <ui-card treatment="expressive">Material treatment without added lift</ui-card>
73
+ ```
74
+
75
+ `treatment="standard"` is the default. Expressive treatment follows the theme: a matte theme stays opaque, while Bookish can use a light-catching frame. Card tones change colors without changing corners or adding a stripe. Solid neutral actions use quiet mode-appropriate surface fills with readable text, instead of inverted text-color fills.
76
+
77
+ To add a sample to the preview, search **Choose a component example**, select a result, then choose **Add to preview**. Existing component cards still link to the full reference.
78
+
79
+ ## Use light, dark, or both
80
+
81
+ Every generated theme includes a coordinated light/dark pair. The type, geometry and motion decisions stay shared. Both palettes derive readable text, outlines, focus and action states from the same identity; the generator may adjust requested color seeds to maintain contrast.
82
+
83
+ Load the generated CSS after Looma's tokens and palette. Choose how it runs:
84
+
85
+ - **System preference:** leave `data-theme` off the document root. The CSS follows the operating system automatically.
86
+ - **Light only:** set `<html data-theme="light">`.
87
+ - **Dark only:** set `<html data-theme="dark">`.
88
+ - **User switch:** set the root attribute to `light` or `dark`, and remove it to return to system preference. Persist that choice in your application if desired.
89
+
90
+ Fixed-mode applications still receive both palettes. A mode boundary applies to its descendants, so a dark panel can contain a light subsection without changing the page or its siblings:
91
+
92
+ ```html
93
+ <section data-theme="dark">
94
+ <h2>After-hours Fellowship office</h2>
95
+ <div data-theme="light">Sam's daylight reading area</div>
96
+ </section>
97
+ ```
98
+
99
+ Use the public semantic color properties inside those regions. A floating surface moved elsewhere in the DOM must retain its originating mode boundary; Looma's isolated documentation previews receive their card's mode automatically.
50
100
 
51
101
  ## Add your components
52
102
 
103
+ Build your design-system components in HTML Next. Their definitions, description attributes, controllers and HTML examples are the source for the generated documentation and customizer cards. Use Looma’s public semantic properties to make your components respond to the same theme.
104
+
53
105
  Opt into source directories in `looma.docs.json`:
54
106
 
55
107
  ```json
@@ -64,7 +116,7 @@ Opt into source directories in `looma.docs.json`:
64
116
  }
65
117
  ```
66
118
 
67
- `content` and `overrides` are optional. Create files you configure before building. Markdown guidance supplements the generated reference; it cannot replace an API page. Relative component and asset dependencies keep their original project locations, and code examples remain literal.
119
+ `content` and `overrides` are optional. Create files you configure before building. Markdown guidance supplements the generated reference; it cannot replace an API page. Relative component and asset dependencies keep their original project locations, and code examples remain literal. Local web fonts referenced by override CSS are embedded in the documentation's theme stylesheet so isolated examples render the same fonts on static hosts. Load your chosen web fonts in the consuming application; downloaded theme CSS selects their font families.
68
120
 
69
121
  A resource can use any valid HTML Next component tag:
70
122
 
@@ -101,7 +153,9 @@ description: Sam has finished the morning checklist.
101
153
 
102
154
  The example appears in both the reference and the customizer's selection list. Optional `.behavior.js` or `.behavior.ts` companions export a default setup function receiving the example root; return a cleanup function when needed. Add `preview: resizable` to expose the shared card's width control. Framework-specific `.snippet.html` and `.vue` companions provide literal source views.
103
155
 
104
- Discovery reads definitions and descriptions without executing their controllers. New resources under a selected root appear after the local server rebuilds. External components keep their own contracts and supported token hooks; discovery does not invent a Vue adapter or make arbitrary components respond to Looma's tokens.
156
+ Discovery reads definitions and descriptions without executing their controllers. New resources under a selected root appear after the local server rebuilds. External components keep their own contracts and supported token hooks; discovery does not make arbitrary components respond to Looma's tokens.
157
+
158
+ The planned HTML Next-only distribution will use the HTML Next Vite plugin or converter for Vue consumption. Author components and documentation examples in HTML Next; framework conversion belongs in the consuming application. The current package still includes Vue exports while that packaging migration is pending.
105
159
 
106
160
  ## Combine installed catalogs
107
161
 
package/docs/card.html CHANGED
@@ -7,6 +7,7 @@
7
7
  <prop name="title" type="string" default="Example">The documented component name.</prop>
8
8
  <prop name="description" type="string" default="">The component's authored description.</prop>
9
9
  <prop name="exampleTitle" type="string" default="">The selected authored example.</prop>
10
+ <prop name="reference" type="boolean" default="false">Use the spacious full-reference example presentation.</prop>
10
11
  <prop name="src" type="string" default="">The compiled isolated example page.</prop>
11
12
  <prop name="resizable" type="boolean" default="false">Expose a width control for responsive examples.</prop>
12
13
  <prop name="category" type="string" default="forms">The shared category identity.</prop>
@@ -18,14 +19,17 @@
18
19
  <prop name="loading" type="keyword" values="lazy, eager" default="lazy">Load visible catalog cards lazily; selected customizer cards eagerly.</prop>
19
20
  </defs>
20
21
  <article from:data-example-card="$identity" from:data-component-card="$tag" from:data-category="$category">
22
+ <header $if="$reference" class="example-heading"><slot name="heading"><h3>{$exampleTitle}</h3><p>{$description}</p></slot></header>
21
23
  <div class="preview"><iframe $ref="frame" from:title="$title" from:src="$src" sandbox="allow-scripts" from:loading="$loading" width="100%" height="208"></iframe></div>
22
- <div class="body">
24
+ <section $if="$reference" class="example-code"><h4>Code</h4><slot name="code"></slot></section>
25
+ <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">
23
27
  <div class="meta"><span>{$category}</span><code>{$nativeRoot}</code></div>
24
28
  <h3><a from:href="$href">{$title}</a></h3>
25
29
  <p class="description">{$description}</p>
26
30
  <p class="owner" $if="$packageName">{$packageName}</p>
27
31
  <p class="example-caption" $if="$exampleTitle">{$exampleTitle}</p>
28
- <ui-form-field $if="$resizable"><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>
32
+
29
33
  <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>
30
34
  </div>
31
35
  </article>
@@ -46,10 +50,20 @@
46
50
  :host([category="layout"]) { --_card-accent: var(--ui-info); }
47
51
  :host([category="editor"]) { --_card-accent: var(--ui-warning); }
48
52
  :host:hover { border-color: color-mix(in srgb, var(--_card-accent) 45%, var(--ui-border)); box-shadow: var(--ui-shadow-md); transform: translateY(-2px); }
53
+ :host([reference]):hover { transform: none; border-color: var(--ui-border); box-shadow: var(--ui-shadow-sm); }
54
+ .example-heading { padding: var(--ui-space-5) !important; border-block-end: var(--ui-border-width) solid var(--ui-border); }
55
+ .example-code { padding: var(--ui-space-5) !important; border-block-start: var(--ui-border-width) solid var(--ui-border); }
56
+ .example-code h4 { margin: 0 0 var(--ui-space-3) !important; font-size: var(--ui-font-size-md); }
57
+ .example-heading p { margin: var(--ui-space-2) 0 0 !important; color: var(--ui-text-secondary); font-size: var(--ui-font-size-sm); }
58
+ :host([reference]) .preview { min-block-size: 11rem; display: grid !important; align-items: center !important; border-block-end: 0; }
59
+ :host([reference]) iframe { min-block-size: 11rem; }
60
+ :host([reference][category="overlay"]) iframe { min-block-size: 26rem; }
61
+ :host([reference]) .preview::before { display: none; }
49
62
  .preview { position: relative; min-block-size: 13rem; overflow: hidden; border-block-end: var(--ui-border-width) solid var(--ui-border); background: color-mix(in srgb, var(--_card-accent) 5%, var(--ui-surface)); }
50
63
  .preview::before { position: absolute; inset-block-start: 0; inset-inline-start: 0; inline-size: 3.5rem; block-size: 3px; background: var(--_card-accent); content: ""; z-index: 1; }
51
64
  iframe { display: block !important; max-inline-size: 100%; border: 0; }
52
65
  footer { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: var(--ui-space-2) !important; }
66
+ .preview-width { padding: var(--ui-space-4) !important; }
53
67
  .body { padding: var(--ui-space-5) !important; }
54
68
  .meta { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: var(--ui-space-2) !important; margin-block-end: var(--ui-space-2) !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; }
55
69
  .meta code { padding: 0 !important; background: transparent; color: inherit; text-transform: none; }
package/docs/card.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { documentationTheme, themeEvent } from './theme-session.mjs';
1
2
  /** Only theme data and measured height cross the opaque preview boundary. */
2
3
  export function connect(host) {
3
4
  const frame = host.refs.frame;
@@ -5,8 +6,17 @@ export function connect(host) {
5
6
  const remove = host.refs.remove;
6
7
  const removeCard = () => host.element.dispatchEvent(new CustomEvent("looma-example-remove", { bubbles: true, detail: { id: host.props.identity.value } }));
7
8
  const view = frame.ownerDocument.defaultView;
8
- let theme = '';
9
- const send = () => frame.contentWindow?.postMessage({ type: 'looma-theme', css: theme }, '*');
9
+ let theme = documentationTheme(view)?.css ?? '';
10
+ const documentTheme = () => { theme = documentationTheme(view)?.css ?? ''; send(); };
11
+ const preference = view.matchMedia('(prefers-color-scheme: dark)');
12
+ const send = () => {
13
+ const scheme = view.getComputedStyle(host.element).colorScheme;
14
+ const mode = scheme === 'dark' || (scheme.includes('dark') && scheme.includes('light') && preference.matches) ? 'dark' : 'light';
15
+ frame.contentWindow?.postMessage({ type: 'looma-theme', css: theme, mode, reference: Boolean(host.props.reference.value) }, '*');
16
+ };
17
+ // Observe only this card's ancestors: an opposite-mode boundary belongs to its own subtree.
18
+ const modes = new view.MutationObserver(send);
19
+ for (let ancestor = host.element; ancestor; ancestor = ancestor.parentElement) modes.observe(ancestor, { attributes: true, attributeFilter: ['data-theme', 'style', 'class'] });
10
20
  const message = event => {
11
21
  if (event.source !== frame.contentWindow) return;
12
22
  if (event.data?.type === 'looma-preview-ready') send();
@@ -21,12 +31,17 @@ export function connect(host) {
21
31
  width?.addEventListener('input', resize);
22
32
  remove?.addEventListener('click', removeCard);
23
33
  view.addEventListener('message', message);
34
+ view.addEventListener(themeEvent, documentTheme);
24
35
  frame.addEventListener('load', send);
36
+ preference.addEventListener('change', send);
25
37
  return () => {
26
38
  host.element.removeEventListener('looma-theme-preview', update);
27
39
  width?.removeEventListener('input', resize);
28
40
  remove?.removeEventListener('click', removeCard);
29
41
  view.removeEventListener('message', message);
42
+ view.removeEventListener(themeEvent, documentTheme);
30
43
  frame.removeEventListener('load', send);
44
+ preference.removeEventListener('change', send);
45
+ modes.disconnect();
31
46
  };
32
47
  }