@almadar/ui 5.163.0 → 5.165.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.
@@ -24445,6 +24445,7 @@ var init_DashboardLayout = __esm({
24445
24445
  onNotificationClick,
24446
24446
  showThemeToggle = true,
24447
24447
  sidebarFooter,
24448
+ sidebarContent,
24448
24449
  onSignOut: onSignOutProp,
24449
24450
  currentPath,
24450
24451
  layoutMode = "sidebar",
@@ -24570,7 +24571,11 @@ var init_DashboardLayout = __esm({
24570
24571
  {
24571
24572
  as: "nav",
24572
24573
  gap: "none",
24573
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
24574
+ className: cn(
24575
+ "py-4 space-y-1 overflow-y-auto",
24576
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
24577
+ isRail ? "px-2" : "px-3"
24578
+ ),
24574
24579
  children: navItems.map((item) => /* @__PURE__ */ jsx(
24575
24580
  NavLink,
24576
24581
  {
@@ -24582,6 +24587,7 @@ var init_DashboardLayout = __esm({
24582
24587
  ))
24583
24588
  }
24584
24589
  ),
24590
+ sidebarContent && !isRail && /* @__PURE__ */ jsx(Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
24585
24591
  sidebarFooter && /* @__PURE__ */ jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
24586
24592
  ]
24587
24593
  }
@@ -26991,7 +26997,17 @@ function fileIcon(name) {
26991
26997
  return "file";
26992
26998
  }
26993
26999
  }
26994
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
27000
+ function ancestorsOf(id, byId) {
27001
+ const out = /* @__PURE__ */ new Set();
27002
+ if (!id) return out;
27003
+ let cur = byId.get(id)?.parentId;
27004
+ while (cur && byId.has(cur) && !out.has(cur)) {
27005
+ out.add(cur);
27006
+ cur = byId.get(cur)?.parentId;
27007
+ }
27008
+ return out;
27009
+ }
27010
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
26995
27011
  var init_FileTree = __esm({
26996
27012
  "components/core/molecules/FileTree.tsx"() {
26997
27013
  "use client";
@@ -27082,42 +27098,77 @@ var init_FileTree = __esm({
27082
27098
  indent,
27083
27099
  childrenByParent,
27084
27100
  onNodeSelect,
27085
- defaultExpanded = false
27101
+ onNodeAction,
27102
+ nodeActionIcon,
27103
+ nodeActionLabel,
27104
+ selectedId,
27105
+ look,
27106
+ isExpanded,
27107
+ onToggle
27086
27108
  }) => {
27087
- const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
27088
27109
  const children = childrenByParent.get(item.id);
27089
27110
  const hasChildren = !!children && children.length > 0;
27111
+ const expanded = hasChildren && isExpanded(item.id, depth);
27112
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
27113
+ const nav = look === "nav";
27090
27114
  const handleClick = useCallback(() => {
27091
- if (hasChildren) setExpanded((prev) => !prev);
27115
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
27092
27116
  onNodeSelect?.(item.id);
27093
- }, [hasChildren, item.id, onNodeSelect]);
27117
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
27118
+ const handleChevron = useCallback((e) => {
27119
+ e.stopPropagation();
27120
+ onToggle(item.id, expanded);
27121
+ }, [item.id, expanded, onToggle]);
27122
+ const handleAction = useCallback((e) => {
27123
+ e.stopPropagation();
27124
+ onNodeAction?.(item.id);
27125
+ }, [item.id, onNodeAction]);
27094
27126
  return /* @__PURE__ */ jsxs(Fragment, { children: [
27095
27127
  /* @__PURE__ */ jsxs(
27096
27128
  Box,
27097
27129
  {
27098
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27130
+ className: `group/treerow flex items-center gap-1.5 px-2 cursor-pointer rounded-sm transition-colors ${nav ? "py-1" : "py-0.5"} ${isSelected ? "bg-primary text-primary-foreground" : nav ? "text-foreground hover:bg-muted" : "hover:bg-muted"}`,
27099
27131
  style: { paddingLeft: depth * indent + 8 },
27100
27132
  onClick: handleClick,
27101
27133
  role: "treeitem",
27134
+ "aria-selected": isSelected,
27102
27135
  "aria-expanded": hasChildren ? expanded : void 0,
27103
27136
  children: [
27104
- hasChildren ? /* @__PURE__ */ jsx(
27137
+ hasChildren ? /* @__PURE__ */ jsx(Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsx(
27105
27138
  Icon,
27106
27139
  {
27107
27140
  name: expanded ? "chevron-down" : "chevron-right",
27108
27141
  size: "xs",
27109
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27142
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
27110
27143
  }
27111
- ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27144
+ ) }) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27112
27145
  /* @__PURE__ */ jsx(
27113
27146
  Icon,
27114
27147
  {
27115
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27148
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27116
27149
  size: "xs",
27117
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27150
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
27151
+ }
27152
+ ),
27153
+ /* @__PURE__ */ jsx(
27154
+ Typography,
27155
+ {
27156
+ variant: "caption",
27157
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
27158
+ children: item.label
27118
27159
  }
27119
27160
  ),
27120
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27161
+ onNodeAction && /* @__PURE__ */ jsx(
27162
+ Box,
27163
+ {
27164
+ onClick: handleAction,
27165
+ role: "button",
27166
+ "aria-label": nodeActionLabel ?? "Node action",
27167
+ title: nodeActionLabel,
27168
+ className: `ml-auto flex-shrink-0 rounded-sm p-0.5 opacity-0 group-hover/treerow:opacity-100 transition-opacity ${isSelected ? "hover:bg-primary-foreground/20" : "hover:bg-border"}`,
27169
+ children: /* @__PURE__ */ jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
27170
+ }
27171
+ )
27121
27172
  ]
27122
27173
  }
27123
27174
  ),
@@ -27128,47 +27179,116 @@ var init_FileTree = __esm({
27128
27179
  depth: depth + 1,
27129
27180
  indent,
27130
27181
  childrenByParent,
27131
- onNodeSelect
27182
+ onNodeSelect,
27183
+ onNodeAction,
27184
+ nodeActionIcon,
27185
+ nodeActionLabel,
27186
+ selectedId,
27187
+ look,
27188
+ isExpanded,
27189
+ onToggle
27132
27190
  },
27133
27191
  child.id
27134
27192
  )) })
27135
27193
  ] });
27136
27194
  };
27195
+ FlatFileTree = ({
27196
+ items,
27197
+ selectedId,
27198
+ look = "files",
27199
+ onNodeSelect,
27200
+ onNodeAction,
27201
+ nodeActionIcon,
27202
+ nodeActionLabel,
27203
+ className,
27204
+ indent = 16
27205
+ }) => {
27206
+ const [overrides, setOverrides] = useState(() => /* @__PURE__ */ new Map());
27207
+ const byId = React85__default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
27208
+ const selectedAncestors = React85__default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
27209
+ const lastSelectedRef = React85__default.useRef(selectedId);
27210
+ React85__default.useEffect(() => {
27211
+ if (selectedId === lastSelectedRef.current) return;
27212
+ lastSelectedRef.current = selectedId;
27213
+ setOverrides((prev) => {
27214
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
27215
+ const next = new Map(prev);
27216
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
27217
+ return next;
27218
+ });
27219
+ }, [selectedId, selectedAncestors]);
27220
+ const isExpanded = useCallback(
27221
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
27222
+ [overrides, selectedAncestors]
27223
+ );
27224
+ const onToggle = useCallback((id, effective) => {
27225
+ setOverrides((prev) => {
27226
+ const next = new Map(prev);
27227
+ next.set(id, !effective);
27228
+ return next;
27229
+ });
27230
+ }, []);
27231
+ if (items.length === 0) return null;
27232
+ const ids = new Set(items.map((node) => node.id));
27233
+ const childrenByParent = /* @__PURE__ */ new Map();
27234
+ const roots = [];
27235
+ for (const item of items) {
27236
+ if (item.parentId && ids.has(item.parentId)) {
27237
+ const siblings = childrenByParent.get(item.parentId);
27238
+ if (siblings) siblings.push(item);
27239
+ else childrenByParent.set(item.parentId, [item]);
27240
+ } else {
27241
+ roots.push(item);
27242
+ }
27243
+ }
27244
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
27245
+ FlatTreeNodeItem,
27246
+ {
27247
+ item,
27248
+ depth: 0,
27249
+ indent,
27250
+ childrenByParent,
27251
+ onNodeSelect,
27252
+ onNodeAction,
27253
+ nodeActionIcon,
27254
+ nodeActionLabel,
27255
+ selectedId,
27256
+ look,
27257
+ isExpanded,
27258
+ onToggle
27259
+ },
27260
+ item.id
27261
+ )) });
27262
+ };
27137
27263
  FileTree = ({
27138
27264
  tree,
27139
27265
  items,
27140
27266
  selectedPath,
27267
+ selectedId,
27268
+ look = "files",
27141
27269
  onFileSelect,
27142
27270
  onNodeSelect,
27271
+ onNodeAction,
27272
+ nodeActionIcon,
27273
+ nodeActionLabel,
27143
27274
  className,
27144
27275
  indent = 16
27145
27276
  }) => {
27146
27277
  if (items) {
27147
- if (items.length === 0) return null;
27148
- const ids = new Set(items.map((node) => node.id));
27149
- const childrenByParent = /* @__PURE__ */ new Map();
27150
- const roots = [];
27151
- for (const item of items) {
27152
- if (item.parentId && ids.has(item.parentId)) {
27153
- const siblings = childrenByParent.get(item.parentId);
27154
- if (siblings) siblings.push(item);
27155
- else childrenByParent.set(item.parentId, [item]);
27156
- } else {
27157
- roots.push(item);
27158
- }
27159
- }
27160
- return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
27161
- FlatTreeNodeItem,
27278
+ return /* @__PURE__ */ jsx(
27279
+ FlatFileTree,
27162
27280
  {
27163
- item,
27164
- depth: 0,
27165
- indent,
27166
- childrenByParent,
27281
+ items,
27282
+ selectedId,
27283
+ look,
27167
27284
  onNodeSelect,
27168
- defaultExpanded: true
27169
- },
27170
- item.id
27171
- )) });
27285
+ onNodeAction,
27286
+ nodeActionIcon,
27287
+ nodeActionLabel,
27288
+ className,
27289
+ indent
27290
+ }
27291
+ );
27172
27292
  }
27173
27293
  if (!tree || tree.length === 0) return null;
27174
27294
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
@@ -36410,13 +36530,35 @@ var init_RichTextEditor = __esm({
36410
36530
  document.execCommand("createLink", false, safe);
36411
36531
  afterEdit();
36412
36532
  }, [afterEdit, t, toolbar.link]);
36533
+ const imageInputRef = useRef(null);
36534
+ const savedRangeRef = useRef(null);
36413
36535
  const execImage = useCallback(() => {
36414
- const url = window.prompt(t("richTextEditor.imagePrompt"));
36415
- if (!url) return;
36416
- const safe = safeUrl(url, ["http://", "https://", "data:image/"]) ?? `https://${url}`;
36417
- document.execCommand("insertImage", false, safe);
36418
- afterEdit();
36419
- }, [afterEdit, t]);
36536
+ const root = ref.current;
36537
+ const sel = window.getSelection();
36538
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
36539
+ imageInputRef.current?.click();
36540
+ }, []);
36541
+ const handleImageFile = useCallback((e) => {
36542
+ const file = e.target.files?.[0];
36543
+ e.target.value = "";
36544
+ if (!file) return;
36545
+ const reader = new FileReader();
36546
+ reader.onload = () => {
36547
+ const url = typeof reader.result === "string" ? reader.result : "";
36548
+ if (!url.startsWith("data:image/")) return;
36549
+ const root = ref.current;
36550
+ if (!root) return;
36551
+ root.focus();
36552
+ const sel = window.getSelection();
36553
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
36554
+ sel.removeAllRanges();
36555
+ sel.addRange(savedRangeRef.current);
36556
+ }
36557
+ document.execCommand("insertImage", false, url);
36558
+ afterEdit();
36559
+ };
36560
+ reader.readAsDataURL(file);
36561
+ }, [afterEdit]);
36420
36562
  const execRule = useCallback(() => {
36421
36563
  document.execCommand("insertHorizontalRule", false);
36422
36564
  afterEdit();
@@ -36429,6 +36571,18 @@ var init_RichTextEditor = __esm({
36429
36571
  }
36430
36572
  return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
36431
36573
  /* @__PURE__ */ jsx(RichTextStyles, {}),
36574
+ /* @__PURE__ */ jsx(
36575
+ "input",
36576
+ {
36577
+ ref: imageInputRef,
36578
+ type: "file",
36579
+ accept: "image/*",
36580
+ className: "hidden",
36581
+ "aria-hidden": "true",
36582
+ tabIndex: -1,
36583
+ onChange: handleImageFile
36584
+ }
36585
+ ),
36432
36586
  showToolbar && /* @__PURE__ */ jsxs(
36433
36587
  Box,
36434
36588
  {
@@ -43328,15 +43482,6 @@ var init_Form = __esm({
43328
43482
  "data-testid": `image-preview-${fieldName2}`
43329
43483
  }
43330
43484
  ) : null,
43331
- /* @__PURE__ */ jsx(
43332
- Input,
43333
- {
43334
- ...commonProps,
43335
- type: "url",
43336
- value: imageUrl,
43337
- onChange: (e) => handleChange(fieldName2, e.target.value)
43338
- }
43339
- ),
43340
43485
  /* @__PURE__ */ jsx(
43341
43486
  UploadDropZone,
43342
43487
  {
@@ -43355,6 +43500,16 @@ var init_Form = __esm({
43355
43500
  reader.readAsDataURL(f3);
43356
43501
  }
43357
43502
  }
43503
+ ),
43504
+ /* @__PURE__ */ jsx(
43505
+ Input,
43506
+ {
43507
+ ...commonProps,
43508
+ type: "url",
43509
+ placeholder: t("form.imageUrlFallback"),
43510
+ value: imageUrl,
43511
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43512
+ }
43358
43513
  )
43359
43514
  ] });
43360
43515
  }
package/locales/ar.json CHANGED
@@ -550,5 +550,6 @@
550
550
  "richTextEditor.linkPrompt": "عنوان الرابط",
551
551
  "richTextEditor.image": "إدراج صورة",
552
552
  "richTextEditor.imagePrompt": "رابط الصورة",
553
- "richTextEditor.placeholder": "ابدأ الكتابة…"
553
+ "richTextEditor.placeholder": "ابدأ الكتابة…",
554
+ "form.imageUrlFallback": "…أو الصق رابط الصورة"
554
555
  }
package/locales/en.json CHANGED
@@ -550,5 +550,6 @@
550
550
  "richTextEditor.linkPrompt": "Link address",
551
551
  "richTextEditor.image": "Insert image",
552
552
  "richTextEditor.imagePrompt": "Image URL",
553
- "richTextEditor.placeholder": "Start writing…"
553
+ "richTextEditor.placeholder": "Start writing…",
554
+ "form.imageUrlFallback": "…or paste an image address"
554
555
  }
package/locales/sl.json CHANGED
@@ -550,5 +550,6 @@
550
550
  "richTextEditor.linkPrompt": "Naslov povezave",
551
551
  "richTextEditor.image": "Vstavi sliko",
552
552
  "richTextEditor.imagePrompt": "URL slike",
553
- "richTextEditor.placeholder": "Začnite pisati …"
553
+ "richTextEditor.placeholder": "Začnite pisati …",
554
+ "form.imageUrlFallback": "…ali prilepite naslov slike"
554
555
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.163.0",
3
+ "version": "5.165.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -118,11 +118,11 @@
118
118
  "access": "public"
119
119
  },
120
120
  "dependencies": {
121
- "@almadar/core": "^10.73.0",
121
+ "@almadar/core": "^10.75.0",
122
122
  "@almadar/evaluator": "^2.42.0",
123
123
  "@almadar/logger": "^1.11.0",
124
- "@almadar/runtime": "^6.63.0",
125
- "@almadar/std": "^16.192.0",
124
+ "@almadar/runtime": "^6.64.0",
125
+ "@almadar/std": "^16.194.0",
126
126
  "@almadar/syntax": "^1.15.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",