@celestia-island/hikari 0.55.222 → 0.55.224
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 +1 -1
- package/src/components/HkAdminShell.tsx +74 -256
- package/src/components/HkAlert.scss +5 -0
- package/src/components/HkBlockingToast.scss +7 -0
- package/src/components/HkConfirmDialog.scss +4 -0
- package/src/components/HkDiffViewer.scss +4 -0
- package/src/components/HkFaceShell.barrel.test.ts +30 -0
- package/src/components/HkFaceShell.test.tsx +242 -0
- package/src/components/HkFaceShell.tsx +369 -0
- package/src/components/HkJsonTree.scss +5 -0
- package/src/components/HkLogWindow.scss +6 -0
- package/src/components/HkMarkdownRenderer.scss +5 -0
- package/src/components/HkMessageBox.scss +5 -0
- package/src/components/HkTable.scss +11 -0
- package/src/components/HkToast.scss +6 -0
- package/src/components/paneSwap.contract.test.ts +5 -1
- package/src/index.ts +1 -0
- package/src/res/boot-chrome.js +5 -4
- package/src/res/bootChrome.test.ts +5 -4
- package/src/styles/index.scss +4 -0
- package/src/styles/selection.scss +59 -0
- package/src/styles/selectionSheet.test.ts +134 -0
package/package.json
CHANGED
|
@@ -1,135 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { defineComponent, h, type PropType } from "vue";
|
|
2
|
+
import { HkFaceShell } from "./HkFaceShell";
|
|
3
3
|
import { provideActionBar } from "../composables/useActionBar";
|
|
4
|
-
import { useI18n } from "../i18n/context";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
// The padding-scan utilities moved INTO HkFaceShell with the content
|
|
6
|
+
// host that applies them (2026-10-29 extraction). Re-exported here
|
|
7
|
+
// because HkAdminShell.padding.test.ts and any external importer know
|
|
8
|
+
// this module as their home — the extraction must not move the public
|
|
9
|
+
// surface, only the implementation.
|
|
10
|
+
export {
|
|
11
|
+
scanPaddingSides,
|
|
12
|
+
splitPaddingSides,
|
|
13
|
+
mobileSideGutterFree,
|
|
14
|
+
} from "./HkFaceShell";
|
|
15
|
+
export type { PaddingSidesScan } from "./HkFaceShell";
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Scan a CSS padding shorthand into its top-level tracks: split on
|
|
26
|
-
* TOP-LEVEL whitespace, quote- and function-aware, so whitespace inside
|
|
27
|
-
* a function belongs to that value — `calc(1rem + 2px)`,
|
|
28
|
-
* `var(--pad, 1rem)` and a quoted argument are ONE track. A plain
|
|
29
|
-
* `split(/\s+/)` shreds them (`["calc(1rem", "+", "2px)"]`), and the
|
|
30
|
-
* rebuilt shorthand is invalid CSS — a browser drops the whole
|
|
31
|
-
* declaration, taking with it the vertical clearance
|
|
32
|
-
* `contentBleedOnMobile` promises to keep (rounds 2 and 3 of this change
|
|
33
|
-
* hit exactly that; rounds 4 and 5 the escape and EOF variants). The
|
|
34
|
-
* depth counter balances parentheses, so nested functions
|
|
35
|
-
* (`clamp(1rem, min(2vw, 3px), 4rem)`) survive; a stray `)` never drives
|
|
36
|
-
* the depth negative. Tabs and newlines are valid CSS separators and
|
|
37
|
-
* split like spaces, and the whitespace around a shorthand yields no
|
|
38
|
-
* empty tracks.
|
|
18
|
+
* HkAdminShell — the ADMIN face of the generalized shell
|
|
19
|
+
* (2026-10-29 extraction): HkFaceShell in document mode, plus the
|
|
20
|
+
* legacy action-bar channel. Everything structural — the frame, the
|
|
21
|
+
* optional sidebar and its mobile drawer, the scroll-viewport content
|
|
22
|
+
* host with `contentPadding` / `contentKey` / `contentBleedOnMobile`,
|
|
23
|
+
* the fixed footer band and its height reservation, the overlays slot —
|
|
24
|
+
* lives in HkFaceShell now; this component is a thin, behavior-identical
|
|
25
|
+
* wrapper kept for every existing consumer (props, slots and rendered
|
|
26
|
+
* classes are unchanged).
|
|
39
27
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
28
|
+
* The one thing the wrapper ADDS is its own action-bar provide: hosts
|
|
29
|
+
* of the pre-extraction contract read the registered actions through
|
|
30
|
+
* the header slot's `actions` param (the bar this shell provides
|
|
31
|
+
* itself). Faces that own their action bar — or their whole content
|
|
32
|
+
* lifecycle, like a canvas stage — consume HkFaceShell directly and
|
|
33
|
+
* provide whatever they want, closest to their pages.
|
|
43
34
|
*/
|
|
44
|
-
export function scanPaddingSides(value: string): PaddingSidesScan {
|
|
45
|
-
const sides: string[] = [];
|
|
46
|
-
let current = "";
|
|
47
|
-
let depth = 0;
|
|
48
|
-
let quote = "";
|
|
49
|
-
for (const ch of value) {
|
|
50
|
-
if (quote) {
|
|
51
|
-
current += ch;
|
|
52
|
-
if (ch === quote) quote = "";
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (ch === "\"" || ch === "'") {
|
|
56
|
-
quote = ch;
|
|
57
|
-
current += ch;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (ch === "(") depth += 1;
|
|
61
|
-
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
62
|
-
if (depth === 0 && /\s/.test(ch)) {
|
|
63
|
-
if (current) sides.push(current);
|
|
64
|
-
current = "";
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
current += ch;
|
|
68
|
-
}
|
|
69
|
-
if (current) sides.push(current);
|
|
70
|
-
return { sides, balanced: depth === 0 && quote === "" };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** The tracks of {@link scanPaddingSides} — for callers that only need
|
|
74
|
-
* the split, and for the unit test that pins the splitting rules. */
|
|
75
|
-
export function splitPaddingSides(value: string): string[] {
|
|
76
|
-
return scanPaddingSides(value).sides;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The side-gutter-free form of a padding shorthand: the vertical tracks
|
|
81
|
-
* survive, the horizontal ones become zero (`"1rem 2rem"` -> `"1rem 0
|
|
82
|
-
* 1rem"`). The value is returned VERBATIM — the page keeps its padding
|
|
83
|
-
* and simply does not bleed — whenever the shorthand cannot be rewritten
|
|
84
|
-
* safely. That fallback IS the contract: emitting a declaration a browser
|
|
85
|
-
* drops costs the page ALL FOUR sides, including the vertical clearance
|
|
86
|
-
* this prop exists to keep.
|
|
87
|
-
*
|
|
88
|
-
* Verbatim cases:
|
|
89
|
-
*
|
|
90
|
-
* - an empty / whitespace-only value has nothing to strip;
|
|
91
|
-
* - a LONE CSS-wide keyword cannot be combined per side (`inherit 0
|
|
92
|
-
* inherit` is not a padding — every engine rejects it);
|
|
93
|
-
* - the value contains a CSS comment, whose text would land in the
|
|
94
|
-
* rebuilt declaration as a garbage track (`1rem 0 x`, measured in
|
|
95
|
-
* Chromium: dropped, all four sides lost);
|
|
96
|
-
* - the value contains a backslash: this scanner does not decode CSS
|
|
97
|
-
* escapes, and an escape can hide a quote, a parenthesis or a whole
|
|
98
|
-
* keyword (`\69 nherit` IS `inherit` to an engine) — the literal text
|
|
99
|
-
* is not the value, so no rewrite of it is trustworthy (round 5);
|
|
100
|
-
* - the value ends with a `!important` annotation: the annotation
|
|
101
|
-
* belongs to the DECLARATION, so a per-side rewrite would either
|
|
102
|
-
* silently lose the priority (`2rem !important`) or rebuild the
|
|
103
|
-
* `inherit 0 inherit` every engine rejects (`inherit !important`,
|
|
104
|
-
* measured in Chromium, round 5);
|
|
105
|
-
* - the scan ends inside an open function or an unterminated quote: CSS
|
|
106
|
-
* auto-closes a function at EOF, so `"calc(1rem + 2px"` IS a real
|
|
107
|
-
* padding (measured: all four sides) while its rewrite is not; an
|
|
108
|
-
* unterminated quote is invalid at both ends, and bailing keeps the
|
|
109
|
-
* two paths identical (round 5).
|
|
110
|
-
*
|
|
111
|
-
* Known residual (documented, not fixable at string level): a `var()`
|
|
112
|
-
* whose custom property resolves to MORE than one track cannot keep its
|
|
113
|
-
* vertical half — `padding: var(--multi) 0 var(--multi)` is valid at
|
|
114
|
-
* parse time and invalid at computed-value time, and all four sides
|
|
115
|
-
* collapse. No consumer passes a multi-track custom property today.
|
|
116
|
-
*/
|
|
117
|
-
export function mobileSideGutterFree(value: string): string {
|
|
118
|
-
if (value.includes("/*") || value.includes("\\")) return value;
|
|
119
|
-
// `!important` is an annotation on the DECLARATION, not a track — it
|
|
120
|
-
// cannot ride a per-side rewrite: `2rem !important` would silently lose
|
|
121
|
-
// its priority, and `inherit !important` would rebuild into the
|
|
122
|
-
// `inherit 0 inherit` every engine drops (measured in Chromium, round
|
|
123
|
-
// 5). Bail out and let the authored padding stand.
|
|
124
|
-
if (/!\s*important\s*$/i.test(value)) return value;
|
|
125
|
-
const { sides, balanced } = scanPaddingSides(value);
|
|
126
|
-
if (!balanced) return value;
|
|
127
|
-
if (sides.length === 0) return value;
|
|
128
|
-
if (sides.length === 1 && CSS_WIDE_KEYWORDS.has(sides[0].toLowerCase())) return value;
|
|
129
|
-
const [top, , bottom] = sides;
|
|
130
|
-
return `${top} 0 ${bottom ?? top}`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
35
|
export const HkAdminShell = defineComponent({
|
|
134
36
|
name: "HkAdminShell",
|
|
135
37
|
props: {
|
|
@@ -145,11 +47,9 @@ export const HkAdminShell = defineComponent({
|
|
|
145
47
|
* whether the shell actually renders a status bar — a filled
|
|
146
48
|
* `footer` slot (the shell's own fixed `.s-status-bar`) reserves
|
|
147
49
|
* `var(--s-footer-height)` by default, and with NO footer slot
|
|
148
|
-
* nothing is reserved (`0px`).
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* fixed bar outside the shell opt back in by passing an explicit
|
|
152
|
-
* value (e.g. `"var(--s-footer-height)"`). */
|
|
50
|
+
* nothing is reserved (`0px`). Hosts that pin their OWN fixed bar
|
|
51
|
+
* outside the shell opt back in by passing an explicit value
|
|
52
|
+
* (e.g. `"var(--s-footer-height)"`). */
|
|
153
53
|
footerHeight: { type: String, default: undefined },
|
|
154
54
|
navTitle: { type: String, default: undefined },
|
|
155
55
|
/** Extra class for the mobile nav drawer's panel — lets consumers zero
|
|
@@ -188,134 +88,52 @@ export const HkAdminShell = defineComponent({
|
|
|
188
88
|
* of risking a declaration a browser would drop whole.
|
|
189
89
|
* Default false: the padding reads the same at every width. */
|
|
190
90
|
contentBleedOnMobile: { type: Boolean, default: false },
|
|
91
|
+
/** The content host mode, forwarded to HkFaceShell. DECLARED on
|
|
92
|
+
* this wrapper on purpose (R2 S2, 2026-10-29): an undeclared input
|
|
93
|
+
* would fall through `$attrs` onto the inner shell, where it IS a
|
|
94
|
+
* declared prop — and fallthrough attrs override explicit props,
|
|
95
|
+
* so a stray `content-mode="stage"` from a template or a
|
|
96
|
+
* `v-bind="$attrs"` relay would silently flip the admin shell into
|
|
97
|
+
* the stage host. Declared here, the input is a real prop of this
|
|
98
|
+
* component and forwards honestly; the default stays `document`,
|
|
99
|
+
* which is what the admin face is. */
|
|
100
|
+
contentMode: { type: String as PropType<"document" | "stage">, default: "document" },
|
|
191
101
|
},
|
|
192
102
|
setup(props, { slots }) {
|
|
193
|
-
const { t } = useI18n();
|
|
194
|
-
const { width: viewportWidth } = useBreakpoint();
|
|
195
|
-
const isDesktop = computed(() => viewportWidth.value >= props.mobileBreakpoint);
|
|
196
|
-
const sidebarOpen = ref(false);
|
|
197
|
-
|
|
198
|
-
const contentStyle = computed(() => ({
|
|
199
|
-
padding: isDesktop.value || !props.contentBleedOnMobile
|
|
200
|
-
? props.contentPadding
|
|
201
|
-
: mobileSideGutterFree(props.contentPadding),
|
|
202
|
-
}));
|
|
203
|
-
|
|
204
103
|
const actionBar = provideActionBar();
|
|
205
104
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
actions: actionBar.actions.value ? actionBar.actions.value() : [],
|
|
240
|
-
onHamburger: toggleHamburger,
|
|
241
|
-
// Lets a header trigger (e.g. the avatar in "drawer" action
|
|
242
|
-
// mode) open the mobile nav drawer directly.
|
|
243
|
-
onOpenDrawer: openSidebar,
|
|
244
|
-
})}
|
|
245
|
-
</div>
|
|
246
|
-
)}
|
|
247
|
-
|
|
248
|
-
<div class="s-admin-shell-body" style={{ paddingBottom: footerPad() }}>
|
|
249
|
-
{isDesktop.value && !props.sidebarCollapsed && slots.sidebar && (
|
|
250
|
-
<aside
|
|
251
|
-
style={{
|
|
252
|
-
width: props.sidebarWidth,
|
|
253
|
-
flexShrink: 0,
|
|
254
|
-
borderRight: "1px solid var(--border-faint, rgb(var(--color-border) / 10%))",
|
|
255
|
-
background: "rgb(var(--color-surface))",
|
|
256
|
-
overflow: "hidden",
|
|
257
|
-
}}
|
|
258
|
-
>
|
|
259
|
-
{slots.sidebar({ collapsed: false, onNavigate: closeSidebar })}
|
|
260
|
-
</aside>
|
|
261
|
-
)}
|
|
262
|
-
<main class="s-admin-shell-main">
|
|
263
|
-
<HScrollContainer class="s-admin-shell-scroll">
|
|
264
|
-
{/* Padding lives INSIDE the scroll viewport (an inner
|
|
265
|
-
wrapper) so card box-shadows are not clipped at the
|
|
266
|
-
viewport edges. */}
|
|
267
|
-
<div style={contentStyle.value}>
|
|
268
|
-
{props.contentKey != null ? (
|
|
269
|
-
<HkCrossfade swapKey={props.contentKey} class="s-admin-shell-swap">
|
|
270
|
-
{slots.content?.()}
|
|
271
|
-
</HkCrossfade>
|
|
272
|
-
) : (
|
|
273
|
-
slots.content?.()
|
|
274
|
-
)}
|
|
275
|
-
</div>
|
|
276
|
-
</HScrollContainer>
|
|
277
|
-
</main>
|
|
278
|
-
|
|
279
|
-
{!isDesktop.value && (
|
|
280
|
-
<HDrawer
|
|
281
|
-
modelValue={sidebarOpen.value}
|
|
282
|
-
onUpdate:modelValue={(v: boolean) => (sidebarOpen.value = v)}
|
|
283
|
-
side="left"
|
|
284
|
-
size="280px"
|
|
285
|
-
title={props.navTitle ?? t("hikari::adminShell.navTitle", "Navigation")}
|
|
286
|
-
panelClass={props.drawerPanelClass}
|
|
287
|
-
>
|
|
288
|
-
{/* The drawer body carries the nav; a `userPanel` slot rides
|
|
289
|
-
the drawer footer (identity + account actions) so mobile
|
|
290
|
-
gets the same content the desktop user menu exposes.
|
|
291
|
-
`inDrawer` lets the sidebar slot fill the drawer width.
|
|
292
|
-
The userPanel receives `onNavigate` (closes the drawer)
|
|
293
|
-
so its action rows — e.g. "go to frontend", which swaps
|
|
294
|
-
the whole layout underneath — can dismiss the drawer
|
|
295
|
-
instead of leaving it hovering over the new page. */}
|
|
296
|
-
{{
|
|
297
|
-
default: () =>
|
|
298
|
-
slots.sidebar?.({ collapsed: false, onNavigate: closeSidebar, inDrawer: true }),
|
|
299
|
-
footer: slots.userPanel
|
|
300
|
-
? () => slots.userPanel!({ onNavigate: closeSidebar })
|
|
301
|
-
: undefined,
|
|
302
|
-
}}
|
|
303
|
-
</HDrawer>
|
|
304
|
-
)}
|
|
305
|
-
</div>
|
|
306
|
-
|
|
307
|
-
{slots.footer && (
|
|
308
|
-
<footer class="s-status-bar" style={{ position: "fixed", bottom: 0, left: 0, right: 0, // Footer band (was a bare 40 — the pre-band chrome value).
|
|
309
|
-
zIndex: "var(--hk-z-toolbar)" }}>
|
|
310
|
-
{slots.footer()}
|
|
311
|
-
</footer>
|
|
312
|
-
)}
|
|
313
|
-
|
|
314
|
-
{/* Scoped slots are functions — rendering the slot itself instead of
|
|
315
|
-
calling it would stringify the compiled withCtx source into a text
|
|
316
|
-
node (normalizeVNode String()s non-vnode children). */}
|
|
317
|
-
{slots.overlays?.()}
|
|
318
|
-
</div>
|
|
319
|
-
);
|
|
105
|
+
return () =>
|
|
106
|
+
h(HkFaceShell, {
|
|
107
|
+
sidebarCollapsed: props.sidebarCollapsed,
|
|
108
|
+
sidebarWidth: props.sidebarWidth,
|
|
109
|
+
mobileBreakpoint: props.mobileBreakpoint,
|
|
110
|
+
footerHeight: props.footerHeight,
|
|
111
|
+
navTitle: props.navTitle,
|
|
112
|
+
drawerPanelClass: props.drawerPanelClass,
|
|
113
|
+
// The admin face IS the document mode by default — stage faces
|
|
114
|
+
// normally consume HkFaceShell directly; the forward keeps an
|
|
115
|
+
// explicit opt-in honest (and the declared prop above keeps a
|
|
116
|
+
// stray attr from ever reaching the inner shell on its own).
|
|
117
|
+
contentMode: props.contentMode,
|
|
118
|
+
contentPadding: props.contentPadding,
|
|
119
|
+
contentKey: props.contentKey,
|
|
120
|
+
contentBleedOnMobile: props.contentBleedOnMobile,
|
|
121
|
+
}, {
|
|
122
|
+
header: slots.header
|
|
123
|
+
? (shellParams: Record<string, unknown>) =>
|
|
124
|
+
slots.header!({
|
|
125
|
+
...shellParams,
|
|
126
|
+
// The legacy channel this wrapper exists to keep: the
|
|
127
|
+
// actions of the bar provided above, handed to the header
|
|
128
|
+
// slot exactly as before the extraction.
|
|
129
|
+
actions: actionBar.actions.value ? actionBar.actions.value() : [],
|
|
130
|
+
})
|
|
131
|
+
: undefined,
|
|
132
|
+
sidebar: slots.sidebar,
|
|
133
|
+
userPanel: slots.userPanel,
|
|
134
|
+
content: slots.content,
|
|
135
|
+
footer: slots.footer,
|
|
136
|
+
overlays: slots.overlays,
|
|
137
|
+
});
|
|
320
138
|
},
|
|
321
139
|
});
|
|
@@ -93,6 +93,11 @@
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
.hk-alert-text {
|
|
96
|
+
/* Alert body = the same copy-this-error-detail content class as the
|
|
97
|
+
* toast message and message-box text (L2 selection policy re-enables,
|
|
98
|
+
* 2026-10-29); the title above stays chrome. */
|
|
99
|
+
-webkit-user-select: text;
|
|
100
|
+
user-select: text;
|
|
96
101
|
margin: 0;
|
|
97
102
|
line-height: 1.5;
|
|
98
103
|
color: color-mix(in srgb, var(--hi-color-text-secondary, #666666) 85%, transparent);
|
|
@@ -58,10 +58,17 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
.hk-blocking-toast-title {
|
|
61
|
+
/* Content, not chrome — copy-worthy detail under the L2 selection
|
|
62
|
+
* policy (2026-10-29): the card shell denies, these two text blocks
|
|
63
|
+
* select again. */
|
|
64
|
+
-webkit-user-select: text;
|
|
65
|
+
user-select: text;
|
|
61
66
|
font-weight: 700;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
.hk-blocking-toast-message {
|
|
70
|
+
-webkit-user-select: text;
|
|
71
|
+
user-select: text;
|
|
65
72
|
margin: 0;
|
|
66
73
|
display: -webkit-box;
|
|
67
74
|
-webkit-line-clamp: 5;
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.hk-confirm-dialog-message {
|
|
18
|
+
/* Dialog message = copy-worthy content, same class as the message-box
|
|
19
|
+
* text (L2 selection policy re-enables, 2026-10-29). */
|
|
20
|
+
-webkit-user-select: text;
|
|
21
|
+
user-select: text;
|
|
18
22
|
margin: 0;
|
|
19
23
|
color: color-mix(in srgb, var(--hi-color-text-secondary) 85%, transparent);
|
|
20
24
|
font-size: var(--text-sm, 0.8125rem);
|
|
@@ -67,6 +67,10 @@
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
.hk-diff-viewer__code {
|
|
70
|
+
/* The diff body is content (review text users copy into replies);
|
|
71
|
+
* the line-number gutter above and the sign column stay denied. */
|
|
72
|
+
-webkit-user-select: text;
|
|
73
|
+
user-select: text;
|
|
70
74
|
padding-inline-end: 1ch;
|
|
71
75
|
min-width: 0;
|
|
72
76
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as barrel from "../index";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The public barrel guard for HkFaceShell (R3 M6 on PR #900): deleting
|
|
6
|
+
* the export line from index.ts kept the ENTIRE suite green before this
|
|
7
|
+
* pin — every other test reaches the component through the direct
|
|
8
|
+
* "./HkFaceShell" path, so only an external consumer would have noticed.
|
|
9
|
+
* Same shape as theme/themeDecorSurface.test.ts.
|
|
10
|
+
*
|
|
11
|
+
* This file deliberately does NOT mock "@celestia-island/hikari": the
|
|
12
|
+
* package's self-reference resolves to the very same src/index.ts module
|
|
13
|
+
* as "../index", so a vi.mock here would replace the barrel under test
|
|
14
|
+
* with the mock's own namespace (measured: HkFaceShell read as undefined
|
|
15
|
+
* through `...actual` under the re-entrant factory).
|
|
16
|
+
*/
|
|
17
|
+
describe("the public barrel", () => {
|
|
18
|
+
it("exports HkFaceShell (deleting the line kept the suite green before this pin)", () => {
|
|
19
|
+
// defineComponent's object syntax yields a plain object, so the
|
|
20
|
+
// identity check is the component's own `name`, not typeof.
|
|
21
|
+
const face = barrel.HkFaceShell as unknown as { name?: string };
|
|
22
|
+
expect(face, "HkFaceShell must be re-exported").toBeTruthy();
|
|
23
|
+
expect(face.name).toBe("HkFaceShell");
|
|
24
|
+
// The admin wrapper stays reachable too — the extraction must not
|
|
25
|
+
// move the public surface.
|
|
26
|
+
const admin = barrel.HkAdminShell as unknown as { name?: string };
|
|
27
|
+
expect(admin, "HkAdminShell must stay exported").toBeTruthy();
|
|
28
|
+
expect(admin.name).toBe("HkAdminShell");
|
|
29
|
+
});
|
|
30
|
+
});
|