@webjsdev/ui 0.3.9 → 0.3.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An AI-first component library - class-helper functions for visuals, custom elements only where state matters. Source-copied into your repo, you own it. Works with any Tailwind v4 project.",
|
|
6
6
|
"bin": {
|
|
@@ -241,7 +241,13 @@ export class UiTabsTrigger extends WebComponent({
|
|
|
241
241
|
const tabs = this._tabs;
|
|
242
242
|
if (!tabs) return;
|
|
243
243
|
const orientation = tabs.orientation;
|
|
244
|
-
|
|
244
|
+
// Scope to triggers belonging to THIS group: querySelectorAll crosses into
|
|
245
|
+
// a nested <ui-tabs> inside a panel, and arrow nav at the boundary would
|
|
246
|
+
// jump into the inner group and set this group's value to an inner-only
|
|
247
|
+
// value (hiding every panel). Same scoping dropdown-menu applies to items.
|
|
248
|
+
const triggers = Array.from(tabs.querySelectorAll<UiTabsTrigger>('ui-tabs-trigger')).filter(
|
|
249
|
+
(t) => t._tabs === tabs,
|
|
250
|
+
);
|
|
245
251
|
const idx = triggers.indexOf(this);
|
|
246
252
|
const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';
|
|
247
253
|
const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';
|
|
@@ -257,7 +263,13 @@ export class UiTabsTrigger extends WebComponent({
|
|
|
257
263
|
e.preventDefault();
|
|
258
264
|
const v = target.value;
|
|
259
265
|
if (v) tabs.setAttribute('value', v);
|
|
260
|
-
|
|
266
|
+
// Focus the inner <button role="tab">, NOT the <ui-tabs-trigger> host.
|
|
267
|
+
// The host carries no tabindex so it is not focusable; calling focus()
|
|
268
|
+
// on it is a no-op that leaves the focus ring stranded on the previous
|
|
269
|
+
// trigger while a different panel shows. The roving tabindex lives on
|
|
270
|
+
// the inner button, so that is what APG focus-follows-selection targets.
|
|
271
|
+
const btn = target.querySelector<HTMLButtonElement>('[role="tab"]');
|
|
272
|
+
(btn ?? target).focus();
|
|
261
273
|
}
|
|
262
274
|
};
|
|
263
275
|
}
|
|
@@ -73,22 +73,48 @@ const GROUPS: Array<[RegExp, string]> = [
|
|
|
73
73
|
[/^grid(-|$)/, 'grid'],
|
|
74
74
|
];
|
|
75
75
|
|
|
76
|
+
// Directional shorthand conflicts (the tailwind-merge model). A shorthand
|
|
77
|
+
// utility invalidates the axis / side utilities it SUBSUMES, because Tailwind's
|
|
78
|
+
// cascade makes the shorthand win: `p-0` after `px-4 py-2` drops both, but
|
|
79
|
+
// `px-4` after `p-2` refines only the x-axis so BOTH survive (the conflict is
|
|
80
|
+
// directional, not symmetric). Keyed by the group a NEWLY-seen token belongs to;
|
|
81
|
+
// the value lists the EARLIER groups whose survivors it removes (its own group
|
|
82
|
+
// is always removed too, handled below).
|
|
83
|
+
const CONFLICTS: Record<string, string[]> = {
|
|
84
|
+
p: ['px', 'py', 'pt', 'pr', 'pb', 'pl'],
|
|
85
|
+
px: ['pl', 'pr'],
|
|
86
|
+
py: ['pt', 'pb'],
|
|
87
|
+
m: ['mx', 'my', 'mt', 'mr', 'mb', 'ml'],
|
|
88
|
+
mx: ['ml', 'mr'],
|
|
89
|
+
my: ['mt', 'mb'],
|
|
90
|
+
size: ['w', 'h'],
|
|
91
|
+
};
|
|
92
|
+
|
|
76
93
|
function dedupeUtilities(input: string): string {
|
|
77
94
|
const tokens = input.split(/\s+/).filter(Boolean);
|
|
78
|
-
|
|
95
|
+
// `${prefix}::${group}` -> index of the last SURVIVING token in that group.
|
|
96
|
+
const lastByKey = new Map<string, number>();
|
|
79
97
|
const result: Array<string | null> = [];
|
|
80
98
|
|
|
81
99
|
for (const token of tokens) {
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
//
|
|
100
|
+
// Strip variant prefix (`hover:`, `dark:md:`, …) before testing each dedupe
|
|
101
|
+
// regex so `hover:bg-red-500` still matches the `bg-color` group. Conflicts
|
|
102
|
+
// only apply WITHIN the same variant (`px-4 hover:p-0` keeps both).
|
|
85
103
|
const prefix = variantPrefix(token);
|
|
86
104
|
const bare = prefix ? token.slice(prefix.length) : token;
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
let gk: string | null = null;
|
|
106
|
+
for (const [re, g] of GROUPS) {
|
|
107
|
+
if (re.test(bare)) { gk = g; break; }
|
|
108
|
+
}
|
|
109
|
+
if (gk) {
|
|
110
|
+
// Remove earlier survivors in this group AND in every group it subsumes.
|
|
111
|
+
for (const g of [gk, ...(CONFLICTS[gk] ?? [])]) {
|
|
112
|
+
const k = `${prefix}::${g}`;
|
|
113
|
+
const idx = lastByKey.get(k);
|
|
114
|
+
if (idx !== undefined) { result[idx] = null; lastByKey.delete(k); }
|
|
115
|
+
}
|
|
116
|
+
lastByKey.set(`${prefix}::${gk}`, result.length);
|
|
89
117
|
}
|
|
90
|
-
if (key && seen.has(key)) result[seen.get(key)!] = null;
|
|
91
|
-
if (key) seen.set(key, result.length);
|
|
92
118
|
result.push(token);
|
|
93
119
|
}
|
|
94
120
|
return result.filter(Boolean).join(' ');
|
|
@@ -135,7 +135,18 @@
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
@layer base {
|
|
138
|
+
/* Focus: a themed, keyboard-only (:focus-visible), never-removed outline, in
|
|
139
|
+
the --ring colour, applied globally. `outline-ring/50` themes only the
|
|
140
|
+
COLOUR and leaves outline-style: auto, which the browser draws as its OWN
|
|
141
|
+
ring (it can look thick/white and ignore the colour). So force a SOLID
|
|
142
|
+
outline on focus, at a consistent width + offset, so the themed --ring/50
|
|
143
|
+
colour actually renders the same on every element. A shadow-DOM component
|
|
144
|
+
sets its own focus (a document rule cannot cross the shadow boundary). */
|
|
138
145
|
* {
|
|
139
146
|
@apply border-border outline-ring/50;
|
|
140
147
|
}
|
|
148
|
+
:focus-visible {
|
|
149
|
+
outline: 2px solid color-mix(in oklab, var(--color-ring) 50%, transparent);
|
|
150
|
+
outline-offset: 2px;
|
|
151
|
+
}
|
|
141
152
|
}
|
package/src/registry/extract.js
CHANGED
|
@@ -19,13 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { loadRegistryItem, loadRegistryIndex, isCustomElementSource } from './local.js';
|
|
22
|
-
import { extractExample
|
|
23
|
-
|
|
24
|
-
// Re-export the lean-copy primitives so the scaffold generator (via
|
|
25
|
-
// `packages/cli/lib/lean-copy.js`) has one import surface for the shared
|
|
26
|
-
// example-strip. (The CLI's own `add` imports them directly from local.js /
|
|
27
|
-
// example.js.)
|
|
28
|
-
export { isCustomElementSource, stripExample };
|
|
22
|
+
import { extractExample } from './example.js';
|
|
29
23
|
|
|
30
24
|
/**
|
|
31
25
|
* Extract the exported class-helper signatures from a Tier-1 source, e.g.
|