cursedbelt 5.34.0 → 5.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 (61) hide show
  1. package/dist/react/file-tree/FileTree.d.ts +27 -1
  2. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  3. package/dist/react/file-tree/FileTree.js +183 -9
  4. package/dist/react/file-tree/FileTree.js.map +1 -1
  5. package/dist/react/file-tree/FolderViewMenus.d.ts +88 -0
  6. package/dist/react/file-tree/FolderViewMenus.d.ts.map +1 -0
  7. package/dist/react/file-tree/FolderViewMenus.js +121 -0
  8. package/dist/react/file-tree/FolderViewMenus.js.map +1 -0
  9. package/dist/react/file-tree/FolderViewPanels.d.ts +78 -0
  10. package/dist/react/file-tree/FolderViewPanels.d.ts.map +1 -0
  11. package/dist/react/file-tree/FolderViewPanels.js +155 -0
  12. package/dist/react/file-tree/FolderViewPanels.js.map +1 -0
  13. package/dist/react/file-tree/fileTree.css +146 -0
  14. package/dist/react/file-tree/folderView.d.ts +117 -0
  15. package/dist/react/file-tree/folderView.d.ts.map +1 -0
  16. package/dist/react/file-tree/folderView.js +230 -0
  17. package/dist/react/file-tree/folderView.js.map +1 -0
  18. package/dist/react/file-tree/folderViewPanels.css +217 -0
  19. package/dist/react/file-tree/index.d.ts +2 -0
  20. package/dist/react/file-tree/index.d.ts.map +1 -1
  21. package/dist/react/file-tree/index.js +9 -0
  22. package/dist/react/file-tree/index.js.map +1 -1
  23. package/dist/react/file-tree/useFolderView.d.ts +56 -0
  24. package/dist/react/file-tree/useFolderView.d.ts.map +1 -0
  25. package/dist/react/file-tree/useFolderView.js +79 -0
  26. package/dist/react/file-tree/useFolderView.js.map +1 -0
  27. package/dist/react/filter-rail/FilterRail.d.ts +7 -0
  28. package/dist/react/filter-rail/FilterRail.d.ts.map +1 -1
  29. package/dist/react/filter-rail/FilterRail.js +8 -1
  30. package/dist/react/filter-rail/FilterRail.js.map +1 -1
  31. package/dist/react/filter-rail/RailSection.d.ts +7 -1
  32. package/dist/react/filter-rail/RailSection.d.ts.map +1 -1
  33. package/dist/react/filter-rail/RailSection.js +5 -3
  34. package/dist/react/filter-rail/RailSection.js.map +1 -1
  35. package/dist/react/filter-rail/headerContextMenu.d.ts +8 -0
  36. package/dist/react/filter-rail/headerContextMenu.d.ts.map +1 -0
  37. package/dist/react/filter-rail/headerContextMenu.js +31 -0
  38. package/dist/react/filter-rail/headerContextMenu.js.map +1 -0
  39. package/dist/react/media/denseGallery.css +46 -0
  40. package/dist/react/media-gallery/rememberedPreference.d.ts +10 -1
  41. package/dist/react/media-gallery/rememberedPreference.d.ts.map +1 -1
  42. package/dist/react/media-gallery/rememberedPreference.js +12 -3
  43. package/dist/react/media-gallery/rememberedPreference.js.map +1 -1
  44. package/package.json +1 -1
  45. package/scripts/guardrails-baseline.json +1 -1
  46. package/src/react/file-tree/FileTree.tsx +323 -4
  47. package/src/react/file-tree/FolderViewMenus.tsx +242 -0
  48. package/src/react/file-tree/FolderViewPanels.tsx +402 -0
  49. package/src/react/file-tree/fileTree.css +146 -0
  50. package/src/react/file-tree/folderView.spec.ts +174 -0
  51. package/src/react/file-tree/folderView.ts +316 -0
  52. package/src/react/file-tree/folderViewPanels.css +217 -0
  53. package/src/react/file-tree/folderViewTree.spec.tsx +252 -0
  54. package/src/react/file-tree/index.ts +14 -0
  55. package/src/react/file-tree/useFolderView.ts +152 -0
  56. package/src/react/filter-rail/FilterRail.tsx +35 -6
  57. package/src/react/filter-rail/RailSection.tsx +10 -2
  58. package/src/react/filter-rail/headerContextMenu.spec.tsx +62 -0
  59. package/src/react/filter-rail/headerContextMenu.ts +37 -0
  60. package/src/react/media/denseGallery.css +46 -0
  61. package/src/react/media-gallery/rememberedPreference.ts +10 -2
@@ -0,0 +1,155 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useId, useMemo, useRef, useState } from 'react';
3
+ import { Input } from '../components/Input.js';
4
+ import { Menu, MenuItem, MenuLabel, MenuSeparator } from '../components/Menu.js';
5
+ import { Popover } from '../components/Popover.js';
6
+ import { ScrollArea } from '../components/ScrollArea.js';
7
+ import { Tooltip } from '../components/Tooltip.js';
8
+ import { UnstyledButton } from '../components/UnstyledButton.js';
9
+ import { normalizeTag, tagFilterActive } from './folderView.js';
10
+ import { TagDot } from './FolderViewMenus.js';
11
+ import './folderViewPanels.css';
12
+ export function TreePanel(props) {
13
+ if (props.kind === 'row')
14
+ return (_jsx(TreeMenuPanel, { at: props.at, label: props.label, onClose: props.onClose, children: _jsx(Menu, { "aria-label": props.label, children: _jsx(TreeMenuItems, { items: props.items, onDone: props.onClose }) }) }));
15
+ if (props.kind === 'view')
16
+ return (_jsx(FolderViewMenu, { view: props.view, at: props.at, hiddenCount: props.hiddenCount, extraItems: props.extraItems, onCollapseAll: props.onCollapseAll, onClose: props.onClose }));
17
+ return (_jsx(FolderTagEditor, { view: props.view, ids: props.ids, names: props.names, at: props.at, onClose: props.onClose }));
18
+ }
19
+ /** The menu items in a tree menu — refused items stay reachable, with their reason. */
20
+ export function TreeMenuItems({ items, onDone, }) {
21
+ return items.map((item, index) => {
22
+ if (item.divider) {
23
+ // A divider at either end, or after another, is a line under nothing.
24
+ const prev = items[index - 1];
25
+ const next = items[index + 1];
26
+ if (!prev || !next || prev.divider || next.divider)
27
+ return null;
28
+ return _jsx(MenuSeparator, {}, item.key);
29
+ }
30
+ const hint = typeof item.description === 'string' ? item.description : undefined;
31
+ return (_jsx(MenuItem, { ...(item.checked !== undefined ? { selected: item.checked } : {}), ...(item.destructive ? { danger: true } : {}), ...(item.shortcut ? { shortcut: item.shortcut } : {}), ...(hint ? { hint } : {}), ...(item.disabled
32
+ ? item.title
33
+ ? { unavailableReason: item.title }
34
+ : { disabled: true }
35
+ : item.title
36
+ ? { title: item.title }
37
+ : {}), onSelect: () => {
38
+ // Shut first, then act: an item that opens ANOTHER panel (Tags…) must not have it
39
+ // shut again by this menu's own close.
40
+ onDone();
41
+ item.onClick?.();
42
+ }, children: item.label }, item.key));
43
+ });
44
+ }
45
+ /** The panel every tree menu sits in — a point-anchored popover with a menu's padding. */
46
+ export function TreeMenuPanel({ at, label, onClose, children, width, }) {
47
+ // A fresh object per open, so reopening at the same point still re-anchors.
48
+ const anchor = useMemo(() => ({ x: at.x, y: at.y }), [at.x, at.y]);
49
+ return (_jsx(Popover, { anchor: anchor, onOpenChange: (open) => {
50
+ if (!open)
51
+ onClose();
52
+ }, label: label, arrow: false, side: 'bottom', align: 'start', sideOffset: 2, ...(width ? { width } : {}), className: 'cbft-menu-panel p-1', children: children }));
53
+ }
54
+ /**
55
+ * The view menu — right-click on the blank space under the rows, or on the app's header.
56
+ *
57
+ * Two parts: COMMANDS (a menu — they act and close) and the TAG FILTER (toggles that stay
58
+ * open, because narrowing to "home and code" is two presses and a menu that shut after the
59
+ * first would make it two right-clicks).
60
+ */
61
+ export function FolderViewMenu({ view, at, hiddenCount, extraItems, onCollapseAll, onClose, }) {
62
+ const commands = [...extraItems];
63
+ if (view) {
64
+ if (commands.length > 0)
65
+ commands.push({ key: 'fv-sep-app', divider: true });
66
+ commands.push({
67
+ key: 'fv-show-hidden',
68
+ label: hiddenCount > 0 ? `Show hidden folders (${hiddenCount})` : 'Show hidden folders',
69
+ checked: view.local.showHidden,
70
+ shortcut: '⇧⌘.',
71
+ title: view.local.showHidden
72
+ ? 'Leave hidden folders out again. On this device only.'
73
+ : 'Draw hidden folders too, faded and in italics, so you can unhide them. On this device only.',
74
+ onClick: () => view.setShowHidden(!view.local.showHidden),
75
+ });
76
+ }
77
+ if (onCollapseAll)
78
+ commands.push({ key: 'fv-collapse', label: 'Collapse all folders', onClick: onCollapseAll });
79
+ return (_jsxs(TreeMenuPanel, { at: at, label: 'Tree view', onClose: onClose, width: '17rem', children: [_jsx(Menu, { "aria-label": 'Tree view', children: _jsx(TreeMenuItems, { items: commands, onDone: onClose }) }), view ? _jsx(TagFilter, { view: view }) : null] }));
80
+ }
81
+ /** "Filter by tag" — one row per tag, each with an Only and a Hide toggle. */
82
+ function TagFilter({ view }) {
83
+ const { local } = view;
84
+ // A tag still in this device's filter but carried by no folder any more must stay listed:
85
+ // it is narrowing the tree, and a filter you cannot see is one you cannot take off.
86
+ const rows = useMemo(() => {
87
+ const list = view.tags.map((t) => ({ ...t }));
88
+ for (const tag of [...local.only, ...local.hide])
89
+ if (!list.some((t) => t.tag === tag))
90
+ list.push({ tag, count: 0 });
91
+ return list;
92
+ }, [view.tags, local.only, local.hide]);
93
+ const headId = useId();
94
+ return (_jsxs("section", { className: 'cbft-fv', "aria-labelledby": headId, children: [_jsx(MenuSeparator, {}), _jsxs("div", { className: 'cbft-fv-head', children: [_jsx(MenuLabel, { className: 'cbft-fv-title', children: _jsx("span", { id: headId, children: "Filter by tag" }) }), _jsx(Tooltip, { content: 'The tag filter is remembered on this device only \u2014 your other computers keep their own.', children: _jsx("span", { className: 'cbft-fv-scope', children: "This device" }) })] }), rows.length === 0 ? (_jsxs("p", { className: 'cbft-fv-empty', children: ["No tags yet. Right-click a folder and choose ", _jsx("b", { children: "Tags\u2026" }), " to add one."] })) : (_jsx(ScrollArea, { className: 'cbft-fv-scroll', space: 'none', safeArea: false, fade: true, children: _jsx("ul", { className: 'cbft-fv-list', children: rows.map(({ tag, count }) => {
95
+ const mode = local.only.includes(tag) ? 'only' : local.hide.includes(tag) ? 'hide' : null;
96
+ return (_jsxs("li", { className: 'cbft-fv-row', "data-mode": mode ?? undefined, children: [_jsx(TagDot, { tag: tag }), _jsx("span", { className: 'cbft-fv-name', children: tag }), _jsx("span", { className: 'cbft-fv-count', children: count }), _jsxs("fieldset", { className: 'cbft-fv-seg', "aria-label": `Filter by ${tag}`, children: [_jsx(UnstyledButton, { tooltip: `Show only folders tagged ${tag} (and those with any other “Only” tag)`, "aria-pressed": mode === 'only', onClick: () => view.filterTag(tag, mode === 'only' ? null : 'only'), children: "Only" }), _jsx(UnstyledButton, { tooltip: `Hide folders tagged ${tag}`, "aria-pressed": mode === 'hide', onClick: () => view.filterTag(tag, mode === 'hide' ? null : 'hide'), children: "Hide" })] })] }, tag));
97
+ }) }) })), tagFilterActive(local) ? (_jsx(UnstyledButton, { className: 'cbft-fv-clear', onClick: () => view.clearTagFilter(), children: "Clear tag filter" })) : null] }));
98
+ }
99
+ /**
100
+ * The tag editor — Gmail's *Label as*: type to find a tag, or to make one.
101
+ *
102
+ * ↑/↓ move through the list, Enter ticks or unticks the highlighted tag (or creates the typed
103
+ * one), Escape closes. A tag ticked here is on the folder at once — there is no Save, because
104
+ * every change is one small reversible write.
105
+ */
106
+ export function FolderTagEditor({ view, ids, names, at, onClose, }) {
107
+ const [query, setQuery] = useState('');
108
+ const [active, setActive] = useState(0);
109
+ const listId = useId();
110
+ const inputRef = useRef(null);
111
+ const typed = normalizeTag(query);
112
+ const options = useMemo(() => {
113
+ const all = view.tags.map((t) => t.tag);
114
+ const shown = typed ? all.filter((tag) => tag.includes(typed)) : all;
115
+ const create = typed && !all.includes(typed) ? [{ tag: typed, create: true }] : [];
116
+ return [...shown.map((tag) => ({ tag, create: false })), ...create];
117
+ }, [view.tags, typed]);
118
+ const at0 = Math.min(active, Math.max(0, options.length - 1));
119
+ const stateOf = (tag) => {
120
+ const have = ids.filter((id) => (view.marks.tags[id] ?? []).includes(tag)).length;
121
+ return have === 0 ? 'none' : have === ids.length ? 'all' : 'some';
122
+ };
123
+ const pick = (tag, create) => {
124
+ view.toggleTag(ids, tag);
125
+ if (create)
126
+ setQuery('');
127
+ inputRef.current?.focus();
128
+ };
129
+ const heading = names.length === 1 ? `Tags for “${names[0]}”` : `Tags for ${names.length} folders`;
130
+ return (_jsx(TreeMenuPanel, { at: at, label: heading, onClose: onClose, width: '16rem', children: _jsxs("div", { className: 'cbft-te', children: [_jsx("div", { className: 'cbft-te-head', children: heading }), _jsx(Input, { ref: inputRef, size: 'sm', className: 'cbft-te-input', autoFocus: true, value: query, placeholder: 'Find or create a tag', "aria-label": 'Find or create a tag', role: 'combobox', "aria-expanded": 'true', "aria-controls": listId, "aria-activedescendant": options[at0] ? `${listId}-${at0}` : undefined, maxLength: 64, onChange: (event) => {
131
+ setQuery(event.currentTarget.value);
132
+ setActive(0);
133
+ }, onKeyDown: (event) => {
134
+ if (event.key === 'ArrowDown') {
135
+ event.preventDefault();
136
+ setActive((i) => Math.min(options.length - 1, i + 1));
137
+ }
138
+ else if (event.key === 'ArrowUp') {
139
+ event.preventDefault();
140
+ setActive((i) => Math.max(0, i - 1));
141
+ }
142
+ else if (event.key === 'Enter') {
143
+ event.preventDefault();
144
+ const option = options[at0];
145
+ if (option)
146
+ pick(option.tag, option.create);
147
+ }
148
+ } }), options.length === 0 ? (_jsx("p", { className: 'cbft-fv-empty', children: "Type a word to make your first tag \u2014 \u201Chome\u201D, \u201Cwork\u201D." })) : (_jsx(ScrollArea, { className: 'cbft-te-scroll', space: 'none', safeArea: false, fade: true, children: _jsx("div", { id: listId, role: 'listbox', "aria-multiselectable": 'true', "aria-label": 'Tags', children: options.map((option, index) => {
149
+ const state = option.create ? 'none' : stateOf(option.tag);
150
+ return (_jsxs("div", { id: `${listId}-${index}`, role: 'option',
151
+ // Reachable through the field's aria-activedescendant, never by Tab.
152
+ tabIndex: -1, "aria-selected": state === 'all', "data-active": index === at0 || undefined, "data-state": state, className: 'cbft-te-option', onMouseEnter: () => setActive(index), onMouseDown: (event) => event.preventDefault(), onClick: () => pick(option.tag, option.create), children: [_jsx("span", { className: 'cbft-te-check', "aria-hidden": 'true', children: state === 'all' ? '✓' : state === 'some' ? '–' : '' }), _jsx(TagDot, { tag: option.tag }), _jsx("span", { className: 'cbft-fv-name', children: option.create ? (_jsxs(_Fragment, { children: ["Create ", _jsxs("b", { children: ["\u201C", option.tag, "\u201D"] })] })) : (option.tag) })] }, `${option.create ? '+' : ''}${option.tag}`));
153
+ }) }) })), _jsx("p", { className: 'cbft-te-foot', children: "Tags follow the folder to every device." })] }) }));
154
+ }
155
+ //# sourceMappingURL=FolderViewPanels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FolderViewPanels.js","sourceRoot":"","sources":["../../../src/react/file-tree/FolderViewPanels.tsx"],"names":[],"mappings":"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"}
@@ -953,3 +953,149 @@
953
953
  white-space: nowrap;
954
954
  border: 0;
955
955
  }
956
+
957
+ /* ── Hidden folders and folder tags ─────────────────────────────────────────────────────
958
+ *
959
+ * Owner, 2026-09-30: *"Showing hidden folders still keeps them hideable and they should be
960
+ * slightly faded or italics or something like that to let me see which ones are hideable when
961
+ * all are showing. The indication should be subtle but easy for me to notice."*
962
+ *
963
+ * So a hidden folder drawn by "Show hidden folders" is FADED, ITALIC and carries a small
964
+ * eye-off; what is inside it is faded only. Faded is what says "not normally here" across the
965
+ * whole list; the eye-off is what keeps it from reading as a PRIVATE row, which is italic too
966
+ * (`data-access` above) and carries a padlock instead. The row stays a full-strength target —
967
+ * only its ink is quieter — because the point of drawing it is to act on it.
968
+ */
969
+ .cbft-row[data-hidden] .cbft-name,
970
+ .cbft-row[data-hidden] .cbft-slot,
971
+ .cbft-row[data-hidden] .cbft-twisty {
972
+ opacity: 0.5;
973
+ }
974
+
975
+ .cbft-row[data-hidden='self'] .cbft-name {
976
+ font-style: italic;
977
+ }
978
+
979
+ /* A selected or focused hidden row is the one being acted on; let it read at nearly full ink. */
980
+ .cbft-row[data-hidden][data-selected] .cbft-name,
981
+ .cbft-row[data-hidden]:focus-visible .cbft-name {
982
+ opacity: 0.8;
983
+ }
984
+
985
+ .cbft-hidden-mark {
986
+ flex: none;
987
+ display: inline-flex;
988
+ color: var(--muted-foreground);
989
+ opacity: 0.7;
990
+ pointer-events: none;
991
+ }
992
+
993
+ /* The row whose right-click menu is open — Finder's ring, so the menu says what it is for. */
994
+ .cbft-row[data-menu-open] {
995
+ outline: 1px solid var(--ring, var(--accent-solid));
996
+ outline-offset: -1px;
997
+ }
998
+
999
+ /*
1000
+ * Tag dots — Finder's: a few coloured dots after the name, one per tag, the rest in the
1001
+ * hover. The colour is the tag's NAME hashed onto the theme's chart palette (`tagHue`), so the
1002
+ * same tag is the same colour everywhere without anyone choosing or storing a colour.
1003
+ */
1004
+ .cbft-tagdots {
1005
+ flex: none;
1006
+ display: inline-flex;
1007
+ align-items: center;
1008
+ gap: 2px;
1009
+ padding-inline: 2px;
1010
+ }
1011
+
1012
+ .cbft-tagdot {
1013
+ flex: none;
1014
+ display: inline-block;
1015
+ inline-size: 7px;
1016
+ block-size: 7px;
1017
+ border-radius: 999px;
1018
+ background: var(--cbft-tag, var(--muted-foreground));
1019
+ }
1020
+
1021
+ .cbft-tagdot[data-hue='0'] { --cbft-tag: var(--chart-1); }
1022
+ .cbft-tagdot[data-hue='1'] { --cbft-tag: var(--chart-2); }
1023
+ .cbft-tagdot[data-hue='2'] { --cbft-tag: var(--chart-3); }
1024
+ .cbft-tagdot[data-hue='3'] { --cbft-tag: var(--chart-4); }
1025
+ .cbft-tagdot[data-hue='4'] { --cbft-tag: var(--chart-5); }
1026
+ .cbft-tagdot[data-hue='5'] { --cbft-tag: var(--chart-6); }
1027
+ .cbft-tagdot[data-hue='6'] { --cbft-tag: var(--chart-7); }
1028
+ .cbft-tagdot[data-hue='7'] { --cbft-tag: var(--chart-8); }
1029
+
1030
+ /*
1031
+ * The strip — what this device is narrowing the tree by, and one press back to everything.
1032
+ * One tree row tall and as quiet as the pinned heading, because it is a reminder rather than
1033
+ * a control panel; the menu behind it is the control panel.
1034
+ */
1035
+ .cbft-fv-strip {
1036
+ display: flex;
1037
+ align-items: center;
1038
+ gap: 0.25rem;
1039
+ flex: none;
1040
+ min-block-size: var(--cbft-row);
1041
+ padding: 0.125rem calc(var(--cbft-inset) - 2px) 0.125rem var(--cbft-inset);
1042
+ margin: 0 0 0.125rem;
1043
+ border-block-end: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
1044
+ font-size: 0.75rem;
1045
+ color: var(--muted-foreground);
1046
+ }
1047
+
1048
+ .cbft-fv-strip-open,
1049
+ .cbft-fv-strip-clear {
1050
+ border: 0;
1051
+ background: transparent;
1052
+ color: inherit;
1053
+ font: inherit;
1054
+ cursor: pointer;
1055
+ border-radius: var(--radius-sm, 0.375rem);
1056
+ }
1057
+
1058
+ .cbft-fv-strip-open {
1059
+ display: flex;
1060
+ flex: 1 1 auto;
1061
+ min-inline-size: 0;
1062
+ align-items: center;
1063
+ gap: 0.5rem;
1064
+ padding: 0.125rem 0.25rem;
1065
+ margin-inline-start: -0.25rem;
1066
+ overflow: hidden;
1067
+ white-space: nowrap;
1068
+ text-align: start;
1069
+ }
1070
+
1071
+ .cbft-fv-strip-clear {
1072
+ flex: none;
1073
+ padding: 0.125rem 0.375rem;
1074
+ color: var(--accent);
1075
+ font-weight: 500;
1076
+ }
1077
+
1078
+ .cbft-fv-strip-open:hover,
1079
+ .cbft-fv-strip-clear:hover {
1080
+ background: color-mix(in srgb, var(--muted-foreground) 12%, transparent);
1081
+ }
1082
+
1083
+ .cbft-fv-strip-open:focus-visible,
1084
+ .cbft-fv-strip-clear:focus-visible {
1085
+ outline: 2px solid var(--ring, var(--accent-solid));
1086
+ outline-offset: -2px;
1087
+ }
1088
+
1089
+ .cbft-fv-part {
1090
+ display: inline-flex;
1091
+ align-items: center;
1092
+ gap: 0.25rem;
1093
+ min-inline-size: 0;
1094
+ }
1095
+
1096
+ .cbft-fv-chip {
1097
+ display: inline-flex;
1098
+ align-items: center;
1099
+ gap: 0.25rem;
1100
+ color: var(--cbft-fg, var(--foreground));
1101
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Hidden folders and folder tags — the arithmetic, with no DOM.
3
+ *
4
+ * Owner, 2026-09-30: *"add an option to the right click menu to hide a folder … In the menu
5
+ * put a show/hide hidden folders option … Add the ability for tagging folders … then being
6
+ * able to hide or show ones by tag or tags like just seeing folders tagged as 'home' and
7
+ * 'code' or 'work' and 'dynatrace' … The general hiding is persisted for all machines. Hiding
8
+ * or showing by tag can be stored per machine."*
9
+ *
10
+ * ── 🔴 Two stores, because the request names two scopes ─────────────────────────────────
11
+ *
12
+ * · {@link FolderMarks} — WHAT a folder is: hidden or not, and its tags. A fact about the
13
+ * folder, so it follows the person to every device (the app syncs it — see
14
+ * `useFolderView`, which announces it on the preference bus like every other synced key).
15
+ * · {@link FolderViewLocal} — HOW this device is looking: whether hidden folders are drawn,
16
+ * and which tags it is narrowed to. *"viewing the 'home' tag at home doesn't change my
17
+ * work computer"*, so this is never announced and never leaves the browser.
18
+ *
19
+ * Hiding a folder in general and filtering by tag are therefore different verbs on purpose:
20
+ * one is a decision about the folder, the other is a lens on the tree.
21
+ *
22
+ * ── The rules {@link applyFolderView} holds ─────────────────────────────────────────────
23
+ *
24
+ * 1. A hidden folder takes its subtree with it. With "show hidden" on, the folder is drawn
25
+ * marked `self` (italic, faded) and everything under it marked `inherited` (faded only),
26
+ * so the one to unhide is the one that looks different from its children.
27
+ * 2. "Hide tagged" wins over everything: a folder carrying a hidden tag, or inside one, is
28
+ * gone — Gmail's and Linear's "is not" filter.
29
+ * 3. "Only tagged" keeps a tagged folder WITH its whole subtree (tagging `code` means the
30
+ * code inside it too), and keeps the folders ON THE WAY to a tagged folder so it can be
31
+ * reached — pruned to just that path, the way a search keeps the folders above a match.
32
+ * 4. Files at the top level are not in any folder and no folder verb can reach them, so a
33
+ * tag filter leaves them alone. A file inside a folder follows its folder.
34
+ * 5. Tags are compared lower-cased and trimmed: `Home` and `home ` are one tag, the same
35
+ * rule GitHub topics and Obsidian tags hold, so a typo in case never splits a filter.
36
+ */
37
+ import type { FileTreeNode } from 'cursedbelt-core/file-tree/file-tree-model';
38
+ /** The synced half — what each folder IS. Keyed by `FileTreeNode.id`. */
39
+ export interface FolderMarks {
40
+ hidden: readonly string[];
41
+ tags: Readonly<Record<string, readonly string[]>>;
42
+ }
43
+ /** The per-device half — how THIS browser is looking at the tree. */
44
+ export interface FolderViewLocal {
45
+ /** Draw hidden folders (marked) instead of leaving them out. Finder's ⇧⌘. */
46
+ showHidden: boolean;
47
+ /** Narrow the tree to folders carrying any of these. Empty = no narrowing. */
48
+ only: readonly string[];
49
+ /** Leave out folders carrying any of these. Wins over {@link only}. */
50
+ hide: readonly string[];
51
+ }
52
+ export declare const EMPTY_FOLDER_MARKS: FolderMarks;
53
+ export declare const EMPTY_FOLDER_VIEW: FolderViewLocal;
54
+ /** Longest tag kept. A tag is a word or two; anything longer is a sentence in the wrong field. */
55
+ export declare const MAX_TAG_CHARS = 32;
56
+ /**
57
+ * The one spelling of a tag: trimmed, inner whitespace collapsed, lower-cased, capped.
58
+ * `null` for nothing left — the caller treats that as "no tag", never as `""`.
59
+ */
60
+ export declare function normalizeTag(raw: string): string | null;
61
+ /**
62
+ * Read stored marks. Anything malformed reads as nothing rather than throwing — a corrupt
63
+ * value must cost the person their marks on that one value, never the tree.
64
+ */
65
+ export declare function parseFolderMarks(raw: string | null | undefined): FolderMarks;
66
+ /** The stored form. `null` when there is nothing to keep, so an app can forget the key. */
67
+ export declare function serializeFolderMarks(marks: FolderMarks): string | null;
68
+ /** Read a device's view. Same forgiving rule as {@link parseFolderMarks}. */
69
+ export declare function parseFolderViewLocal(raw: string | null | undefined): FolderViewLocal;
70
+ /** Hide or unhide these ids — a new value; the input is never touched. */
71
+ export declare function setHidden(marks: FolderMarks, ids: readonly string[], hidden: boolean): FolderMarks;
72
+ /** Replace one folder's tags. An empty list forgets the folder entirely. */
73
+ export declare function setFolderTags(marks: FolderMarks, id: string, tags: readonly string[]): FolderMarks;
74
+ /** Add or take away ONE tag on one folder — the tag editor's checkbox. */
75
+ export declare function toggleFolderTag(marks: FolderMarks, id: string, raw: string): FolderMarks;
76
+ /** A tag that exists, and how many folders carry it — most-used first, then by name. */
77
+ export interface TagCount {
78
+ tag: string;
79
+ count: number;
80
+ }
81
+ /**
82
+ * Every tag in use. Derived, never stored: a tag no folder carries any more simply stops
83
+ * existing, so there is no tag list to tidy and no orphan to delete.
84
+ */
85
+ export declare function tagCounts(marks: FolderMarks): TagCount[];
86
+ /** How many colours a tag dot can be — `--cbft-tag-0` … `--cbft-tag-7` in fileTree.css. */
87
+ export declare const TAG_HUES = 8;
88
+ /**
89
+ * A tag's colour slot, from its name — the same tag is the same colour in every app, on every
90
+ * device, without a colour having to be chosen or stored. FNV-1a, because it is four lines and
91
+ * spreads short words well.
92
+ */
93
+ export declare function tagHue(tag: string): number;
94
+ /** Whether a device's view narrows the tree by tag at all. */
95
+ export declare function tagFilterActive(local: FolderViewLocal): boolean;
96
+ /** Cycle one tag in the device's filter: `'only'`, `'hide'`, or `null` for neither. */
97
+ export declare function setTagFilter(local: FolderViewLocal, raw: string, mode: 'only' | 'hide' | null): FolderViewLocal;
98
+ /** What {@link applyFolderView} hands back. */
99
+ export interface FolderViewResult {
100
+ /** The tree to draw — pruned, with new objects only where a child list changed. */
101
+ nodes: readonly FileTreeNode[];
102
+ /**
103
+ * Hidden rows that ARE drawn (show-hidden on): `self` for the folder that was hidden,
104
+ * `inherited` for everything inside it.
105
+ */
106
+ hiddenMarks: ReadonlyMap<string, 'self' | 'inherited'>;
107
+ /** Hidden folders the tree holds right now — the count beside "Show hidden folders". */
108
+ hiddenCount: number;
109
+ }
110
+ /**
111
+ * The tree this device should draw. See the header for the five rules.
112
+ *
113
+ * Returns the input array itself when nothing is marked and nothing is filtered, so a tree
114
+ * with no marks pays one check and no copy.
115
+ */
116
+ export declare function applyFolderView(nodes: readonly FileTreeNode[], marks: FolderMarks, local: FolderViewLocal): FolderViewResult;
117
+ //# sourceMappingURL=folderView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folderView.d.ts","sourceRoot":"","sources":["../../../src/react/file-tree/folderView.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAE9E,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CACnD;AAED,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,UAAU,EAAE,OAAO,CAAC;IACpB,8EAA8E;IAC9E,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,uEAAuE;IACvE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACzB;AAED,eAAO,MAAM,kBAAkB,EAAE,WAG/B,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,eAI9B,CAAC;AAEH,kGAAkG;AAClG,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGvD;AAWD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,CAmB5E;AAED,2FAA2F;AAC3F,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAItE;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,CAYpF;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,GAAG,WAAW,CAOlG;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAIlG;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CASxF;AAED,wFAAwF;AACxF,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,EAAE,CAOxD;AAED,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,IAAI,CAAC;AAE1B;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO1C;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,uFAAuF;AACvF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,eAAe,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAC3B,eAAe,CAQjB;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/B;;;OAGG;IACH,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IACvD,wFAAwF;IACxF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,eAAe,GACrB,gBAAgB,CA0ElB"}