@signal9/era-ui 22.0.0 → 23.1.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.
- package/CHANGELOG.md +32 -0
- package/dist/era-ui.css +1 -1
- package/dist/eslint/index.d.ts +55 -0
- package/dist/eslint/index.js +77 -0
- package/dist/eslint/rules-core.d.ts +14 -0
- package/dist/eslint/rules-core.js +111 -0
- package/dist/index.d.ts +1 -1
- package/dist/os/desktop.svelte +42 -11
- package/dist/os/desktop.svelte.d.ts +16 -0
- package/dist/ui/accordion/accordion-trigger.svelte +2 -2
- package/dist/ui/accordion/accordion-trigger.svelte.d.ts +2 -2
- package/dist/ui/alert-dialog/alert-dialog-content.svelte +6 -2
- package/dist/ui/alert-dialog/alert-dialog-content.svelte.d.ts +2 -2
- package/dist/ui/alert-dialog/alert-dialog-root.svelte +33 -0
- package/dist/ui/alert-dialog/alert-dialog-root.svelte.d.ts +8 -0
- package/dist/ui/alert-dialog/alert-dialog-trigger.svelte +8 -0
- package/dist/ui/alert-dialog/index.d.ts +1 -1
- package/dist/ui/alert-dialog/index.js +1 -1
- package/dist/ui/combobox/combobox-trigger.svelte +2 -2
- package/dist/ui/combobox/combobox-trigger.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-content.svelte +6 -2
- package/dist/ui/context-menu/context-menu-content.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-root.svelte +33 -0
- package/dist/ui/context-menu/context-menu-root.svelte.d.ts +8 -0
- package/dist/ui/context-menu/context-menu-sub-content.svelte +6 -2
- package/dist/ui/context-menu/context-menu-sub-content.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-sub-trigger.svelte +2 -2
- package/dist/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +2 -2
- package/dist/ui/context-menu/context-menu-trigger.svelte +8 -0
- package/dist/ui/context-menu/index.d.ts +1 -1
- package/dist/ui/context-menu/index.js +1 -1
- package/dist/ui/date-picker/date-picker-trigger.svelte +2 -2
- package/dist/ui/date-picker/date-picker-trigger.svelte.d.ts +2 -2
- package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +2 -2
- package/dist/ui/date-range-picker/date-range-picker-trigger.svelte.d.ts +2 -2
- package/dist/ui/dialog/dialog-content.svelte +16 -2
- package/dist/ui/dialog/dialog-content.svelte.d.ts +2 -2
- package/dist/ui/dialog/dialog-root.svelte +43 -0
- package/dist/ui/dialog/dialog-root.svelte.d.ts +8 -0
- package/dist/ui/dialog/dialog-trigger.svelte +10 -0
- package/dist/ui/dialog/index.d.ts +1 -1
- package/dist/ui/dialog/index.js +1 -1
- package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +6 -2
- package/dist/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-root.svelte +33 -0
- package/dist/ui/dropdown-menu/dropdown-menu-root.svelte.d.ts +8 -0
- package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +6 -2
- package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +10 -2
- package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +2 -2
- package/dist/ui/dropdown-menu/index.d.ts +1 -1
- package/dist/ui/dropdown-menu/index.js +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/link-preview/index.d.ts +1 -1
- package/dist/ui/link-preview/index.js +1 -1
- package/dist/ui/link-preview/link-preview-content.svelte +6 -2
- package/dist/ui/link-preview/link-preview-content.svelte.d.ts +2 -2
- package/dist/ui/link-preview/link-preview-root.svelte +33 -0
- package/dist/ui/link-preview/link-preview-root.svelte.d.ts +8 -0
- package/dist/ui/link-preview/link-preview-trigger.svelte +8 -0
- package/dist/ui/menubar/menubar-content.svelte +6 -2
- package/dist/ui/menubar/menubar-content.svelte.d.ts +2 -2
- package/dist/ui/menubar/menubar-root.svelte +26 -1
- package/dist/ui/menubar/menubar-root.svelte.d.ts +1 -1
- package/dist/ui/menubar/menubar-sub-content.svelte +6 -2
- package/dist/ui/menubar/menubar-sub-content.svelte.d.ts +2 -2
- package/dist/ui/menubar/menubar-sub-trigger.svelte +2 -2
- package/dist/ui/menubar/menubar-sub-trigger.svelte.d.ts +2 -2
- package/dist/ui/nav/index.d.ts +2 -0
- package/dist/ui/nav/index.js +2 -0
- package/dist/ui/nav/nav-bar.svelte +51 -0
- package/dist/ui/nav/nav-bar.svelte.d.ts +21 -0
- package/dist/ui/nav/nav-item.svelte +79 -0
- package/dist/ui/nav/nav-item.svelte.d.ts +29 -0
- package/dist/ui/provider/axes.d.ts +14 -1
- package/dist/ui/provider/axis-relay.svelte.d.ts +34 -0
- package/dist/ui/provider/axis-relay.svelte.js +109 -0
- package/dist/ui/select/select-content.svelte +2 -2
- package/dist/ui/select/select-content.svelte.d.ts +2 -2
- package/dist/ui/select/select-trigger.svelte +2 -2
- package/dist/ui/select/select-trigger.svelte.d.ts +2 -2
- package/dist/ui/sheet/sheet-content.svelte +8 -2
- package/dist/ui/sheet/sheet-content.svelte.d.ts +2 -2
- package/dist/ui/step/step-content.svelte +2 -2
- package/dist/ui/step/step-content.svelte.d.ts +2 -2
- package/dist/ui/step/step-summary.svelte +2 -2
- package/dist/ui/step/step-summary.svelte.d.ts +2 -2
- package/dist/ui/tooltip/index.d.ts +1 -1
- package/dist/ui/tooltip/index.js +1 -1
- package/dist/ui/tooltip/tooltip-content.svelte +6 -2
- package/dist/ui/tooltip/tooltip-content.svelte.d.ts +2 -2
- package/dist/ui/tooltip/tooltip-root.svelte +33 -0
- package/dist/ui/tooltip/tooltip-root.svelte.d.ts +8 -0
- package/dist/ui/tooltip/tooltip-trigger.svelte +8 -0
- package/dist/utils/index.d.ts +36 -3
- package/package.json +10 -5
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ESLINT RULES FOR CONSUMING ERA.
|
|
3
|
+
*
|
|
4
|
+
* era's failure mode is silently wrong, never loudly broken. An unknown class
|
|
5
|
+
* renders as nothing; an arbitrary value renders as SOMETHING, which is worse,
|
|
6
|
+
* because it looks fine on the density you wrote it at and quietly falls off
|
|
7
|
+
* the ladder on the other three. Neither shows up in a type check, a test, or a
|
|
8
|
+
* screenshot taken at one setting.
|
|
9
|
+
*
|
|
10
|
+
* These rules catch the two shapes that account for nearly all of it, and both
|
|
11
|
+
* were proposed by a consumer who had hit them repeatedly rather than invented
|
|
12
|
+
* here:
|
|
13
|
+
*
|
|
14
|
+
* era/no-arbitrary-tier h-[24px] / h-(--era-h-control) where h-control is
|
|
15
|
+
* the name for exactly that value.
|
|
16
|
+
* era/no-hardcoded-motion duration-150 / ease-in-out, which pin timing that
|
|
17
|
+
* the motion axis is supposed to own — including at
|
|
18
|
+
* prefers-reduced-motion, where they refuse to stop.
|
|
19
|
+
*
|
|
20
|
+
* THE VOCABULARY IS IMPORTED, NOT RESTATED. TIERS and SPACING come from
|
|
21
|
+
* utils/tw-merge-config.js, the same lists cn() is built from and that
|
|
22
|
+
* check-twmerge verifies against the stylesheet. A second copy here would be
|
|
23
|
+
* the exact hand-maintained duplicate this repo keeps having to delete — and it
|
|
24
|
+
* would go stale in the direction that matters, telling consumers a rung exists
|
|
25
|
+
* that does not.
|
|
26
|
+
*/
|
|
27
|
+
import { TIERS, SPACING } from '../utils/tw-merge-config.js';
|
|
28
|
+
import { findArbitraryTiers as findTiers, findHardcodedMotion } from './rules-core.js';
|
|
29
|
+
/** era's real vocabulary, bound once. */
|
|
30
|
+
const VOCAB = { TIERS, SPACING };
|
|
31
|
+
/** Arbitrary/var-form lengths where era has a name, against era's own lists. */
|
|
32
|
+
export const findArbitraryTiers = (value) => findTiers(value, VOCAB);
|
|
33
|
+
export { findHardcodedMotion };
|
|
34
|
+
function makeRule(description, find) {
|
|
35
|
+
return {
|
|
36
|
+
meta: { type: 'problem', docs: { description }, schema: [] },
|
|
37
|
+
create(ctx) {
|
|
38
|
+
const check = (node, text) => {
|
|
39
|
+
for (const r of find(text))
|
|
40
|
+
ctx.report({ node, message: r.message });
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
Literal(node) {
|
|
44
|
+
if (typeof node.value === 'string')
|
|
45
|
+
check(node, node.value);
|
|
46
|
+
},
|
|
47
|
+
TemplateElement(node) {
|
|
48
|
+
const cooked = node.value?.cooked;
|
|
49
|
+
if (typeof cooked === 'string')
|
|
50
|
+
check(node, cooked);
|
|
51
|
+
},
|
|
52
|
+
SvelteLiteral(node) {
|
|
53
|
+
if (typeof node.value === 'string')
|
|
54
|
+
check(node, node.value);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export const rules = {
|
|
61
|
+
'no-arbitrary-tier': makeRule('Use era’s named rungs instead of arbitrary or var-form lengths.', findArbitraryTiers),
|
|
62
|
+
'no-hardcoded-motion': makeRule('Take transition timing from the motion axis, not from a literal.', findHardcodedMotion)
|
|
63
|
+
};
|
|
64
|
+
const plugin = {
|
|
65
|
+
meta: { name: '@sig-nine/era-ui/eslint' },
|
|
66
|
+
rules,
|
|
67
|
+
configs: {}
|
|
68
|
+
};
|
|
69
|
+
/** Flat-config preset: both rules as errors. */
|
|
70
|
+
plugin.configs.recommended = {
|
|
71
|
+
plugins: { era: plugin },
|
|
72
|
+
rules: {
|
|
73
|
+
'era/no-arbitrary-tier': 'error',
|
|
74
|
+
'era/no-hardcoded-motion': 'error'
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
export default plugin;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** One flagged class, with the offset that lets a host report it precisely. */
|
|
2
|
+
export interface Report {
|
|
3
|
+
index: number;
|
|
4
|
+
length: number;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Vocabulary {
|
|
8
|
+
TIERS: string[];
|
|
9
|
+
SPACING: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Every offending token in one class string, with its offset for a precise report. */
|
|
12
|
+
export declare function findArbitraryTiers(value: string, vocab: Vocabulary): Report[];
|
|
13
|
+
/** Hardcoded timing, which the motion axis is supposed to own. */
|
|
14
|
+
export declare function findHardcodedMotion(value: string): Report[];
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* THE PURE RULE LOGIC — string in, reports out.
|
|
3
|
+
*
|
|
4
|
+
* Split from index.ts for one reason: the vocabulary is PASSED IN rather than
|
|
5
|
+
* imported here. index.ts wires it to the real TIERS/SPACING (the same lists
|
|
6
|
+
* cn() is built from), and scripts/check-eslint-rules.ts wires the same lists
|
|
7
|
+
* itself, so the self-test exercises exactly what ships without needing to
|
|
8
|
+
* resolve the package's `.js`-suffixed internal imports from source.
|
|
9
|
+
*
|
|
10
|
+
* No cross-directory imports live in this file, and that is deliberate.
|
|
11
|
+
*/
|
|
12
|
+
/** Prefixes whose value is a tier height/width. */
|
|
13
|
+
const TIER_PREFIXES = ['h', 'min-h', 'max-h', 'w', 'min-w', 'max-w', 'size', 'rounded'];
|
|
14
|
+
/** Prefixes whose value is on the --spacing-* scale. */
|
|
15
|
+
const SPACING_PREFIXES = [
|
|
16
|
+
'p',
|
|
17
|
+
'px',
|
|
18
|
+
'py',
|
|
19
|
+
'pt',
|
|
20
|
+
'pr',
|
|
21
|
+
'pb',
|
|
22
|
+
'pl',
|
|
23
|
+
'm',
|
|
24
|
+
'mx',
|
|
25
|
+
'my',
|
|
26
|
+
'mt',
|
|
27
|
+
'mr',
|
|
28
|
+
'mb',
|
|
29
|
+
'ml',
|
|
30
|
+
'gap',
|
|
31
|
+
'gap-x',
|
|
32
|
+
'gap-y',
|
|
33
|
+
'top',
|
|
34
|
+
'right',
|
|
35
|
+
'bottom',
|
|
36
|
+
'left',
|
|
37
|
+
'inset',
|
|
38
|
+
'inset-x',
|
|
39
|
+
'inset-y'
|
|
40
|
+
];
|
|
41
|
+
/** `--era-h-control` -> `control`, so the var form can be named back. */
|
|
42
|
+
function tierFromToken(token, TIERS) {
|
|
43
|
+
const m = token.match(/^--era-(?:h|rd)-([a-z]+)$/);
|
|
44
|
+
return m && TIERS.includes(m[1]) ? m[1] : null;
|
|
45
|
+
}
|
|
46
|
+
function spacingFromToken(token, SPACING) {
|
|
47
|
+
const m = token.match(/^--era-(.+)$/);
|
|
48
|
+
if (!m)
|
|
49
|
+
return null;
|
|
50
|
+
const name = m[1] === 'gap' ? 'gutter' : m[1];
|
|
51
|
+
return SPACING.includes(name) ? name : null;
|
|
52
|
+
}
|
|
53
|
+
/** Every offending token in one class string, with its offset for a precise report. */
|
|
54
|
+
export function findArbitraryTiers(value, vocab) {
|
|
55
|
+
const { TIERS, SPACING } = vocab;
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const m of value.matchAll(/(?:^|\s)((?:[a-z-]+:)*)([a-z-]+)-(\[[^\]]+\]|\([^)]+\))/g)) {
|
|
58
|
+
const cls = m[0].trimStart();
|
|
59
|
+
const index = m.index + (m[0].length - cls.length);
|
|
60
|
+
const [, variants, prefix, raw] = m;
|
|
61
|
+
const inner = raw.slice(1, -1);
|
|
62
|
+
// The paren form is era's own shorthand for a token — it is not wrong, it
|
|
63
|
+
// is just unnamed when a name exists.
|
|
64
|
+
const named = tierFromToken(inner, TIERS) ?? spacingFromToken(inner, SPACING);
|
|
65
|
+
if (named) {
|
|
66
|
+
const suggestion = `${variants}${prefix}-${named}`;
|
|
67
|
+
if ((TIER_PREFIXES.includes(prefix) && TIERS.includes(named)) ||
|
|
68
|
+
(SPACING_PREFIXES.includes(prefix) && SPACING.includes(named))) {
|
|
69
|
+
out.push({
|
|
70
|
+
index,
|
|
71
|
+
length: cls.length,
|
|
72
|
+
message: `\`${cls}\` names a value era already has a name for — use \`${suggestion}\`. The named form is what tailwind-merge can resolve, so a consumer overriding it actually wins.`
|
|
73
|
+
});
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// A bare ABSOLUTE length in a tier or spacing position is the real hazard:
|
|
78
|
+
// it is correct at the one density it was written at and wrong at the
|
|
79
|
+
// other three.
|
|
80
|
+
//
|
|
81
|
+
// Absolute only — px/rem/em, and a unitless 0. A percentage, ch, vh or fr
|
|
82
|
+
// is not on the ladder and era has no rung to offer instead, so flagging
|
|
83
|
+
// `w-[40%]` or `max-w-[65ch]` would be telling a consumer to replace
|
|
84
|
+
// correct code with something that does not exist. Both were false
|
|
85
|
+
// positives in the first draft, caught by this rule's own self-test.
|
|
86
|
+
const isLadderLength = /^-?\d+(?:\.\d+)?(?:px|rem|em)?$/.test(inner);
|
|
87
|
+
if (isLadderLength && (TIER_PREFIXES.includes(prefix) || SPACING_PREFIXES.includes(prefix))) {
|
|
88
|
+
out.push({
|
|
89
|
+
index,
|
|
90
|
+
length: cls.length,
|
|
91
|
+
message: `\`${cls}\` hardcodes a length on an axis era scales. It is correct at the density you wrote it at and wrong at the other three — use a rung (${prefix}-${TIERS.join(' / ')}) or a spacing name.`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/** Hardcoded timing, which the motion axis is supposed to own. */
|
|
98
|
+
export function findHardcodedMotion(value) {
|
|
99
|
+
const out = [];
|
|
100
|
+
for (const m of value.matchAll(/(?:^|\s)((?:[a-z-]+:)*)(duration-\d+|duration-\[[^\]]+\]|ease-(?:linear|in|out|in-out)|ease-\[[^\]]+\])/g)) {
|
|
101
|
+
const cls = (m[1] ?? '') + m[2];
|
|
102
|
+
const index = m.index + (m[0].length - cls.length);
|
|
103
|
+
const fix = m[2].startsWith('duration') ? 'duration-base' : 'ease-base';
|
|
104
|
+
out.push({
|
|
105
|
+
index,
|
|
106
|
+
length: cls.length,
|
|
107
|
+
message: `\`${cls}\` pins timing the motion axis owns — use \`${fix}\`. Hardcoded timing ignores data-motion="instant", which is also what prefers-reduced-motion resolves to, so this animates for people who asked it not to.`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
* moment it exists, with no export map to maintain — and it means no component
|
|
30
30
|
* can ever be pulled in by accident, because nothing re-exports them together.
|
|
31
31
|
*/
|
|
32
|
-
export { cn, tv, hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey, type KeyBindingMap, type KeyBindingPress, type KeyBindingOptions, type PartProps, type VariantProps } from './utils/index.js';
|
|
32
|
+
export { cn, tv, hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey, type KeyBindingMap, type KeyBindingPress, type KeyBindingOptions, type PartProps, type OwnedPartProps, type VariantProps } from './utils/index.js';
|
|
33
33
|
export { modes, defaultMode, surfaces, defaultSurface, surfaceRegistry, corners, defaultCorners, fonts, defaultFont, motions, defaultMotion, axisRegistry, axisKeys, type Mode, type Surface, type SurfaceMeta, type Corners, type Font, type Motion, type AxisKey, type AxisMeta, type AxisLevel } from './ui/provider/index.js';
|
|
34
34
|
export { Mode as ModeProvider } from './ui/mode/index.js';
|
package/dist/os/desktop.svelte
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
let {
|
|
10
10
|
pm: wmProp,
|
|
11
11
|
notifications: notifProp,
|
|
12
|
+
embedded = false,
|
|
12
13
|
children,
|
|
13
14
|
class: className,
|
|
14
15
|
...restProps
|
|
@@ -18,6 +19,22 @@
|
|
|
18
19
|
pm?: PaneManager;
|
|
19
20
|
/** The notification service, likewise shared via context. */
|
|
20
21
|
notifications?: NotificationService;
|
|
22
|
+
/**
|
|
23
|
+
* This Desktop is a WIDGET inside an ordinary page, not the viewport.
|
|
24
|
+
*
|
|
25
|
+
* A full-viewport Desktop takes the document with it: it locks
|
|
26
|
+
* <html>/<body> against scrolling and fences pinch-zoom, both of which are
|
|
27
|
+
* correct when the workspace IS the page and hostile when it is a 26rem
|
|
28
|
+
* box in the middle of a marketing site. Shipped exactly that way — a
|
|
29
|
+
* consumer embedded one and their whole page stopped scrolling.
|
|
30
|
+
*
|
|
31
|
+
* Named for the situation rather than the mechanism. `lockScroll={false}`
|
|
32
|
+
* was the obvious spelling and is the wrong one: more than one behaviour
|
|
33
|
+
* differs for an embedded Desktop, so a mechanism-named boolean needs a
|
|
34
|
+
* second boolean the first time that is true — and a consumer would have
|
|
35
|
+
* to know which mechanisms to disable rather than what they are building.
|
|
36
|
+
*/
|
|
37
|
+
embedded?: boolean;
|
|
21
38
|
/** Chrome + wallpaper: place <Taskbar>, <Toaster>, <Launcher>,
|
|
22
39
|
* <NotificationCenter> and any desktop widgets here. */
|
|
23
40
|
children?: Snippet;
|
|
@@ -68,16 +85,23 @@
|
|
|
68
85
|
untrack(() => pm.relayout());
|
|
69
86
|
});
|
|
70
87
|
|
|
71
|
-
// Document scroll lock:
|
|
72
|
-
// never scroll, rubber-band, or pull-to-refresh — on a phone
|
|
73
|
-
// whole workspace around, which is meaningless for a fixed
|
|
74
|
-
// <html>/<body> for the Desktop's lifetime and restore their
|
|
75
|
-
// values on unmount
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
88
|
+
// Document scroll lock: a full-viewport Desktop IS the viewport, so the page
|
|
89
|
+
// itself must never scroll, rubber-band, or pull-to-refresh — on a phone
|
|
90
|
+
// those drag the whole workspace around, which is meaningless for a fixed
|
|
91
|
+
// surface. Lock <html>/<body> for the Desktop's lifetime and restore their
|
|
92
|
+
// prior inline values on unmount. Scrolling INSIDE panes and menus is
|
|
93
|
+
// unaffected — those are their own overflow-auto containers with
|
|
94
|
+
// overscroll-behavior. overscroll-behavior:none is the specific killer for
|
|
95
|
+
// iOS pull-to-refresh and the elastic bounce; overflow:hidden + height stop
|
|
96
|
+
// any residual page scroll.
|
|
97
|
+
//
|
|
98
|
+
// NOT WHEN EMBEDDED. This comment used to say the consumer's ordinary page is
|
|
99
|
+
// untouched "before and after the workspace mounts", which is true and reads
|
|
100
|
+
// as broader than it is — DURING was the whole problem. A Desktop embedded as
|
|
101
|
+
// a widget in a scrolling page froze that page for its entire lifetime, and
|
|
102
|
+
// nothing in the API suggested a component would take the document with it.
|
|
80
103
|
$effect(() => {
|
|
104
|
+
if (embedded) return;
|
|
81
105
|
const els = [document.documentElement, document.body];
|
|
82
106
|
const lock = { overflow: 'hidden', overscrollBehavior: 'none', height: '100%' } as const;
|
|
83
107
|
const keys = Object.keys(lock) as (keyof typeof lock)[];
|
|
@@ -93,9 +117,16 @@
|
|
|
93
117
|
// chrome away and strand panes). Safari fires non-standard gesture* events
|
|
94
118
|
// for pinch/rotate; other engines surface it as a multi-touch touchmove.
|
|
95
119
|
// Cancel both — non-passive, so preventDefault actually stops the gesture.
|
|
96
|
-
// (Single-finger pane dragging runs on pointer events and is unaffected;
|
|
97
|
-
//
|
|
120
|
+
// (Single-finger pane dragging runs on pointer events and is unaffected; a
|
|
121
|
+
// full-viewport Desktop cannot scroll the page anyway, see the scroll lock.)
|
|
122
|
+
//
|
|
123
|
+
// ALSO NOT WHEN EMBEDDED, and this half is an accessibility question rather
|
|
124
|
+
// than a layout one. Pinch-zoom is how people with low vision read a page;
|
|
125
|
+
// swallowing it because the finger landed on a widget takes that away from
|
|
126
|
+
// the whole document. The parenthetical above is the tell — it assumed the
|
|
127
|
+
// page could not scroll, which stops being true the moment the lock is off.
|
|
98
128
|
$effect(() => {
|
|
129
|
+
if (embedded) return;
|
|
99
130
|
const el = ref;
|
|
100
131
|
if (!el) return;
|
|
101
132
|
const stop = (e: Event) => e.preventDefault();
|
|
@@ -8,6 +8,22 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
8
8
|
pm?: PaneManager;
|
|
9
9
|
/** The notification service, likewise shared via context. */
|
|
10
10
|
notifications?: NotificationService;
|
|
11
|
+
/**
|
|
12
|
+
* This Desktop is a WIDGET inside an ordinary page, not the viewport.
|
|
13
|
+
*
|
|
14
|
+
* A full-viewport Desktop takes the document with it: it locks
|
|
15
|
+
* <html>/<body> against scrolling and fences pinch-zoom, both of which are
|
|
16
|
+
* correct when the workspace IS the page and hostile when it is a 26rem
|
|
17
|
+
* box in the middle of a marketing site. Shipped exactly that way — a
|
|
18
|
+
* consumer embedded one and their whole page stopped scrolling.
|
|
19
|
+
*
|
|
20
|
+
* Named for the situation rather than the mechanism. `lockScroll={false}`
|
|
21
|
+
* was the obvious spelling and is the wrong one: more than one behaviour
|
|
22
|
+
* differs for an embedded Desktop, so a mechanism-named boolean needs a
|
|
23
|
+
* second boolean the first time that is true — and a consumer would have
|
|
24
|
+
* to know which mechanisms to disable rather than what they are building.
|
|
25
|
+
*/
|
|
26
|
+
embedded?: boolean;
|
|
11
27
|
/** Chrome + wallpaper: place <Taskbar>, <Toaster>, <Launcher>,
|
|
12
28
|
* <NotificationCenter> and any desktop widgets here. */
|
|
13
29
|
children?: Snippet;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Accordion } from 'bits-ui';
|
|
3
3
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
4
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
5
5
|
|
|
6
6
|
let {
|
|
7
7
|
ref = $bindable(null),
|
|
8
8
|
children,
|
|
9
9
|
class: className,
|
|
10
10
|
...restProps
|
|
11
|
-
}:
|
|
11
|
+
}: OwnedPartProps<Accordion.TriggerProps> = $props();
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<Accordion.Trigger
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Accordion } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const AccordionTrigger: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const AccordionTrigger: import("svelte").Component<OwnedPartProps<Accordion.TriggerProps>, {}, "ref">;
|
|
4
4
|
type AccordionTrigger = ReturnType<typeof AccordionTrigger>;
|
|
5
5
|
export default AccordionTrigger;
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
2
3
|
import { AlertDialog } from 'bits-ui';
|
|
3
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
7
8
|
children,
|
|
8
9
|
class: className,
|
|
9
10
|
...restProps
|
|
10
|
-
}:
|
|
11
|
+
}: OwnedPartProps<AlertDialog.ContentProps> = $props();
|
|
12
|
+
|
|
13
|
+
const relay = getAxisRelay();
|
|
11
14
|
</script>
|
|
12
15
|
|
|
13
16
|
<AlertDialog.Portal>
|
|
14
17
|
<AlertDialog.Overlay class="fixed inset-0 z-overlay bg-overlay" />
|
|
15
18
|
<AlertDialog.Content
|
|
16
19
|
bind:ref
|
|
20
|
+
{...relay?.attrs}
|
|
17
21
|
class={cn(
|
|
18
22
|
'fixed top-1/2 left-1/2 z-overlay flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-gutter rounded-bar border border-divider bg-well p-card shadow-lg glass-blur',
|
|
19
23
|
className
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
declare const AlertDialogContent: import("svelte").Component<
|
|
1
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
2
|
+
declare const AlertDialogContent: import("svelte").Component<OwnedPartProps<import("bits-ui").DialogContentProps>, {}, "ref">;
|
|
3
3
|
type AlertDialogContent = ReturnType<typeof AlertDialogContent>;
|
|
4
4
|
export default AlertDialogContent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { AlertDialog } from 'bits-ui';
|
|
4
|
+
import { setAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* era owns Root ONLY to open the axis channel — it renders no DOM, adds no
|
|
8
|
+
* behaviour and forwards everything. There has to BE a component here: a
|
|
9
|
+
* re-export (`export const Root = AlertDialog.Root`) has nowhere to put a context,
|
|
10
|
+
* which is exactly why the axes could not cross the portal before.
|
|
11
|
+
*
|
|
12
|
+
* `open` is re-declared bindable because a spread cannot carry a two-way
|
|
13
|
+
* binding, so wrapping a primitive breaks `bind:open` at every call site
|
|
14
|
+
* unless the wrapper states it.
|
|
15
|
+
*/
|
|
16
|
+
let {
|
|
17
|
+
open = $bindable(false),
|
|
18
|
+
children,
|
|
19
|
+
...restProps
|
|
20
|
+
}: AlertDialog.RootProps & { children?: Snippet } = $props();
|
|
21
|
+
|
|
22
|
+
const relay = setAxisRelay();
|
|
23
|
+
|
|
24
|
+
// Capture on OPEN, not on mount: bits-ui mounts content eagerly and toggles
|
|
25
|
+
// it, so a mount-time read runs once at page load and never again.
|
|
26
|
+
$effect(() => {
|
|
27
|
+
if (open) relay.refresh();
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<AlertDialog.Root bind:open {...restProps}>
|
|
32
|
+
{@render children?.()}
|
|
33
|
+
</AlertDialog.Root>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { AlertDialog } from 'bits-ui';
|
|
3
|
+
type $$ComponentProps = AlertDialog.RootProps & {
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const AlertDialogRoot: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
7
|
+
type AlertDialogRoot = ReturnType<typeof AlertDialogRoot>;
|
|
8
|
+
export default AlertDialogRoot;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import { AlertDialog, type WithoutChildrenOrChild } from 'bits-ui';
|
|
4
5
|
import { Button, type ButtonVariants } from '../button';
|
|
@@ -17,6 +18,13 @@
|
|
|
17
18
|
ButtonVariants & {
|
|
18
19
|
children?: Snippet;
|
|
19
20
|
} = $props();
|
|
21
|
+
|
|
22
|
+
// The trigger is the last node still inside the subtree, so it is the
|
|
23
|
+
// anchor the axes are read FROM when the content opens.
|
|
24
|
+
const relay = getAxisRelay();
|
|
25
|
+
$effect(() => {
|
|
26
|
+
relay?.register(ref);
|
|
27
|
+
});
|
|
20
28
|
</script>
|
|
21
29
|
|
|
22
30
|
<AlertDialog.Trigger {...restProps}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AlertDialog } from 'bits-ui';
|
|
2
|
-
export
|
|
2
|
+
export { default as Root } from './alert-dialog-root.svelte';
|
|
3
3
|
export declare const Portal: typeof AlertDialog.Portal;
|
|
4
4
|
export { default as Trigger } from './alert-dialog-trigger.svelte';
|
|
5
5
|
export { default as Content } from './alert-dialog-content.svelte';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AlertDialog } from 'bits-ui';
|
|
2
|
-
export
|
|
2
|
+
export { default as Root } from './alert-dialog-root.svelte';
|
|
3
3
|
export const Portal = AlertDialog.Portal;
|
|
4
4
|
export { default as Trigger } from './alert-dialog-trigger.svelte';
|
|
5
5
|
export { default as Content } from './alert-dialog-content.svelte';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Combobox } from 'bits-ui';
|
|
3
3
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
4
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
5
5
|
|
|
6
6
|
let {
|
|
7
7
|
ref = $bindable(null),
|
|
8
8
|
children,
|
|
9
9
|
class: className,
|
|
10
10
|
...restProps
|
|
11
|
-
}:
|
|
11
|
+
}: OwnedPartProps<Combobox.TriggerProps> = $props();
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<Combobox.Trigger
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Combobox } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const ComboboxTrigger: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const ComboboxTrigger: import("svelte").Component<OwnedPartProps<Combobox.TriggerProps>, {}, "ref">;
|
|
4
4
|
type ComboboxTrigger = ReturnType<typeof ComboboxTrigger>;
|
|
5
5
|
export default ComboboxTrigger;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
2
3
|
import { ContextMenu } from 'bits-ui';
|
|
3
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
@@ -8,12 +9,15 @@
|
|
|
8
9
|
class: className,
|
|
9
10
|
sideOffset = 4,
|
|
10
11
|
...restProps
|
|
11
|
-
}:
|
|
12
|
+
}: OwnedPartProps<ContextMenu.ContentProps> = $props();
|
|
13
|
+
|
|
14
|
+
const relay = getAxisRelay();
|
|
12
15
|
</script>
|
|
13
16
|
|
|
14
17
|
<ContextMenu.Portal>
|
|
15
18
|
<ContextMenu.Content
|
|
16
19
|
bind:ref
|
|
20
|
+
{...relay?.attrs}
|
|
17
21
|
align="start"
|
|
18
22
|
// p-menu, SURFACE-owned on purpose: 0 on flat — rows run the
|
|
19
23
|
// panel's full width, flush, and the first row sits directly under the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
declare const ContextMenuContent: import("svelte").Component<
|
|
1
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
2
|
+
declare const ContextMenuContent: import("svelte").Component<OwnedPartProps<import("bits-ui").DropdownMenuContentProps>, {}, "ref">;
|
|
3
3
|
type ContextMenuContent = ReturnType<typeof ContextMenuContent>;
|
|
4
4
|
export default ContextMenuContent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { ContextMenu } from 'bits-ui';
|
|
4
|
+
import { setAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* era owns Root ONLY to open the axis channel — it renders no DOM, adds no
|
|
8
|
+
* behaviour and forwards everything. There has to BE a component here: a
|
|
9
|
+
* re-export (`export const Root = ContextMenu.Root`) has nowhere to put a context,
|
|
10
|
+
* which is exactly why the axes could not cross the portal before.
|
|
11
|
+
*
|
|
12
|
+
* `open` is re-declared bindable because a spread cannot carry a two-way
|
|
13
|
+
* binding, so wrapping a primitive breaks `bind:open` at every call site
|
|
14
|
+
* unless the wrapper states it.
|
|
15
|
+
*/
|
|
16
|
+
let {
|
|
17
|
+
open = $bindable(false),
|
|
18
|
+
children,
|
|
19
|
+
...restProps
|
|
20
|
+
}: ContextMenu.RootProps & { children?: Snippet } = $props();
|
|
21
|
+
|
|
22
|
+
const relay = setAxisRelay();
|
|
23
|
+
|
|
24
|
+
// Capture on OPEN, not on mount: bits-ui mounts content eagerly and toggles
|
|
25
|
+
// it, so a mount-time read runs once at page load and never again.
|
|
26
|
+
$effect(() => {
|
|
27
|
+
if (open) relay.refresh();
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<ContextMenu.Root bind:open {...restProps}>
|
|
32
|
+
{@render children?.()}
|
|
33
|
+
</ContextMenu.Root>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { ContextMenu } from 'bits-ui';
|
|
3
|
+
type $$ComponentProps = ContextMenu.RootProps & {
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const ContextMenuRoot: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
7
|
+
type ContextMenuRoot = ReturnType<typeof ContextMenuRoot>;
|
|
8
|
+
export default ContextMenuRoot;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getAxisRelay } from '../provider/axis-relay.svelte.js';
|
|
2
3
|
import { ContextMenu } from 'bits-ui';
|
|
3
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
ref = $bindable(null),
|
|
@@ -8,12 +9,15 @@
|
|
|
8
9
|
class: className,
|
|
9
10
|
sideOffset = 4,
|
|
10
11
|
...restProps
|
|
11
|
-
}:
|
|
12
|
+
}: OwnedPartProps<ContextMenu.SubContentProps> = $props();
|
|
13
|
+
|
|
14
|
+
const relay = getAxisRelay();
|
|
12
15
|
</script>
|
|
13
16
|
|
|
14
17
|
<ContextMenu.Portal>
|
|
15
18
|
<ContextMenu.SubContent
|
|
16
19
|
bind:ref
|
|
20
|
+
{...relay?.attrs}
|
|
17
21
|
class={cn(
|
|
18
22
|
'z-overlay w-max overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
|
|
19
23
|
className
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContextMenu } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const ContextMenuSubContent: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const ContextMenuSubContent: import("svelte").Component<OwnedPartProps<ContextMenu.SubContentProps>, {}, "ref">;
|
|
4
4
|
type ContextMenuSubContent = ReturnType<typeof ContextMenuSubContent>;
|
|
5
5
|
export default ContextMenuSubContent;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { ContextMenu } from 'bits-ui';
|
|
3
3
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
|
4
|
-
import { cn, type
|
|
4
|
+
import { cn, type OwnedPartProps } from '../../utils/index.js';
|
|
5
5
|
|
|
6
6
|
let {
|
|
7
7
|
ref = $bindable(null),
|
|
8
8
|
children,
|
|
9
9
|
class: className,
|
|
10
10
|
...restProps
|
|
11
|
-
}:
|
|
11
|
+
}: OwnedPartProps<ContextMenu.SubTriggerProps> = $props();
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<ContextMenu.SubTrigger
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContextMenu } from 'bits-ui';
|
|
2
|
-
import { type
|
|
3
|
-
declare const ContextMenuSubTrigger: import("svelte").Component<
|
|
2
|
+
import { type OwnedPartProps } from '../../utils/index.js';
|
|
3
|
+
declare const ContextMenuSubTrigger: import("svelte").Component<OwnedPartProps<ContextMenu.SubTriggerProps>, {}, "ref">;
|
|
4
4
|
type ContextMenuSubTrigger = ReturnType<typeof ContextMenuSubTrigger>;
|
|
5
5
|
export default ContextMenuSubTrigger;
|