@nubisco/ui 5.6.0 → 5.8.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/dist/Avatar-672086EP.js +57 -0
- package/dist/Avatar-DqHh71ha.cjs +1 -0
- package/dist/{Tree-DI_ouWnv.cjs → Tree-CMXvRX6y.cjs} +1 -1
- package/dist/{Tree-DHdPsRXV.js → Tree-Clpnh7Mh.js} +1 -1
- package/dist/TreeContext-51-0WkVM.cjs +1 -0
- package/dist/TreeContext-C77g-WGF.js +4 -0
- package/dist/TreeNode-DsOOkac_.js +171 -0
- package/dist/TreeNode-n65SoYln.cjs +1 -0
- package/dist/all.cjs +1 -1
- package/dist/all.mjs +17 -16
- package/dist/components/Avatar.cjs +1 -1
- package/dist/components/Avatar.d.ts +19 -2
- package/dist/components/Avatar.mjs +1 -1
- package/dist/components/Avatar.vue.d.ts +3 -1
- package/dist/components/Avatar.vue.d.ts.map +1 -1
- package/dist/components/Tree.cjs +1 -1
- package/dist/components/Tree.d.ts +2 -3
- package/dist/components/Tree.mjs +1 -1
- package/dist/components/TreeContext.d.ts +5 -0
- package/dist/components/TreeContext.d.ts.map +1 -1
- package/dist/components/TreeNode.cjs +1 -1
- package/dist/components/TreeNode.mjs +1 -1
- package/dist/components/TreeNode.vue.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +12 -11
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/styles/TreeNode.css +1 -1
- package/dist/ui.css +1 -1
- package/dist/utils/treeMove.helper.cjs +1 -0
- package/dist/utils/treeMove.helper.d.ts +87 -0
- package/dist/utils/treeMove.helper.d.ts.map +1 -0
- package/dist/utils/treeMove.helper.mjs +86 -0
- package/package.json +1 -1
- package/src/styles/_accent.scss +75 -0
- package/src/styles/_theme-api.scss +119 -1
- package/src/styles/_theme.scss +5 -33
- package/src/styles/variables/_categories.scss +169 -0
- package/src/styles/variables/index.scss +1 -0
- package/src/utils/treeMove.helper.ts +223 -0
- package/dist/Avatar-C6pCz1lv.cjs +0 -1
- package/dist/Avatar-DdI07lRT.js +0 -47
- package/dist/TreeContext-BH2qpPud.cjs +0 -1
- package/dist/TreeContext-DMADOfuO.js +0 -4
- package/dist/TreeNode-CME77z98.js +0 -165
- package/dist/TreeNode-CpV3GOzv.cjs +0 -1
package/src/styles/_theme.scss
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@use 'sass:color';
|
|
2
2
|
@use 'sass:map';
|
|
3
|
+
@use 'accent';
|
|
3
4
|
@use 'config';
|
|
4
5
|
@use 'variables/layout';
|
|
5
6
|
@use 'variables/colors';
|
|
@@ -158,11 +159,7 @@
|
|
|
158
159
|
// hover border visibly moves. The dark theme repeats the mix at 50%
|
|
159
160
|
// because its --nb-c-primary is already a lighter step of the same ramp
|
|
160
161
|
// and needs less white to land on the same wash (dE2000 0.00 there).
|
|
161
|
-
|
|
162
|
-
in srgb,
|
|
163
|
-
var(--nb-c-primary) 39%,
|
|
164
|
-
var(--nb-c-white)
|
|
165
|
-
);
|
|
162
|
+
@include accent.subtle(39%);
|
|
166
163
|
|
|
167
164
|
--nb-c-secondary: var(--nb-c-plain-black-700);
|
|
168
165
|
--nb-c-secondary-a11y: var(--nb-c-plain-black-700-a11y);
|
|
@@ -375,7 +372,7 @@
|
|
|
375
372
|
--nb-c-component-discrete-text: var(--nb-c-nouveau-gray-700);
|
|
376
373
|
|
|
377
374
|
// Focus ring — used by all interactive controls via :focus-visible
|
|
378
|
-
|
|
375
|
+
@include accent.focus-ring;
|
|
379
376
|
|
|
380
377
|
// ── Scrim / overlay ───────────────────────────────────────────────────
|
|
381
378
|
// The dim laid over the page by anything that takes over the viewport:
|
|
@@ -588,11 +585,7 @@
|
|
|
588
585
|
// Dark chrome derived from the brand: a near-black tint of --nb-c-primary so
|
|
589
586
|
// rebranding the primary colour re-tints the sidebar too (semantic, not a
|
|
590
587
|
// raw palette tint).
|
|
591
|
-
|
|
592
|
-
in srgb,
|
|
593
|
-
var(--nb-c-primary) 20%,
|
|
594
|
-
var(--nb-c-black)
|
|
595
|
-
);
|
|
588
|
+
@include accent.shell;
|
|
596
589
|
--nb-shell-sidebar-width: calc(var(--nb-base-unit) * 7); // 56px
|
|
597
590
|
// The verbose variant's rail width. A token rather than a literal inside
|
|
598
591
|
// Shell.vue's variant rule, so a product with long navigation labels can
|
|
@@ -613,19 +606,6 @@
|
|
|
613
606
|
var(--nb-c-white) 90%,
|
|
614
607
|
transparent
|
|
615
608
|
);
|
|
616
|
-
--nb-shell-sidebar-link-active-bg: color-mix(
|
|
617
|
-
in srgb,
|
|
618
|
-
var(--nb-c-primary) 25%,
|
|
619
|
-
transparent
|
|
620
|
-
);
|
|
621
|
-
// Light tint of --nb-c-primary: keeps the accent on-brand and rebrandable
|
|
622
|
-
// while staying legible (~6:1) on the dark rail, where --nb-c-primary itself
|
|
623
|
-
// is too dark (~2.5:1).
|
|
624
|
-
--nb-shell-sidebar-link-active-color: color-mix(
|
|
625
|
-
in srgb,
|
|
626
|
-
var(--nb-c-primary) 60%,
|
|
627
|
-
var(--nb-c-white)
|
|
628
|
-
);
|
|
629
609
|
--nb-shell-topbar-height: calc(var(--nb-base-unit) * 6.25); // 50px
|
|
630
610
|
|
|
631
611
|
// Shell chrome. Every rule that paints the frame reads one of these, so a
|
|
@@ -672,10 +652,6 @@
|
|
|
672
652
|
--nb-shell-inspector-max-width: max(calc(var(--nb-base-unit) * 36), 50vw);
|
|
673
653
|
--nb-shell-inspector-border: 1px solid var(--nb-c-border);
|
|
674
654
|
--nb-shell-inspector-bg: var(--nb-c-surface);
|
|
675
|
-
// The grab-line on the inspector's resize handle. A token, not a literal
|
|
676
|
-
// brand hex: a white-label product should never have to name the vendor's
|
|
677
|
-
// ramp to restyle the frame.
|
|
678
|
-
--nb-shell-inspector-resize-color: var(--nb-c-primary);
|
|
679
655
|
// The dimmer behind the navigation drawer at collapsed widths. Black at
|
|
680
656
|
// 45% reads on both themes because it darkens whatever is under it rather
|
|
681
657
|
// than painting a colour of its own.
|
|
@@ -707,11 +683,7 @@
|
|
|
707
683
|
--nb-c-primary: #{colors.get-color('grape-hyacinth', 400)};
|
|
708
684
|
// Same wash, re-mixed: the dark --nb-c-primary is a 400, so it takes less
|
|
709
685
|
// white than the light 500 does to reach the tint this token used to alias.
|
|
710
|
-
|
|
711
|
-
in srgb,
|
|
712
|
-
var(--nb-c-primary) 50%,
|
|
713
|
-
var(--nb-c-white)
|
|
714
|
-
);
|
|
686
|
+
@include accent.subtle(50%);
|
|
715
687
|
--nb-c-primary-a11y: #{colors.color-contrast(
|
|
716
688
|
colors.get-color('grape-hyacinth', 400)
|
|
717
689
|
)};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
@use 'sass:list';
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use 'sass:meta';
|
|
4
|
+
@use '../logic/a11y' as a11y;
|
|
5
|
+
@use 'layers';
|
|
6
|
+
|
|
7
|
+
/// The audience categories, and the accent pair each one resolves to.
|
|
8
|
+
///
|
|
9
|
+
/// Nubisco groups its products by audience: creatives, engineers, home, fun.
|
|
10
|
+
/// These are the four accents that grouping resolves to, each a
|
|
11
|
+
/// `(light, dark)` pair.
|
|
12
|
+
///
|
|
13
|
+
/// ## What is deliberately not here
|
|
14
|
+
///
|
|
15
|
+
/// There is no product list. The library holds the four values and nothing
|
|
16
|
+
/// else: it does not know which product belongs to which category, and it must
|
|
17
|
+
/// not. A registry of product ids here would mean cutting a library release
|
|
18
|
+
/// every time a product is added or renamed, and it would invert the
|
|
19
|
+
/// dependency, making the design system know about the things that consume it.
|
|
20
|
+
/// An app names its own category when it themes itself, so a new product needs
|
|
21
|
+
/// no change to this file. The product-to-category map is a portfolio fact and
|
|
22
|
+
/// lives in the workspace standards.
|
|
23
|
+
///
|
|
24
|
+
/// ## Why these exact values
|
|
25
|
+
///
|
|
26
|
+
/// Every value clears WCAG AA (4.5:1) against all four surface layers of the
|
|
27
|
+
/// mode it belongs to. That is a stricter bar than measuring against pure
|
|
28
|
+
/// white and pure black, and it is the one that matters: the library never
|
|
29
|
+
/// renders a pure-white surface. Light accents are measured against the
|
|
30
|
+
/// darkest light layer (`layer-2`, `#e7e8e9`) and dark accents against the
|
|
31
|
+
/// lightest dark layer (`layer-3`, `#3d3e3f`), because those are the worst
|
|
32
|
+
/// cases. `tests/categoryContrast.test.ts` measures the compiled values and
|
|
33
|
+
/// fails if an edit drops one below the bar.
|
|
34
|
+
///
|
|
35
|
+
/// Hover and active are the adjacent steps of the same ramp, darker in light
|
|
36
|
+
/// mode and lighter in dark, so both are strictly further from the surface
|
|
37
|
+
/// than the base. The base is therefore the worst case, and testing it is
|
|
38
|
+
/// enough.
|
|
39
|
+
///
|
|
40
|
+
/// @type Map
|
|
41
|
+
/// @access public
|
|
42
|
+
/// @group @nubisco/colors
|
|
43
|
+
$categories: (
|
|
44
|
+
creatives: (
|
|
45
|
+
#846201,
|
|
46
|
+
#e8c15a,
|
|
47
|
+
),
|
|
48
|
+
engineers: (
|
|
49
|
+
#5b48c9,
|
|
50
|
+
#a99dff,
|
|
51
|
+
),
|
|
52
|
+
home: (
|
|
53
|
+
#086f9e,
|
|
54
|
+
#5ec1e8,
|
|
55
|
+
),
|
|
56
|
+
fun: (
|
|
57
|
+
#b83266,
|
|
58
|
+
#f686ae,
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
/// The `(light, dark)` accent pair for a category.
|
|
63
|
+
///
|
|
64
|
+
/// An unknown name is a build error rather than a silent fallback: a typo that
|
|
65
|
+
/// quietly resolved to the default accent would ship a product in the wrong
|
|
66
|
+
/// colour and nothing would say so.
|
|
67
|
+
///
|
|
68
|
+
/// @param {String} $name - a key of `$categories`
|
|
69
|
+
/// @return {List} the `(light, dark)` pair
|
|
70
|
+
/// @access public
|
|
71
|
+
/// @group @nubisco/colors
|
|
72
|
+
@function category($name) {
|
|
73
|
+
@if not map.has-key($categories, $name) {
|
|
74
|
+
@error "Unknown category #{$name}. Expected one of: #{map.keys($categories)}.";
|
|
75
|
+
}
|
|
76
|
+
@return map.get($categories, $name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Validates a `(light, dark)` accent pair supplied by an app.
|
|
80
|
+
///
|
|
81
|
+
/// An app with an established identity of its own passes its own pair rather
|
|
82
|
+
/// than taking its category's. This checks the shape so a malformed value
|
|
83
|
+
/// fails at build time instead of emitting nonsense custom properties.
|
|
84
|
+
///
|
|
85
|
+
/// @param {List} $accent - a two-item list of colours
|
|
86
|
+
/// @return {List} the same pair, once validated
|
|
87
|
+
/// @access public
|
|
88
|
+
/// @group @nubisco/colors
|
|
89
|
+
@function accent-pair($accent) {
|
|
90
|
+
@if meta.type-of($accent) != list or list.length($accent) != 2 {
|
|
91
|
+
@error "An accent must be a (light, dark) pair of colours, got #{$accent}.";
|
|
92
|
+
}
|
|
93
|
+
@each $value in $accent {
|
|
94
|
+
@if meta.type-of($value) != color {
|
|
95
|
+
@error "An accent pair must hold colours, got #{$value}.";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
@return $accent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// The surfaces an accent is measured against, per mode.
|
|
102
|
+
///
|
|
103
|
+
/// These are the library's own layers, resolved from
|
|
104
|
+
/// `variables/_layers.scss`, not pure white and pure black. That distinction
|
|
105
|
+
/// is the point: an accent verified against a surface the product never
|
|
106
|
+
/// renders has not been verified. The worst case is the darkest light layer
|
|
107
|
+
/// and the lightest dark layer, and both are in here.
|
|
108
|
+
///
|
|
109
|
+
/// @type Map
|
|
110
|
+
/// @access private
|
|
111
|
+
/// @group @nubisco/colors
|
|
112
|
+
/// Read from the layer ramps rather than restated, so a change to the surface
|
|
113
|
+
/// system is measured against automatically instead of silently invalidating
|
|
114
|
+
/// numbers copied out of it.
|
|
115
|
+
$_grounds: (
|
|
116
|
+
light: (
|
|
117
|
+
layers.level(layers.$light, surface, 0),
|
|
118
|
+
layers.level(layers.$light, surface, 1),
|
|
119
|
+
layers.level(layers.$light, surface, 2),
|
|
120
|
+
layers.level(layers.$light, surface, 3),
|
|
121
|
+
),
|
|
122
|
+
dark: (
|
|
123
|
+
layers.level(layers.$dark, surface, 0),
|
|
124
|
+
layers.level(layers.$dark, surface, 1),
|
|
125
|
+
layers.level(layers.$dark, surface, 2),
|
|
126
|
+
layers.level(layers.$dark, surface, 3),
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
/// Fails the build if an accent pair does not clear WCAG AA on every surface.
|
|
131
|
+
///
|
|
132
|
+
/// The library measures the accents it ships, in
|
|
133
|
+
/// `tests/categoryContrast.test.ts`. It cannot measure an accent an app
|
|
134
|
+
/// supplies, because it never sees it. An app that overrides its accent calls
|
|
135
|
+
/// this so its own build holds it to the same bar.
|
|
136
|
+
///
|
|
137
|
+
/// ```scss
|
|
138
|
+
/// @debug nb.assert-accent-contrast((#0b7285, #41d6e0));
|
|
139
|
+
/// ```
|
|
140
|
+
///
|
|
141
|
+
/// @param {List} $accent - a `(light, dark)` pair
|
|
142
|
+
/// @param {Number} $minimum - the ratio to clear, 4.5 by default
|
|
143
|
+
/// @return {String} a summary, so the call reads as an assertion that returns
|
|
144
|
+
/// @access public
|
|
145
|
+
/// @group @nubisco/colors
|
|
146
|
+
@function assert-accent-contrast($accent, $minimum: 4.5) {
|
|
147
|
+
$pair: accent-pair($accent);
|
|
148
|
+
$worst: 21;
|
|
149
|
+
|
|
150
|
+
@each $mode, $grounds in $_grounds {
|
|
151
|
+
$value: list.nth($pair, 2);
|
|
152
|
+
$direction: 'lighter';
|
|
153
|
+
@if $mode == light {
|
|
154
|
+
$value: list.nth($pair, 1);
|
|
155
|
+
$direction: 'darker';
|
|
156
|
+
}
|
|
157
|
+
@each $ground in $grounds {
|
|
158
|
+
$ratio: a11y.contrast-ratio($value, $ground);
|
|
159
|
+
@if $ratio < $minimum {
|
|
160
|
+
@error "Accent #{$value} is #{$ratio}:1 on #{$mode} surface #{$ground}, below #{$minimum}:1. Pick a #{$direction} value.";
|
|
161
|
+
}
|
|
162
|
+
@if $ratio < $worst {
|
|
163
|
+
$worst: $ratio;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@return 'accent clears #{$minimum}:1 on every surface (worst #{$worst}:1)';
|
|
169
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moving a node around a tree, as data.
|
|
3
|
+
*
|
|
4
|
+
* Dragging a row and a "Move to" picker ask the same questions, and neither of
|
|
5
|
+
* them is about the DOM: where may this node go, and what does the tree look
|
|
6
|
+
* like afterwards. Every product that reorders a tree writes this again, and
|
|
7
|
+
* the one rule that is easy to miss is the one that does real damage: a node
|
|
8
|
+
* dropped into its own subtree detaches that subtree from the root, and
|
|
9
|
+
* everything under it disappears.
|
|
10
|
+
*
|
|
11
|
+
* These work on any tree of `{ id, children }` and never touch the tree they
|
|
12
|
+
* are given: a plan is a new tree of shallow copies, so every field a product
|
|
13
|
+
* carries on its nodes comes through. `NbTree` enforces the same rule for
|
|
14
|
+
* pointer drags, so a product that uses both gets one answer.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** The shape these read: an id, and children when it has any. */
|
|
18
|
+
export interface ITreeMoveNode {
|
|
19
|
+
id: string
|
|
20
|
+
children?: ITreeMoveNode[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Where a node goes: next to another node, inside it, or at the top level. */
|
|
24
|
+
export type TTreeMovePlacement =
|
|
25
|
+
| { kind: 'inside'; target: string }
|
|
26
|
+
| { kind: 'before'; target: string }
|
|
27
|
+
| { kind: 'after'; target: string }
|
|
28
|
+
| { kind: 'root' }
|
|
29
|
+
|
|
30
|
+
export interface ITreeMovePlan<T extends ITreeMoveNode> {
|
|
31
|
+
/** The tree after the move, as shallow copies. The original is untouched. */
|
|
32
|
+
tree: T[]
|
|
33
|
+
/** The node's new parent, or null at the top level. */
|
|
34
|
+
parentId: string | null
|
|
35
|
+
/** Its index among its new siblings. */
|
|
36
|
+
index: number
|
|
37
|
+
/** The sibling it now follows, or null when it is first. */
|
|
38
|
+
afterId: string | null
|
|
39
|
+
/** The sibling it now precedes, or null when it is last. */
|
|
40
|
+
beforeId: string | null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ILocated<T extends ITreeMoveNode> {
|
|
44
|
+
node: T
|
|
45
|
+
siblings: T[]
|
|
46
|
+
parent: T | null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function childrenOf<T extends ITreeMoveNode>(node: T): T[] {
|
|
50
|
+
return (node.children ?? []) as T[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function locate<T extends ITreeMoveNode>(
|
|
54
|
+
nodes: T[],
|
|
55
|
+
id: string,
|
|
56
|
+
parent: T | null = null,
|
|
57
|
+
): ILocated<T> | null {
|
|
58
|
+
for (const node of nodes) {
|
|
59
|
+
if (node.id === id) return { node, siblings: nodes, parent }
|
|
60
|
+
const found = locate(childrenOf(node), id, node)
|
|
61
|
+
if (found) return found
|
|
62
|
+
}
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The ids of the nodes above `id`, outermost first. Empty for a root node. */
|
|
67
|
+
export function ancestorsOf<T extends ITreeMoveNode>(
|
|
68
|
+
nodes: T[],
|
|
69
|
+
id: string,
|
|
70
|
+
): string[] {
|
|
71
|
+
const walk = (list: T[], trail: string[]): string[] | null => {
|
|
72
|
+
for (const node of list) {
|
|
73
|
+
if (node.id === id) return trail
|
|
74
|
+
const found = walk(childrenOf(node), [...trail, node.id])
|
|
75
|
+
if (found) return found
|
|
76
|
+
}
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
return walk(nodes, []) ?? []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `id` and every node under it. Empty when `id` is not in the tree. */
|
|
83
|
+
export function subtreeOf<T extends ITreeMoveNode>(
|
|
84
|
+
nodes: T[],
|
|
85
|
+
id: string,
|
|
86
|
+
): Set<string> {
|
|
87
|
+
const out = new Set<string>()
|
|
88
|
+
const found = locate(nodes, id)
|
|
89
|
+
const walk = (node: T) => {
|
|
90
|
+
out.add(node.id)
|
|
91
|
+
childrenOf(node).forEach(walk)
|
|
92
|
+
}
|
|
93
|
+
if (found) walk(found.node)
|
|
94
|
+
return out
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether `target` is `source` itself or somewhere under it, which is the one
|
|
99
|
+
* place a node can never go.
|
|
100
|
+
*/
|
|
101
|
+
export function isInvalidTarget<T extends ITreeMoveNode>(
|
|
102
|
+
nodes: T[],
|
|
103
|
+
source: string,
|
|
104
|
+
target: string,
|
|
105
|
+
): boolean {
|
|
106
|
+
return subtreeOf(nodes, source).has(target)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A node `source` may move to, in reading order, with its nesting depth. */
|
|
110
|
+
export interface ITreeMoveTarget<T extends ITreeMoveNode> {
|
|
111
|
+
id: string
|
|
112
|
+
depth: number
|
|
113
|
+
node: T
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Every node `source` may move to: all of them but itself and its own subtree.
|
|
118
|
+
* The list a "Move to" picker offers.
|
|
119
|
+
*/
|
|
120
|
+
export function moveTargets<T extends ITreeMoveNode>(
|
|
121
|
+
nodes: T[],
|
|
122
|
+
source: string,
|
|
123
|
+
): ITreeMoveTarget<T>[] {
|
|
124
|
+
const out: ITreeMoveTarget<T>[] = []
|
|
125
|
+
const walk = (list: T[], depth: number) => {
|
|
126
|
+
for (const node of list) {
|
|
127
|
+
if (node.id === source) continue
|
|
128
|
+
out.push({ id: node.id, depth, node })
|
|
129
|
+
walk(childrenOf(node), depth + 1)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
walk(nodes, 0)
|
|
133
|
+
return out
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The tree after moving `source` to `placement`, or null when the move is
|
|
138
|
+
* refused (into its own subtree, or a node or target that is not there) or
|
|
139
|
+
* would change nothing.
|
|
140
|
+
*
|
|
141
|
+
* "Inside" appends as the last child, so dragging a node onto its own current
|
|
142
|
+
* parent moves it to the end rather than leaving it where it was. The returned
|
|
143
|
+
* `parentId`, `index`, `afterId` and `beforeId` describe the new position, for
|
|
144
|
+
* a product to turn into whatever its own storage records.
|
|
145
|
+
*/
|
|
146
|
+
export function planTreeMove<T extends ITreeMoveNode>(
|
|
147
|
+
nodes: T[],
|
|
148
|
+
source: string,
|
|
149
|
+
placement: TTreeMovePlacement,
|
|
150
|
+
): ITreeMovePlan<T> | null {
|
|
151
|
+
const from = locate(nodes, source)
|
|
152
|
+
if (!from) return null
|
|
153
|
+
if (placement.kind !== 'root') {
|
|
154
|
+
if (isInvalidTarget(nodes, source, placement.target)) return null
|
|
155
|
+
if (!locate(nodes, placement.target)) return null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const clone = (list: T[]): T[] =>
|
|
159
|
+
list.map((node) =>
|
|
160
|
+
node.children === undefined
|
|
161
|
+
? ({ ...node } as T)
|
|
162
|
+
: ({ ...node, children: clone(childrenOf(node)) } as T),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const tree = clone(nodes)
|
|
166
|
+
const moving = locate(tree, source)!
|
|
167
|
+
moving.siblings.splice(moving.siblings.indexOf(moving.node), 1)
|
|
168
|
+
|
|
169
|
+
if (placement.kind === 'root' || placement.kind === 'inside') {
|
|
170
|
+
const parent =
|
|
171
|
+
placement.kind === 'root' ? null : locate(tree, placement.target)!.node
|
|
172
|
+
if (parent && parent.children === undefined) parent.children = []
|
|
173
|
+
const list = parent ? childrenOf(parent) : tree
|
|
174
|
+
list.push(moving.node)
|
|
175
|
+
} else {
|
|
176
|
+
const anchor = locate(tree, placement.target)!
|
|
177
|
+
const index = anchor.siblings.indexOf(anchor.node)
|
|
178
|
+
anchor.siblings.splice(
|
|
179
|
+
placement.kind === 'before' ? index : index + 1,
|
|
180
|
+
0,
|
|
181
|
+
moving.node,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const after = locate(tree, source)!
|
|
186
|
+
const index = after.siblings.indexOf(after.node)
|
|
187
|
+
const unchanged =
|
|
188
|
+
(after.parent?.id ?? null) === (from.parent?.id ?? null) &&
|
|
189
|
+
index === from.siblings.indexOf(from.node)
|
|
190
|
+
if (unchanged) return null
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
tree,
|
|
194
|
+
parentId: after.parent?.id ?? null,
|
|
195
|
+
index,
|
|
196
|
+
afterId: index > 0 ? after.siblings[index - 1].id : null,
|
|
197
|
+
beforeId:
|
|
198
|
+
index < after.siblings.length - 1 ? after.siblings[index + 1].id : null,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A flat list in reading order, each row carrying its nesting depth, rebuilt
|
|
204
|
+
* into a tree. The shape most APIs return a tree in.
|
|
205
|
+
*/
|
|
206
|
+
export function nestByDepth<T extends { id: string; depth: number }>(
|
|
207
|
+
rows: T[],
|
|
208
|
+
): (Omit<T, 'depth'> & { children: unknown[] })[] {
|
|
209
|
+
type TNested = Omit<T, 'depth'> & { children: TNested[] }
|
|
210
|
+
const roots: TNested[] = []
|
|
211
|
+
const stack: { node: TNested; depth: number }[] = []
|
|
212
|
+
for (const row of rows) {
|
|
213
|
+
const { depth, ...rest } = row
|
|
214
|
+
const node = { ...rest, children: [] } as unknown as TNested
|
|
215
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
216
|
+
stack.pop()
|
|
217
|
+
}
|
|
218
|
+
if (stack.length === 0) roots.push(node)
|
|
219
|
+
else stack[stack.length - 1].node.children.push(node)
|
|
220
|
+
stack.push({ node, depth })
|
|
221
|
+
}
|
|
222
|
+
return roots
|
|
223
|
+
}
|
package/dist/Avatar-C6pCz1lv.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
require(`./chunk-D-qHiVGv.cjs`);const e=require(`./utils/initials.helper.cjs`);let t=require(`vue`);var n=[`role`,`aria-label`,`aria-hidden`,`title`],r=[`src`],i={key:1,class:`nb-avatar__initials`,"aria-hidden":`true`},a=(0,t.defineComponent)({__name:`Avatar`,props:{name:{default:void 0},email:{default:void 0},picture:{default:void 0},size:{default:`md`},decorative:{type:Boolean,default:!1}},setup(a){let o=a,s=(0,t.ref)(!1);(0,t.watch)(()=>o.picture,()=>{s.value=!1});let c=(0,t.computed)(()=>!!o.picture&&!s.value),l=(0,t.computed)(()=>e.initialsOf(o)),u=(0,t.computed)(()=>o.name?.trim()||o.email?.trim()||``);return(e,o)=>((0,t.openBlock)(),(0,t.createElementBlock)(`span`,{class:(0,t.normalizeClass)([`nb-avatar`,[`nb-avatar--${a.size}`]]),role:a.decorative?void 0:`img`,"aria-label":a.decorative?void 0:u.value||void 0,"aria-hidden":a.decorative?`true`:void 0,title:a.decorative?void 0:u.value||void 0},[c.value?((0,t.openBlock)(),(0,t.createElementBlock)(`img`,{key:0,class:`nb-avatar__picture`,src:a.picture??void 0,alt:``,onError:o[0]||=e=>s.value=!0},null,40,r)):((0,t.openBlock)(),(0,t.createElementBlock)(`span`,i,(0,t.toDisplayString)(l.value),1))],10,n))}});Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return a}});
|
package/dist/Avatar-DdI07lRT.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { initialsOf as e } from "./utils/initials.helper.mjs";
|
|
2
|
-
import { computed as t, createElementBlock as n, defineComponent as r, normalizeClass as i, openBlock as a, ref as o, toDisplayString as s, watch as c } from "vue";
|
|
3
|
-
//#region src/components/Avatar.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
-
var l = [
|
|
5
|
-
"role",
|
|
6
|
-
"aria-label",
|
|
7
|
-
"aria-hidden",
|
|
8
|
-
"title"
|
|
9
|
-
], u = ["src"], d = {
|
|
10
|
-
key: 1,
|
|
11
|
-
class: "nb-avatar__initials",
|
|
12
|
-
"aria-hidden": "true"
|
|
13
|
-
}, f = /* @__PURE__ */ r({
|
|
14
|
-
__name: "Avatar",
|
|
15
|
-
props: {
|
|
16
|
-
name: { default: void 0 },
|
|
17
|
-
email: { default: void 0 },
|
|
18
|
-
picture: { default: void 0 },
|
|
19
|
-
size: { default: "md" },
|
|
20
|
-
decorative: {
|
|
21
|
-
type: Boolean,
|
|
22
|
-
default: !1
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
setup(r) {
|
|
26
|
-
let f = r, p = o(!1);
|
|
27
|
-
c(() => f.picture, () => {
|
|
28
|
-
p.value = !1;
|
|
29
|
-
});
|
|
30
|
-
let m = t(() => !!f.picture && !p.value), h = t(() => e(f)), g = t(() => f.name?.trim() || f.email?.trim() || "");
|
|
31
|
-
return (e, t) => (a(), n("span", {
|
|
32
|
-
class: i(["nb-avatar", [`nb-avatar--${r.size}`]]),
|
|
33
|
-
role: r.decorative ? void 0 : "img",
|
|
34
|
-
"aria-label": r.decorative ? void 0 : g.value || void 0,
|
|
35
|
-
"aria-hidden": r.decorative ? "true" : void 0,
|
|
36
|
-
title: r.decorative ? void 0 : g.value || void 0
|
|
37
|
-
}, [m.value ? (a(), n("img", {
|
|
38
|
-
key: 0,
|
|
39
|
-
class: "nb-avatar__picture",
|
|
40
|
-
src: r.picture ?? void 0,
|
|
41
|
-
alt: "",
|
|
42
|
-
onError: t[0] ||= (e) => p.value = !0
|
|
43
|
-
}, null, 40, u)) : (a(), n("span", d, s(h.value), 1))], 10, l));
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
//#endregion
|
|
47
|
-
export { f as t };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=Symbol(`nb-tree`),t=Symbol(`nb-tree-depth`);Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return t}});
|