@pihanga2/shadcn 0.2.15 → 0.2.18
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/AGENT.building-cards.md +68 -0
- package/AGENT.using-cards.md +15 -3
- package/README.md +4 -1
- package/cards/conditional/conditional.component.js +30 -12
- package/cards/conditional/conditional.component.js.map +1 -1
- package/cards/conditional/conditional.types.d.ts +21 -3
- package/cards/conditional/conditional.types.js +1 -1
- package/cards/conditional/conditional.types.js.map +1 -1
- package/cards/fileDrop/fileDrop.component.js.map +1 -1
- package/cards/fileDrop/fileDrop.css +1 -1
- package/cards/jsonViewer/jsonViewer.css +1 -1
- package/cards/keyboardOverlay/keyboardOverlay.component.js +25 -13
- package/cards/keyboardOverlay/keyboardOverlay.component.js.map +1 -1
- package/cards/keyboardOverlay/keyboardOverlay.types.d.ts +22 -4
- package/cards/keyboardOverlay/keyboardOverlay.types.js.map +1 -1
- package/cards/loadingOverlay/loadingOverlay.css +1 -1
- package/cards/modeToggle/mode-toggle.component.js +1 -1
- package/cards/modeToggle/mode-toggle.component.js.map +1 -1
- package/cards/pageWithNavbar/pageWithNavbar.css +1 -1
- package/cards/pasteTarget/pasteTarget.css +1 -1
- package/cards/resizableColumns/index.d.ts +1 -0
- package/cards/resizableColumns/index.js +12 -0
- package/cards/resizableColumns/index.js.map +1 -0
- package/cards/resizableColumns/resizableColumns.component.d.ts +4 -0
- package/cards/resizableColumns/resizableColumns.component.js +105 -0
- package/cards/resizableColumns/resizableColumns.component.js.map +1 -0
- package/cards/resizableColumns/resizableColumns.css +1 -0
- package/cards/resizableColumns/resizableColumns.types.d.ts +22 -0
- package/cards/resizableColumns/resizableColumns.types.js +7 -0
- package/cards/resizableColumns/resizableColumns.types.js.map +1 -0
- package/cards/resizableGrid/index.d.ts +1 -0
- package/cards/resizableGrid/index.js +12 -0
- package/cards/resizableGrid/index.js.map +1 -0
- package/cards/resizableGrid/resizableGrid.component.d.ts +4 -0
- package/cards/resizableGrid/resizableGrid.component.js +166 -0
- package/cards/resizableGrid/resizableGrid.component.js.map +1 -0
- package/cards/resizableGrid/resizableGrid.css +1 -0
- package/cards/resizableGrid/resizableGrid.types.d.ts +33 -0
- package/cards/resizableGrid/resizableGrid.types.js +7 -0
- package/cards/resizableGrid/resizableGrid.types.js.map +1 -0
- package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.css +1 -1
- package/cards/suspense/index.d.ts +1 -0
- package/cards/suspense/index.js +12 -0
- package/cards/suspense/index.js.map +1 -0
- package/cards/suspense/suspense.component.d.ts +15 -0
- package/cards/suspense/suspense.component.js +45 -0
- package/cards/suspense/suspense.component.js.map +1 -0
- package/cards/suspense/suspense.types.d.ts +67 -0
- package/cards/suspense/suspense.types.js +7 -0
- package/cards/suspense/suspense.types.js.map +1 -0
- package/cards/typography/typography.component.js +13 -12
- package/cards/typography/typography.component.js.map +1 -1
- package/cards/typography/typography.types.d.ts +2 -0
- package/cards/typography/typography.types.js.map +1 -1
- package/package.json +17 -2
package/AGENT.building-cards.md
CHANGED
|
@@ -740,6 +740,74 @@ rethemable by the consumer:
|
|
|
740
740
|
<div className="bg-white text-gray-900 border border-gray-200 rounded-xl shadow-sm">
|
|
741
741
|
```
|
|
742
742
|
|
|
743
|
+
### ⚠️ CSS variables are oklch values — critical rules for card CSS files
|
|
744
|
+
|
|
745
|
+
This project uses **Tailwind v4 with oklch colour values**. The CSS custom
|
|
746
|
+
properties (`--border`, `--muted`, `--foreground`, etc.) are **complete** CSS
|
|
747
|
+
colour values — they are **not** HSL channel triplets.
|
|
748
|
+
|
|
749
|
+
**Rule 1 — Never wrap a variable in `hsl()`:**
|
|
750
|
+
|
|
751
|
+
```css
|
|
752
|
+
/* ❌ Invalid — wraps a full oklch(...) value inside hsl() */
|
|
753
|
+
background-color: hsl(var(--border, 214 32% 91%));
|
|
754
|
+
|
|
755
|
+
/* ✅ Correct — use the variable directly */
|
|
756
|
+
background-color: var(--border);
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
**Rule 2 — Never use `@media (prefers-color-scheme: dark)` in card CSS.**
|
|
760
|
+
The project uses class-based dark mode (a `.dark` class on `<html>`). A media
|
|
761
|
+
query won't fire when the user toggles the theme via the `modeToggle` card.
|
|
762
|
+
|
|
763
|
+
```css
|
|
764
|
+
/* ❌ Wrong — ignored when the .dark class is toggled programmatically */
|
|
765
|
+
@media (prefers-color-scheme: dark) {
|
|
766
|
+
.my-card { background-color: #161b22; }
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/* ✅ Correct — use .dark class override */
|
|
770
|
+
.dark .my-card { background-color: var(--card); }
|
|
771
|
+
|
|
772
|
+
/* ✅ Even better — use a CSS variable that adapts automatically */
|
|
773
|
+
.my-card { background-color: var(--card); } /* no dark override needed */
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
**Rule 3 — Use `color-mix()` for alpha/transparency:**
|
|
777
|
+
|
|
778
|
+
```css
|
|
779
|
+
/* ❌ Wrong — hardcoded rgba */
|
|
780
|
+
background: rgba(0, 0, 0, 0.3);
|
|
781
|
+
|
|
782
|
+
/* ✅ Correct — theme-aware semi-transparent foreground */
|
|
783
|
+
background: color-mix(in srgb, var(--foreground) 30%, transparent);
|
|
784
|
+
|
|
785
|
+
/* ✅ Correct — semi-transparent background overlay */
|
|
786
|
+
background: color-mix(in srgb, var(--background) 70%, transparent);
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**Rule 4 — Expose component-level colour tokens for cards with semantic colours.**
|
|
790
|
+
When a card needs colours that have no shadcn/ui equivalent (syntax highlighting,
|
|
791
|
+
annotation markers, etc.), define them as CSS custom properties in `:root` / `.dark`
|
|
792
|
+
rather than hardcoding hex values:
|
|
793
|
+
|
|
794
|
+
```css
|
|
795
|
+
/* ✅ Correct — semantic tokens with dark-mode variants */
|
|
796
|
+
:root {
|
|
797
|
+
--jv-color-string: oklch(0.45 0.15 145); /* green */
|
|
798
|
+
--jv-color-number: oklch(0.40 0.18 265); /* blue */
|
|
799
|
+
}
|
|
800
|
+
.dark {
|
|
801
|
+
--jv-color-string: oklch(0.70 0.12 145);
|
|
802
|
+
--jv-color-number: oklch(0.68 0.14 265);
|
|
803
|
+
}
|
|
804
|
+
.jv-string { color: var(--jv-color-string); }
|
|
805
|
+
|
|
806
|
+
/* ❌ Wrong — hardcoded per-class dark overrides */
|
|
807
|
+
.jv-string { color: #2e7d32; }
|
|
808
|
+
.dark .jv-string { color: #81c784; }
|
|
809
|
+
```
|
|
810
|
+
|
|
743
811
|
Standard shadcn tokens available as Tailwind utilities:
|
|
744
812
|
|
|
745
813
|
| Tailwind class | CSS variable | Default (light) |
|
package/AGENT.using-cards.md
CHANGED
|
@@ -626,7 +626,11 @@ import {registerFramework, registerCard, register} from "@pihanga2/core";
|
|
|
626
626
|
import {SdFramework} from "./cards/framework";
|
|
627
627
|
|
|
628
628
|
export function appPiInit(): void {
|
|
629
|
-
|
|
629
|
+
// theme: "system" defers to the OS preference on first load;
|
|
630
|
+
// "light" / "dark" force a specific mode.
|
|
631
|
+
// The user's choice (once they click the modeToggle) is persisted
|
|
632
|
+
// to localStorage under the key "shadcn-ui-theme".
|
|
633
|
+
registerFramework(SdFramework({page: "app/main", theme: "system"}));
|
|
630
634
|
registerCard("app/main", /* … card def … */);
|
|
631
635
|
}
|
|
632
636
|
```
|
|
@@ -675,12 +679,13 @@ import {
|
|
|
675
679
|
PageWithNavbar,
|
|
676
680
|
onPageWithNavbarNavigateTo,
|
|
677
681
|
} from "@/cards/pageWithNavbar";
|
|
682
|
+
import {ModeToggle} from "@/cards/modeToggle";
|
|
678
683
|
import {memo, register, registerCard, registerFramework} from "@pihanga2/core";
|
|
679
684
|
import {SdFramework} from "@/cards/framework";
|
|
680
685
|
import type {AppState} from "@/app.state";
|
|
681
686
|
|
|
682
687
|
export function appPiInit(): void {
|
|
683
|
-
registerFramework(SdFramework({page: "app/main", theme: "
|
|
688
|
+
registerFramework(SdFramework({page: "app/main", theme: "system"}));
|
|
684
689
|
|
|
685
690
|
register((r) => {
|
|
686
691
|
onPageWithNavbarNavigateTo(r, (state: AppState, {id}) => {
|
|
@@ -688,6 +693,9 @@ export function appPiInit(): void {
|
|
|
688
693
|
});
|
|
689
694
|
});
|
|
690
695
|
|
|
696
|
+
// Register the theme toggle button and place it in the navbar
|
|
697
|
+
registerCard("app/mode-toggle", ModeToggle({}));
|
|
698
|
+
|
|
691
699
|
registerCard("app/main", PageWithNavbar({
|
|
692
700
|
title: "My App",
|
|
693
701
|
navLinks: [
|
|
@@ -698,6 +706,10 @@ export function appPiInit(): void {
|
|
|
698
706
|
(s: AppState) => s.currentPage ?? "home",
|
|
699
707
|
(page) => `app/page/${page}`, // resolves to "app/page/home" etc.
|
|
700
708
|
),
|
|
709
|
+
// ── Theme toggle in the top-right of the navbar header ─────────────────
|
|
710
|
+
// Clicking the Sun/Moon button toggles light ↔ dark and persists the
|
|
711
|
+
// choice to localStorage. Remove this line to hide the toggle.
|
|
712
|
+
headerRightCard: "app/mode-toggle",
|
|
701
713
|
}));
|
|
702
714
|
|
|
703
715
|
registerCard("app/page/home", /* … */);
|
|
@@ -794,7 +806,7 @@ const inits = [appPiInit, playgroundPiInit];
|
|
|
794
806
|
// registerFramework call from playgroundPiInit.
|
|
795
807
|
export function appPiInit(): void {
|
|
796
808
|
playgroundPiInit(); // no longer calls registerFramework
|
|
797
|
-
registerFramework(SdFramework({page: "app/main", theme: "
|
|
809
|
+
registerFramework(SdFramework({page: "app/main", theme: "system"}));
|
|
798
810
|
// …
|
|
799
811
|
}
|
|
800
812
|
```
|
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ import "@pihanga2/shadcn/cards/dataTable";
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
## Included cards (
|
|
65
|
+
## Included cards (47)
|
|
66
66
|
|
|
67
67
|
- `avatar`
|
|
68
68
|
- `badge`
|
|
@@ -95,6 +95,8 @@ import "@pihanga2/shadcn/cards/dataTable";
|
|
|
95
95
|
- `pageWithNavbarMeta`
|
|
96
96
|
- `pasteTarget`
|
|
97
97
|
- `resizable`
|
|
98
|
+
- `resizableColumns`
|
|
99
|
+
- `resizableGrid`
|
|
98
100
|
- `scrollbarWithAnnotations`
|
|
99
101
|
- `select`
|
|
100
102
|
- `sheetCard`
|
|
@@ -102,6 +104,7 @@ import "@pihanga2/shadcn/cards/dataTable";
|
|
|
102
104
|
- `sliderValue`
|
|
103
105
|
- `stack`
|
|
104
106
|
- `stepper`
|
|
107
|
+
- `suspense`
|
|
105
108
|
- `switch`
|
|
106
109
|
- `tabs`
|
|
107
110
|
- `textField`
|
|
@@ -1,23 +1,41 @@
|
|
|
1
1
|
import { useBreakpoint as e, useContainerBreakpoint as t } from "../../components/hooks/use-breakpoint.js";
|
|
2
2
|
import * as n from "react";
|
|
3
3
|
import { Card as r } from "@pihanga2/core";
|
|
4
|
-
import {
|
|
4
|
+
import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
5
5
|
//#region src/cards/conditional/conditional.component.tsx
|
|
6
|
-
var
|
|
7
|
-
let { cardName:
|
|
8
|
-
return
|
|
9
|
-
ref:
|
|
6
|
+
var s = (s) => {
|
|
7
|
+
let { cardName: c, show: l = !0, showOn: u, containerQuery: d = !1, content: f, alternativeContent: p, keepMounted: m = !1 } = s, h = n.useRef(null), g = e(d ? void 0 : u), _ = t(d ? u : void 0, h), v = l && (d ? _ : g);
|
|
8
|
+
return d ? /* @__PURE__ */ a("div", {
|
|
9
|
+
ref: h,
|
|
10
10
|
style: { width: "100%" },
|
|
11
|
-
children:
|
|
12
|
-
cardName:
|
|
13
|
-
parentCard:
|
|
11
|
+
children: v ? /* @__PURE__ */ a(r, {
|
|
12
|
+
cardName: f,
|
|
13
|
+
parentCard: c
|
|
14
|
+
}) : p && /* @__PURE__ */ a(r, {
|
|
15
|
+
cardName: p,
|
|
16
|
+
parentCard: c
|
|
14
17
|
})
|
|
15
|
-
}) : m ? /* @__PURE__ */ i(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
}) : m ? /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a("div", {
|
|
19
|
+
style: { display: v ? "contents" : "none" },
|
|
20
|
+
children: /* @__PURE__ */ a(r, {
|
|
21
|
+
cardName: f,
|
|
22
|
+
parentCard: c
|
|
23
|
+
})
|
|
24
|
+
}), p && /* @__PURE__ */ a("div", {
|
|
25
|
+
style: { display: v ? "none" : "contents" },
|
|
26
|
+
children: /* @__PURE__ */ a(r, {
|
|
27
|
+
cardName: p,
|
|
28
|
+
parentCard: c
|
|
29
|
+
})
|
|
30
|
+
})] }) : v ? /* @__PURE__ */ a(r, {
|
|
31
|
+
cardName: f,
|
|
32
|
+
parentCard: c
|
|
33
|
+
}) : p ? /* @__PURE__ */ a(r, {
|
|
34
|
+
cardName: p,
|
|
35
|
+
parentCard: c
|
|
18
36
|
}) : null;
|
|
19
37
|
};
|
|
20
38
|
//#endregion
|
|
21
|
-
export {
|
|
39
|
+
export { s as ConditionalComponent };
|
|
22
40
|
|
|
23
41
|
//# sourceMappingURL=conditional.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conditional.component.js","names":[],"sources":["../../../src/cards/conditional/conditional.component.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {\n useBreakpoint,\n useContainerBreakpoint,\n} from \"@/components/hooks/use-breakpoint\";\nimport type {ConditionalProps} from \"./conditional.types\";\n\n/**\n * ConditionalComponent\n *\n * Renders the `content` card when the combined visibility condition is `true`,\n * returns `null` otherwise.\n *\n * Visibility rules (all must be satisfied):\n * 1. `show` — manual boolean gate (defaults to `true` when omitted)\n * 2. `showOn` — breakpoint selector (always `true` when omitted)\n * • `containerQuery: false` (default) → evaluated against the **viewport**\n * via `window.matchMedia`\n * • `containerQuery: true` → evaluated against the width of the\n * **enclosing container** via `ResizeObserver`; a thin `<div>` wrapper\n * is rendered so the container width can be measured\n *\n * Both hooks are always called unconditionally (React rules); whichever is not\n * \"active\" receives `undefined` and returns `true` immediately.\n */\nexport const ConditionalComponent = (\n props: PiCardProps<ConditionalProps>,\n): React.ReactNode => {\n const {\n cardName,\n show = true,\n showOn,\n containerQuery = false,\n content,\n } = props;\n\n // Ref for container-query mode — attached to the wrapper div.\n // Always created (hooks must not be conditional).\n const wrapperRef = React.useRef<HTMLDivElement>(null);\n\n // Viewport breakpoint: active when containerQuery is false.\n const viewportMatch = useBreakpoint(containerQuery ? undefined : showOn);\n\n // Container breakpoint: active when containerQuery is true.\n const containerMatch = useContainerBreakpoint(\n containerQuery ? showOn : undefined,\n wrapperRef,\n );\n\n const breakpointMatch = containerQuery ? containerMatch : viewportMatch;\n const visible = show && breakpointMatch;\n\n // ── Container-query mode ─────────────────────────────────────────────────\n // Render a full-width wrapper so ResizeObserver has something to measure.\n // The wrapper is always in the DOM (measuring must be continuous), but the\n // content card inside it is mounted/unmounted based on `visible`.\n if (containerQuery) {\n return (\n <div ref={wrapperRef} style={{width: \"100%\"}}>\n {visible
|
|
1
|
+
{"version":3,"file":"conditional.component.js","names":[],"sources":["../../../src/cards/conditional/conditional.component.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {\n useBreakpoint,\n useContainerBreakpoint,\n} from \"@/components/hooks/use-breakpoint\";\nimport type {ConditionalProps} from \"./conditional.types\";\n\n/**\n * ConditionalComponent\n *\n * Renders the `content` card when the combined visibility condition is `true`,\n * returns `null` otherwise.\n *\n * Visibility rules (all must be satisfied):\n * 1. `show` — manual boolean gate (defaults to `true` when omitted)\n * 2. `showOn` — breakpoint selector (always `true` when omitted)\n * • `containerQuery: false` (default) → evaluated against the **viewport**\n * via `window.matchMedia`\n * • `containerQuery: true` → evaluated against the width of the\n * **enclosing container** via `ResizeObserver`; a thin `<div>` wrapper\n * is rendered so the container width can be measured\n *\n * Both hooks are always called unconditionally (React rules); whichever is not\n * \"active\" receives `undefined` and returns `true` immediately.\n */\nexport const ConditionalComponent = (\n props: PiCardProps<ConditionalProps>,\n): React.ReactNode => {\n const {\n cardName,\n show = true,\n showOn,\n containerQuery = false,\n content,\n alternativeContent,\n keepMounted = false,\n } = props;\n\n // Ref for container-query mode — attached to the wrapper div.\n // Always created (hooks must not be conditional).\n const wrapperRef = React.useRef<HTMLDivElement>(null);\n\n // Viewport breakpoint: active when containerQuery is false.\n const viewportMatch = useBreakpoint(containerQuery ? undefined : showOn);\n\n // Container breakpoint: active when containerQuery is true.\n const containerMatch = useContainerBreakpoint(\n containerQuery ? showOn : undefined,\n wrapperRef,\n );\n\n const breakpointMatch = containerQuery ? containerMatch : viewportMatch;\n const visible = show && breakpointMatch;\n\n // ── Container-query mode ─────────────────────────────────────────────────\n // Render a full-width wrapper so ResizeObserver has something to measure.\n // The wrapper is always in the DOM (measuring must be continuous), but the\n // content card inside it is mounted/unmounted based on `visible`.\n if (containerQuery) {\n return (\n <div ref={wrapperRef} style={{width: \"100%\"}}>\n {visible ? (\n <Card cardName={content} parentCard={cardName} />\n ) : (\n alternativeContent && (\n <Card cardName={alternativeContent} parentCard={cardName} />\n )\n )}\n </div>\n );\n }\n\n // ── keepMounted mode ─────────────────────────────────────────────────────\n // Keep the subtree mounted at all times; toggle display so React never\n // destroys expensive components (e.g. Plate editors) on hide.\n // `display:contents` makes the wrapper transparent to layout when visible;\n // `display:none` hides it entirely when not.\n if (keepMounted) {\n return (\n <>\n <div style={{display: visible ? \"contents\" : \"none\"}}>\n <Card cardName={content} parentCard={cardName} />\n </div>\n {alternativeContent && (\n <div style={{display: visible ? \"none\" : \"contents\"}}>\n <Card cardName={alternativeContent} parentCard={cardName} />\n </div>\n )}\n </>\n );\n }\n\n // ── Viewport / manual mode ───────────────────────────────────────────────\n // Transparent pass-through — no extra DOM node.\n if (!visible) {\n return alternativeContent ? (\n <Card cardName={alternativeContent} parentCard={cardName} />\n ) : null;\n }\n return <Card cardName={content} parentCard={cardName} />;\n};\n"],"mappings":";;;;;AA0BA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,aACA,UAAO,IACP,WACA,oBAAiB,IACjB,YACA,uBACA,iBAAc,OACZ,GAIE,IAAa,EAAM,OAAuB,IAAI,GAG9C,IAAgB,EAAc,IAAiB,KAAA,IAAY,CAAM,GAGjE,IAAiB,EACrB,IAAiB,IAAS,KAAA,GAC1B,CACF,GAGM,IAAU,MADQ,IAAiB,IAAiB;CAgD1D,OAzCI,IAEA,kBAAC,OAAD;EAAK,KAAK;EAAY,OAAO,EAAC,OAAO,OAAM;YACxC,IACC,kBAAC,GAAD;GAAM,UAAU;GAAS,YAAY;EAAW,CAAA,IAEhD,KACE,kBAAC,GAAD;GAAM,UAAU;GAAoB,YAAY;EAAW,CAAA;CAG5D,CAAA,IASL,IAEA,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;EAAK,OAAO,EAAC,SAAS,IAAU,aAAa,OAAM;YACjD,kBAAC,GAAD;GAAM,UAAU;GAAS,YAAY;EAAW,CAAA;CAC7C,CAAA,GACJ,KACC,kBAAC,OAAD;EAAK,OAAO,EAAC,SAAS,IAAU,SAAS,WAAU;YACjD,kBAAC,GAAD;GAAM,UAAU;GAAoB,YAAY;EAAW,CAAA;CACxD,CAAA,CAEP,EAAA,CAAA,IAMD,IAKE,kBAAC,GAAD;EAAM,UAAU;EAAS,YAAY;CAAW,CAAA,IAJ9C,IACL,kBAAC,GAAD;EAAM,UAAU;EAAoB,YAAY;CAAW,CAAA,IACzD;AAGR"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PiCardRef } from '@pihanga2/core';
|
|
2
|
-
export declare const CONDITIONAL_CARD = "
|
|
2
|
+
export declare const CONDITIONAL_CARD = "pi/conditional";
|
|
3
3
|
export declare const Conditional: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<ConditionalProps, S, {}>) => import('@pihanga2/core').PiCardDef;
|
|
4
4
|
/**
|
|
5
5
|
* Named Tailwind-compatible breakpoints.
|
|
@@ -94,6 +94,14 @@ export type BreakpointMap<T = string> = Partial<Record<BreakpointName, T>>;
|
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
96
|
export type ConditionalProps = {
|
|
97
|
+
/** The card to render when the visibility condition is met. */
|
|
98
|
+
content: PiCardRef;
|
|
99
|
+
/**
|
|
100
|
+
* Optional fallback card to render when **all** visibility conditions
|
|
101
|
+
* (`show`, `showOn`) evaluate to `false`. When omitted, nothing is
|
|
102
|
+
* rendered in the hidden state (existing behaviour).
|
|
103
|
+
*/
|
|
104
|
+
alternativeContent?: PiCardRef;
|
|
97
105
|
/**
|
|
98
106
|
* Manual boolean gate. When omitted it defaults to `true` so that a
|
|
99
107
|
* `showOn`-only card does not need to set it explicitly.
|
|
@@ -131,6 +139,16 @@ export type ConditionalProps = {
|
|
|
131
139
|
* @default false
|
|
132
140
|
*/
|
|
133
141
|
containerQuery?: boolean;
|
|
134
|
-
/**
|
|
135
|
-
|
|
142
|
+
/**
|
|
143
|
+
* When `true`, the content card stays **mounted** even when the visibility
|
|
144
|
+
* condition is `false`. Instead of returning `null`, the component wraps
|
|
145
|
+
* the card in a `<div style="display:none">` (hidden) or
|
|
146
|
+
* `<div style="display:contents">` (transparent pass-through when visible).
|
|
147
|
+
*
|
|
148
|
+
* Use this for expensive subtrees — such as Plate editors — where
|
|
149
|
+
* remounting from scratch on every show/hide is too costly.
|
|
150
|
+
*
|
|
151
|
+
* @default false
|
|
152
|
+
*/
|
|
153
|
+
keepMounted?: boolean;
|
|
136
154
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conditional.types.js","names":[],"sources":["../../../src/cards/conditional/conditional.types.ts"],"sourcesContent":["import {createCardDeclaration} from \"@pihanga2/core\";\nimport type {PiCardRef} from \"@pihanga2/core\";\n\n// ── Card id ───────────────────────────────────────────────────────────────────\n\nexport const CONDITIONAL_CARD = \"
|
|
1
|
+
{"version":3,"file":"conditional.types.js","names":[],"sources":["../../../src/cards/conditional/conditional.types.ts"],"sourcesContent":["import {createCardDeclaration} from \"@pihanga2/core\";\nimport type {PiCardRef} from \"@pihanga2/core\";\n\n// ── Card id ───────────────────────────────────────────────────────────────────\n\nexport const CONDITIONAL_CARD = \"pi/conditional\";\n\n// ── Card declaration factory ──────────────────────────────────────────────────\n\nexport const Conditional =\n createCardDeclaration<ConditionalProps>(CONDITIONAL_CARD);\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/**\n * Named Tailwind-compatible breakpoints.\n *\n * `xs` → viewport < 640 px (mobile-first default)\n * `sm` → viewport ≥ 640 px\n * `md` → viewport ≥ 768 px\n * `lg` → viewport ≥ 1024 px\n * `xl` → viewport ≥ 1280 px\n * `2xl` → viewport ≥ 1536 px\n */\nexport type BreakpointName = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\n/**\n * A `BreakpointName` **or** a custom pixel expression.\n *\n * Pixel expressions\n * ─────────────────\n * `400px` → viewport ≥ 400 px (bare value = min-width)\n * `>=640px` → viewport ≥ 640 px\n * `>640px` → viewport > 640 px (min-width: 641px)\n * `<=1024px` → viewport ≤ 1024 px\n * `<1024px` → viewport < 1024 px (max-width: 1023px)\n */\nexport type BreakpointSelector =\n | BreakpointName\n | (string & Record<never, never>); // allows arbitrary strings with IDE hints\n\n/**\n * A partial map from each named breakpoint to a value of type `T`.\n *\n * Useful for props that need per-breakpoint configuration — e.g. the CSS\n * `display` value to apply at each viewport width.\n *\n * @example\n * ```ts\n * const display: BreakpointMap = { xs: \"none\", md: \"flex\" };\n * ```\n */\nexport type BreakpointMap<T = string> = Partial<Record<BreakpointName, T>>;\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\n/**\n * Props for the `shad/conditional` card.\n *\n * The card renders `content` only when the combined visibility condition is\n * `true`; it renders nothing otherwise. This is a transparent pass-through —\n * no extra DOM wrapper is added.\n *\n * ## Modes\n *\n * ### 1 · Manual boolean (existing behaviour)\n *\n * Drive `show` from a `memo()` selector so the card reactively mounts/unmounts\n * as state changes:\n *\n * ```ts\n * import {memo, registerCard} from \"@pihanga2/core\";\n * import {Conditional} from \"@/cards/conditional\";\n *\n * registerCard(\"myApp/hint\", Conditional({\n * show: memo((s: AppState) => s.items.length === 0 && !s.isLoading),\n * content: \"myApp/emptyStateHint\",\n * }));\n * ```\n *\n * ### 2 · Breakpoint-based auto-selection (new)\n *\n * Set `showOn` to a breakpoint name or pixel expression. The component\n * subscribes to `window.matchMedia` and automatically mounts/unmounts the\n * content card as the viewport crosses the breakpoint — no state, no memo:\n *\n * ```ts\n * // Show only on tablet-sized screens and up\n * registerCard(\"myApp/sidebar\", Conditional({\n * showOn: \"md\",\n * content: \"myApp/desktopSidebar\",\n * }));\n *\n * // Show only on narrow viewports (mobile drawer)\n * registerCard(\"myApp/mobileNav\", Conditional({\n * showOn: \"<768px\",\n * content: \"myApp/drawer\",\n * }));\n * ```\n *\n * ### 3 · Combined\n *\n * Both conditions are ANDed — content renders only when the breakpoint matches\n * **and** `show` is `true`:\n *\n * ```ts\n * registerCard(\"myApp/adminSidebar\", Conditional({\n * show: memo((s: AppState) => s.isAdmin),\n * showOn: \"lg\",\n * content: \"myApp/adminPanel\",\n * }));\n * ```\n */\nexport type ConditionalProps = {\n /** The card to render when the visibility condition is met. */\n content: PiCardRef;\n\n /**\n * Optional fallback card to render when **all** visibility conditions\n * (`show`, `showOn`) evaluate to `false`. When omitted, nothing is\n * rendered in the hidden state (existing behaviour).\n */\n alternativeContent?: PiCardRef;\n\n /**\n * Manual boolean gate. When omitted it defaults to `true` so that a\n * `showOn`-only card does not need to set it explicitly.\n *\n * Drive with `memo()` for reactive mount/unmount behaviour.\n */\n show?: boolean;\n\n /**\n * Viewport-width breakpoint selector. When set, the component subscribes\n * to `window.matchMedia` and reactively shows/hides the content card\n * whenever the viewport crosses the breakpoint.\n *\n * Supported values: `xs` | `sm` | `md` | `lg` | `xl` | `2xl` (Tailwind),\n * or pixel expressions such as `>400px`, `>=640px`, `<768px`, `<=1024px`,\n * `400px`.\n *\n * When both `show` and `showOn` are provided the content is rendered only\n * when **both** conditions are satisfied.\n *\n * By default the breakpoint is evaluated against the **viewport** width via\n * `window.matchMedia`. Set `containerQuery: true` to evaluate it against\n * the width of the **enclosing container** instead (uses `ResizeObserver`).\n */\n showOn?: BreakpointSelector;\n\n /**\n * When `true`, the `showOn` breakpoint is measured against the width of the\n * component's **enclosing container** rather than the viewport.\n *\n * Internally the component renders a transparent `<div style=\"width:100%\">`\n * wrapper and observes it with `ResizeObserver`. This means a single extra\n * DOM node is added when `containerQuery` is `true`.\n *\n * Has no effect when `showOn` is not set.\n *\n * @default false\n */\n containerQuery?: boolean;\n\n /**\n * When `true`, the content card stays **mounted** even when the visibility\n * condition is `false`. Instead of returning `null`, the component wraps\n * the card in a `<div style=\"display:none\">` (hidden) or\n * `<div style=\"display:contents\">` (transparent pass-through when visible).\n *\n * Use this for expensive subtrees — such as Plate editors — where\n * remounting from scratch on every show/hide is too costly.\n *\n * @default false\n */\n keepMounted?: boolean;\n};\n"],"mappings":";;AAKA,IAAa,IAAmB,kBAInB,IACX,EAAwC,CAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileDrop.component.js","names":[],"sources":["../../../src/cards/fileDrop/fileDrop.component.tsx"],"sourcesContent":["import React from \"react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {FileUploader} from \"react-drag-drop-files\";\nimport {\n DEF_FILE_DROP_FILE_TYPES,\n getFileDropTheme,\n type FileDropEvents,\n type FileDropProps,\n} from \"./fileDrop.types\";\nimport {getIcon} from \"../icons\";\nimport \"./fileDrop.css\";\n\ntype LastDropped = {name: string; file: File};\n\nconst KEY = Symbol.for(\"pihanga.card.FileDrop.LastDropped\");\n\nconst globalForCache = globalThis as unknown as Record<\n symbol,\n LastDropped | null | undefined\n>;\n\
|
|
1
|
+
{"version":3,"file":"fileDrop.component.js","names":[],"sources":["../../../src/cards/fileDrop/fileDrop.component.tsx"],"sourcesContent":["import React from \"react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {FileUploader} from \"react-drag-drop-files\";\nimport {\n DEF_FILE_DROP_FILE_TYPES,\n getFileDropTheme,\n type FileDropEvents,\n type FileDropProps,\n} from \"./fileDrop.types\";\nimport {getIcon} from \"../icons\";\nimport \"./fileDrop.css\";\n\ntype LastDropped = {name: string; file: File};\n\nconst KEY = Symbol.for(\"pihanga.card.FileDrop.LastDropped\");\n\nconst globalForCache = globalThis as unknown as Record<\n symbol,\n LastDropped | null | undefined\n>;\n\nexport function get_last_dropped(name: string): File | null {\n const slot = (globalForCache[KEY] ??= null);\n if (slot?.name === name) {\n return slot.file;\n }\n return null;\n}\n\nfunction setLastDropped(value: LastDropped): void {\n globalForCache[KEY] = value;\n}\n\nfunction clearLastDropped(): void {\n globalForCache[KEY] = null;\n}\n\nexport const FileDropComponent = (\n props: PiCardProps<FileDropProps, FileDropEvents>,\n): React.ReactNode => {\n const {\n fileTypes = DEF_FILE_DROP_FILE_TYPES,\n title = \"Click or drop a file right here\",\n description,\n showProgress = false,\n progress = 0,\n progressStyle = {},\n dropStyle = {},\n icon,\n iconProps,\n browseLabel,\n onFileDropped,\n onError,\n cardName,\n theme,\n classNames,\n className,\n _cls,\n } = props;\n\n // Merge theme (base) with per-card classNames (overrides)\n const cn = {...(theme ? getFileDropTheme(theme) : {}), ...classNames};\n\n function handleChange(file: File | File[]): void {\n const f = Array.isArray(file) ? file[0] : file;\n if (!f) return;\n const {name, size, type} = f;\n setLastDropped({name, file: f});\n onFileDropped({name, size, type});\n // clean up reference to File in a few sec to avoid dangling reference\n setTimeout(clearLastDropped, 2000);\n }\n\n function handleTypeError(err: unknown): void {\n onError({error: String(err)});\n }\n\n function renderProgress(): React.ReactNode {\n const label = `${progress}%`;\n const msg = `${label} Complete`;\n const containerStyle = {width: \"50%\", ...progressStyle};\n return (\n <div className=\"pi-progress\">\n <div className=\"pi-progress-label\">{label}</div>\n <div\n className=\"pi-progress-container\"\n style={containerStyle as React.CSSProperties}\n >\n <div\n className=\"pi-progress-bar\"\n style={{width: label}}\n role=\"progressbar\"\n aria-label={msg}\n />\n </div>\n </div>\n );\n }\n\n function renderDropZone(): React.ReactElement {\n const dzCn = [\"dropzone-msg\", cn.dropZone].filter(Boolean).join(\" \");\n return (\n <div className={dzCn} style={dropStyle as React.CSSProperties}>\n {icon && <div className={cn.icon}>{getIcon(icon, iconProps)}</div>}\n {title && (\n <h3\n className={[\"dropzone-msg-title\", cn.title]\n .filter(Boolean)\n .join(\" \")}\n >\n {title}\n </h3>\n )}\n {description && (\n <span\n className={[\"dropzone-msg-desc\", cn.description]\n .filter(Boolean)\n .join(\" \")}\n >\n {description}\n </span>\n )}\n {browseLabel && <div className={cn.browseButton}>{browseLabel}</div>}\n </div>\n );\n }\n\n function renderFileUploader(): React.ReactNode {\n return (\n <FileUploader\n handleChange={handleChange}\n onTypeError={handleTypeError}\n name=\"file\"\n types={fileTypes}\n hoverTitle=\" \"\n >\n {renderDropZone()}\n </FileUploader>\n );\n }\n\n // classNames.root takes precedence over the legacy className prop\n const rootCn = _cls(\"root\", cn.root ?? className);\n return (\n <div className={rootCn} data-pihanga={cardName}>\n {showProgress ? renderProgress() : renderFileUploader()}\n </div>\n );\n};\n"],"mappings":";;;;;;;AAcA,IAAM,IAAM,OAAO,IAAI,mCAAmC,GAEpD,IAAiB;AAKvB,SAAgB,EAAiB,GAA2B;CAC1D,IAAM,IAAQ,EAAe,OAAS;CAItC,OAHI,GAAM,SAAS,IACV,EAAK,OAEP;AACT;AAEA,SAAS,EAAe,GAA0B;CAChD,EAAe,KAAO;AACxB;AAEA,SAAS,IAAyB;CAChC,EAAe,KAAO;AACxB;AAEA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,eAAY,GACZ,WAAQ,mCACR,gBACA,kBAAe,IACf,cAAW,GACX,mBAAgB,CAAC,GACjB,eAAY,CAAC,GACb,SACA,cACA,gBACA,kBACA,YACA,aACA,UACA,eACA,cACA,YACE,GAGE,IAAK;EAAC,GAAI,IAAQ,EAAiB,CAAK,IAAI,CAAC;EAAI,GAAG;CAAU;CAEpE,SAAS,EAAa,GAA2B;EAC/C,IAAM,IAAI,MAAM,QAAQ,CAAI,IAAI,EAAK,KAAK;EAC1C,IAAI,CAAC,GAAG;EACR,IAAM,EAAC,SAAM,SAAM,YAAQ;EAI3B,AAHA,EAAe;GAAC;GAAM,MAAM;EAAC,CAAC,GAC9B,EAAc;GAAC;GAAM;GAAM;EAAI,CAAC,GAEhC,WAAW,GAAkB,GAAI;CACnC;CAEA,SAAS,EAAgB,GAAoB;EAC3C,EAAQ,EAAC,OAAO,OAAO,CAAG,EAAC,CAAC;CAC9B;CAEA,SAAS,IAAkC;EACzC,IAAM,IAAQ,GAAG,EAAS,IACpB,IAAM,GAAG,EAAM,YACf,IAAiB;GAAC,OAAO;GAAO,GAAG;EAAa;EACtD,OACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAqB;GAAW,CAAA,GAC/C,kBAAC,OAAD;IACE,WAAU;IACV,OAAO;cAEP,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAC,OAAO,EAAK;KACpB,MAAK;KACL,cAAY;IACb,CAAA;GACE,CAAA,CACF;;CAET;CAEA,SAAS,IAAqC;EAE5C,OACE,kBAAC,OAAD;GAAK,WAFM,CAAC,gBAAgB,EAAG,QAAQ,EAAE,OAAO,OAAO,EAAE,KAAK,GAE9C;GAAM,OAAO;aAA7B;IACG,KAAQ,kBAAC,OAAD;KAAK,WAAW,EAAG;eAAO,EAAQ,GAAM,CAAS;IAAO,CAAA;IAChE,KACC,kBAAC,MAAD;KACE,WAAW,CAAC,sBAAsB,EAAG,KAAK,EACvC,OAAO,OAAO,EACd,KAAK,GAAG;eAEV;IACC,CAAA;IAEL,KACC,kBAAC,QAAD;KACE,WAAW,CAAC,qBAAqB,EAAG,WAAW,EAC5C,OAAO,OAAO,EACd,KAAK,GAAG;eAEV;IACG,CAAA;IAEP,KAAe,kBAAC,OAAD;KAAK,WAAW,EAAG;eAAe;IAAiB,CAAA;GAChE;;CAET;CAEA,SAAS,IAAsC;EAC7C,OACE,kBAAC,GAAD;GACgB;GACd,aAAa;GACb,MAAK;GACL,OAAO;GACP,YAAW;aAEV,EAAe;EACJ,CAAA;CAElB;CAIA,OACE,kBAAC,OAAD;EAAK,WAFQ,EAAK,QAAQ,EAAG,QAAQ,CAErB;EAAQ,gBAAc;YACnC,IAAe,EAAe,IAAI,EAAmB;CACnD,CAAA;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.pi-file-drop-root label{border:2px
|
|
1
|
+
.pi-file-drop-root label{border:dashed 2px var(--border);flex-grow:1;justify-content:space-between;max-width:100%;display:flex}.pi-file-drop-root .dropzone-msg{cursor:pointer;text-align:center;flex-grow:4;margin:3em 0}.pi-file-drop-root .pi-progress{max-width:100%;padding:1.25em}.pi-file-drop-root .pi-progress-label{font-size:1rem;font-weight:600;line-height:1.5rem}.pi-file-drop-root.pi-progress-container{border-radius:var(--radius-sm);background-color:var(--muted);height:1rem;display:flex;position:relative;overflow:hidden}.pi-file-drop-root .pi-progress-bar{background-color:var(--primary);color:var(--primary-foreground);flex-direction:column;justify-content:center;display:flex;overflow:hidden}.pi-file-drop-root .pi-progress .label{font-size:1em;display:block}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.jv-wrapper{position:relative}.jv-copy-btn{z-index:10;width:22px;height:22px;color:
|
|
1
|
+
:root{--jv-color-string:oklch(45% .15 145);--jv-color-number:oklch(40% .18 265);--jv-color-boolean:oklch(45% .1 188);--jv-color-null:oklch(45% .2 27);--jv-color-done:oklch(45% .15 145)}.dark{--jv-color-string:oklch(70% .12 145);--jv-color-number:oklch(68% .14 265);--jv-color-boolean:oklch(65% .1 188);--jv-color-null:oklch(73% .14 27);--jv-color-done:oklch(70% .12 145)}.jv-wrapper{position:relative}.jv-copy-btn{z-index:10;border-radius:var(--radius-sm);width:22px;height:22px;color:var(--muted-foreground);cursor:pointer;opacity:.35;background:0 0;border:1px solid #0000;justify-content:center;align-items:center;padding:0;transition:opacity .15s,color .15s,background .15s,border-color .15s;display:inline-flex;position:absolute;top:4px;right:4px}.jv-wrapper:hover .jv-copy-btn,.jv-wrapper:focus-within .jv-copy-btn{opacity:1}.jv-copy-btn:hover{color:var(--foreground);background:var(--muted);border-color:var(--border)}.jv-copy-btn--done{opacity:1;color:var(--jv-color-done)}.jv-container{white-space:pre-wrap;word-wrap:break-word;color:var(--foreground);background:0 0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:12.5px;font-weight:400;line-height:1.55}.jv-basic-child{margin:0;padding:0 0 0 1.1em;display:block;position:relative}.jv-label{color:var(--foreground);margin-right:0;font-weight:400}.jv-clickable-label{color:var(--foreground);cursor:pointer;margin-right:0;font-weight:400}.jv-clickable-label:hover{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.jv-punctuation{color:var(--foreground);font-weight:400}.jv-expand-icon,.jv-collapse-icon{width:1em;color:var(--muted-foreground);-webkit-user-select:none;user-select:none;cursor:pointer;justify-content:center;align-items:center;font-size:.65em;line-height:1;display:inline-flex;position:absolute;top:.775em;left:0;transform:translateY(-50%)}.jv-expand-icon:after{content:"▶"}.jv-collapse-icon:after{content:"▼"}.jv-collapsed-content{cursor:pointer;color:var(--muted-foreground)}.jv-collapsed-content:after{content:" …"}.jv-child-fields-container{border-left:1px solid var(--border);margin:0 0 0 .3em;padding:0 0 0 1.25em}.jv-null,.jv-undefined{color:var(--jv-color-null)}.jv-string{color:var(--jv-color-string)}.jv-number{color:var(--jv-color-number)}.jv-boolean{color:var(--jv-color-boolean)}.jv-other{color:var(--foreground)}
|
|
@@ -2,12 +2,12 @@ import { useEffect as e, useRef as t } from "react";
|
|
|
2
2
|
import { Card as n } from "@pihanga2/core";
|
|
3
3
|
import { jsx as r } from "react/jsx-runtime";
|
|
4
4
|
//#region src/cards/keyboardOverlay/keyboardOverlay.component.tsx
|
|
5
|
-
function i(e) {
|
|
6
|
-
let
|
|
7
|
-
for (;
|
|
8
|
-
let e =
|
|
5
|
+
function i(e, t) {
|
|
6
|
+
let n = e;
|
|
7
|
+
for (; n;) {
|
|
8
|
+
let e = n.getAttribute(`data-${t}`);
|
|
9
9
|
if (e) return e;
|
|
10
|
-
|
|
10
|
+
n = n.parentElement;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
function a(e, t) {
|
|
@@ -15,13 +15,13 @@ function a(e, t) {
|
|
|
15
15
|
return (n.has("ctrl") ? e.ctrlKey : !e.ctrlKey) && (n.has("shift") ? e.shiftKey : !e.shiftKey) && (n.has("alt") ? e.altKey : !e.altKey) && (n.has("meta") ? e.metaKey : !e.metaKey);
|
|
16
16
|
}
|
|
17
17
|
var o = (o) => {
|
|
18
|
-
let { content: s, shortcuts: c, onShortcut: l, cardName: u, className: d, style: f } = o,
|
|
18
|
+
let { content: s, shortcuts: c, onShortcut: l, cardName: u, className: d, style: f, captureFocus: p = !0, dataKey: m = "pihanga" } = o, h = t({
|
|
19
19
|
x: 0,
|
|
20
20
|
y: 0
|
|
21
|
-
});
|
|
22
|
-
|
|
21
|
+
}), g = t(null);
|
|
22
|
+
e(() => {
|
|
23
23
|
let e = (e) => {
|
|
24
|
-
|
|
24
|
+
h.current = {
|
|
25
25
|
x: e.clientX,
|
|
26
26
|
y: e.clientY
|
|
27
27
|
};
|
|
@@ -30,12 +30,12 @@ var o = (o) => {
|
|
|
30
30
|
let n = t.modifiers ?? [];
|
|
31
31
|
if (!(e.key === t.key || e.code === t.key) || !a(e, n)) continue;
|
|
32
32
|
t.propagate || (e.preventDefault(), e.stopPropagation());
|
|
33
|
-
let { x: r, y: o } =
|
|
33
|
+
let { x: r, y: o } = h.current, s = i(document.elementFromPoint(r, o), m);
|
|
34
34
|
l({
|
|
35
35
|
shortcutId: t.id ?? t.key,
|
|
36
36
|
key: t.key,
|
|
37
37
|
modifiers: n,
|
|
38
|
-
|
|
38
|
+
dataValue: s,
|
|
39
39
|
cursorX: r,
|
|
40
40
|
cursorY: o
|
|
41
41
|
});
|
|
@@ -45,12 +45,24 @@ var o = (o) => {
|
|
|
45
45
|
return document.addEventListener("mousemove", e, { passive: !0 }), document.addEventListener("keydown", t, { capture: !0 }), () => {
|
|
46
46
|
document.removeEventListener("mousemove", e), document.removeEventListener("keydown", t, { capture: !0 });
|
|
47
47
|
};
|
|
48
|
-
}, [
|
|
48
|
+
}, [
|
|
49
|
+
c,
|
|
50
|
+
l,
|
|
51
|
+
m
|
|
52
|
+
]);
|
|
53
|
+
function _() {
|
|
54
|
+
p && g.current?.focus();
|
|
55
|
+
}
|
|
56
|
+
return /* @__PURE__ */ r("div", {
|
|
57
|
+
ref: g,
|
|
58
|
+
tabIndex: p ? -1 : void 0,
|
|
59
|
+
onMouseEnter: _,
|
|
49
60
|
"data-pihanga": u,
|
|
50
61
|
className: d,
|
|
51
62
|
style: {
|
|
52
63
|
...f,
|
|
53
|
-
position: "relative"
|
|
64
|
+
position: "relative",
|
|
65
|
+
outline: "none"
|
|
54
66
|
},
|
|
55
67
|
children: /* @__PURE__ */ r(n, {
|
|
56
68
|
cardName: s,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboardOverlay.component.js","names":[],"sources":["../../../src/cards/keyboardOverlay/keyboardOverlay.component.tsx"],"sourcesContent":["import {Card, type PiCardProps} from \"@pihanga2/core\";\nimport React, {useEffect, useRef} from \"react\";\n\nimport type {\n KeyboardOverlayEvents,\n KeyboardOverlayProps,\n Modifier,\n} from \"./keyboardOverlay.types\";\n\n/** Walk up the DOM from `el` and return the first `data-
|
|
1
|
+
{"version":3,"file":"keyboardOverlay.component.js","names":[],"sources":["../../../src/cards/keyboardOverlay/keyboardOverlay.component.tsx"],"sourcesContent":["import {Card, type PiCardProps} from \"@pihanga2/core\";\nimport React, {useEffect, useRef} from \"react\";\n\nimport type {\n KeyboardOverlayEvents,\n KeyboardOverlayProps,\n Modifier,\n} from \"./keyboardOverlay.types\";\n\n/** Walk up the DOM from `el` and return the first value of `data-{dataKey}` found. */\nfunction findDataAttribute(\n el: Element | null,\n dataKey: string,\n): string | undefined {\n let node: Element | null = el;\n while (node) {\n const attr = node.getAttribute(`data-${dataKey}`);\n if (attr) return attr;\n node = node.parentElement;\n }\n return undefined;\n}\n\n/** Returns `true` when the keyboard event's active modifiers exactly match the definition. */\nfunction modifiersMatch(e: KeyboardEvent, required: Modifier[]): boolean {\n const need = new Set(required);\n return (\n (need.has(\"ctrl\") ? e.ctrlKey : !e.ctrlKey) &&\n (need.has(\"shift\") ? e.shiftKey : !e.shiftKey) &&\n (need.has(\"alt\") ? e.altKey : !e.altKey) &&\n (need.has(\"meta\") ? e.metaKey : !e.metaKey)\n );\n}\n\nexport const KeyboardOverlayComponent = (\n props: PiCardProps<KeyboardOverlayProps, KeyboardOverlayEvents>,\n): React.ReactNode => {\n const {\n content,\n shortcuts,\n onShortcut,\n cardName,\n className,\n style,\n captureFocus = true,\n dataKey = \"pihanga\",\n } = props;\n\n // Track cursor position passively — no re-render needed.\n const cursorPos = useRef<{x: number; y: number}>({x: 0, y: 0});\n const divRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const onMouseMove = (e: MouseEvent) => {\n cursorPos.current = {x: e.clientX, y: e.clientY};\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n for (const shortcut of shortcuts) {\n const mods: Modifier[] = shortcut.modifiers ?? [];\n const keyMatch = e.key === shortcut.key || e.code === shortcut.key;\n if (!keyMatch || !modifiersMatch(e, mods)) continue;\n\n // Only consume the event when propagate !== true.\n if (!shortcut.propagate) {\n e.preventDefault();\n e.stopPropagation();\n }\n\n const {x, y} = cursorPos.current;\n const el = document.elementFromPoint(x, y);\n const dataValue = findDataAttribute(el, dataKey);\n\n onShortcut({\n shortcutId: shortcut.id ?? shortcut.key,\n key: shortcut.key,\n modifiers: mods,\n dataValue,\n cursorX: x,\n cursorY: y,\n });\n break;\n }\n };\n\n // capture:true ensures we see the keydown before any focused child element.\n document.addEventListener(\"mousemove\", onMouseMove, {passive: true});\n document.addEventListener(\"keydown\", onKeyDown, {capture: true});\n return () => {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"keydown\", onKeyDown, {capture: true});\n };\n }, [shortcuts, onShortcut, dataKey]);\n\n function onMouseEnter() {\n if (captureFocus) {\n divRef.current?.focus();\n }\n }\n\n return (\n <div\n ref={divRef}\n tabIndex={captureFocus ? -1 : undefined}\n onMouseEnter={onMouseEnter}\n data-pihanga={cardName}\n className={className}\n style={{...style, position: \"relative\", outline: \"none\"}}\n >\n <Card cardName={content} parentCard={cardName} />\n </div>\n );\n};\n"],"mappings":";;;;AAUA,SAAS,EACP,GACA,GACoB;CACpB,IAAI,IAAuB;CAC3B,OAAO,IAAM;EACX,IAAM,IAAO,EAAK,aAAa,QAAQ,GAAS;EAChD,IAAI,GAAM,OAAO;EACjB,IAAO,EAAK;CACd;AAEF;AAGA,SAAS,EAAe,GAAkB,GAA+B;CACvE,IAAM,IAAO,IAAI,IAAI,CAAQ;CAC7B,QACG,EAAK,IAAI,MAAM,IAAI,EAAE,UAAU,CAAC,EAAE,aAClC,EAAK,IAAI,OAAO,IAAI,EAAE,WAAW,CAAC,EAAE,cACpC,EAAK,IAAI,KAAK,IAAI,EAAE,SAAS,CAAC,EAAE,YAChC,EAAK,IAAI,MAAM,IAAI,EAAE,UAAU,CAAC,EAAE;AAEvC;AAEA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,YACA,cACA,eACA,aACA,cACA,UACA,kBAAe,IACf,aAAU,cACR,GAGE,IAAY,EAA+B;EAAC,GAAG;EAAG,GAAG;CAAC,CAAC,GACvD,IAAS,EAAuB,IAAI;CAE1C,QAAgB;EACd,IAAM,KAAe,MAAkB;GACrC,EAAU,UAAU;IAAC,GAAG,EAAE;IAAS,GAAG,EAAE;GAAO;EACjD,GAEM,KAAa,MAAqB;GACtC,KAAK,IAAM,KAAY,GAAW;IAChC,IAAM,IAAmB,EAAS,aAAa,CAAC;IAEhD,IAAI,EADa,EAAE,QAAQ,EAAS,OAAO,EAAE,SAAS,EAAS,QAC9C,CAAC,EAAe,GAAG,CAAI,GAAG;IAG3C,AAAK,EAAS,cACZ,EAAE,eAAe,GACjB,EAAE,gBAAgB;IAGpB,IAAM,EAAC,MAAG,SAAK,EAAU,SAEnB,IAAY,EADP,SAAS,iBAAiB,GAAG,CACJ,GAAI,CAAO;IAE/C,EAAW;KACT,YAAY,EAAS,MAAM,EAAS;KACpC,KAAK,EAAS;KACd,WAAW;KACX;KACA,SAAS;KACT,SAAS;IACX,CAAC;IACD;GACF;EACF;EAKA,OAFA,SAAS,iBAAiB,aAAa,GAAa,EAAC,SAAS,GAAI,CAAC,GACnE,SAAS,iBAAiB,WAAW,GAAW,EAAC,SAAS,GAAI,CAAC,SAClD;GAEX,AADA,SAAS,oBAAoB,aAAa,CAAW,GACrD,SAAS,oBAAoB,WAAW,GAAW,EAAC,SAAS,GAAI,CAAC;EACpE;CACF,GAAG;EAAC;EAAW;EAAY;CAAO,CAAC;CAEnC,SAAS,IAAe;EACtB,AAAI,KACF,EAAO,SAAS,MAAM;CAE1B;CAEA,OACE,kBAAC,OAAD;EACE,KAAK;EACL,UAAU,IAAe,KAAK,KAAA;EAChB;EACd,gBAAc;EACH;EACX,OAAO;GAAC,GAAG;GAAO,UAAU;GAAY,SAAS;EAAM;YAEvD,kBAAC,GAAD;GAAM,UAAU;GAAS,YAAY;EAAW,CAAA;CAC7C,CAAA;AAET"}
|
|
@@ -39,6 +39,23 @@ export type KeyboardOverlayProps = {
|
|
|
39
39
|
content: PiCardRef;
|
|
40
40
|
/** Shortcuts to intercept; all other key events pass through. */
|
|
41
41
|
shortcuts: ShortcutDef[];
|
|
42
|
+
/**
|
|
43
|
+
* When `true` (default) the wrapper `<div>` is made focusable (`tabIndex -1`)
|
|
44
|
+
* and automatically focused on mount, giving it the keyboard context so that
|
|
45
|
+
* shortcuts are reliably delivered even inside iframes or when an outer element
|
|
46
|
+
* would otherwise hold focus.
|
|
47
|
+
*
|
|
48
|
+
* Set to `false` only if you need focus to stay on a specific child element
|
|
49
|
+
* (e.g. a text input) right from the start; the document-level capture listener
|
|
50
|
+
* will still handle shortcuts, but without focus ownership.
|
|
51
|
+
*/
|
|
52
|
+
captureFocus?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* When set, the overlay walks up the DOM from the cursor position looking for
|
|
55
|
+
* `data-{dataKey}` instead of the default `data-pihanga` attribute.
|
|
56
|
+
* The found value is forwarded as `dataValue` in the shortcut event.
|
|
57
|
+
*/
|
|
58
|
+
dataKey?: string;
|
|
42
59
|
/**
|
|
43
60
|
* Extra CSS classes applied to the wrapper `<div>`.
|
|
44
61
|
*
|
|
@@ -56,16 +73,17 @@ export type KeyboardOverlayProps = {
|
|
|
56
73
|
/**
|
|
57
74
|
* Payload emitted by `onShortcut` whenever a registered shortcut fires.
|
|
58
75
|
*
|
|
59
|
-
* `
|
|
60
|
-
*
|
|
61
|
-
* until a match is found.
|
|
76
|
+
* `dataValue` is the value of the `data-{dataKey}` attribute (defaulting to
|
|
77
|
+
* `data-pihanga`) of the **lowest** DOM element under the cursor at the moment
|
|
78
|
+
* the key was pressed, walking up the tree until a match is found.
|
|
79
|
+
* `undefined` when no matching ancestor exists.
|
|
62
80
|
*/
|
|
63
81
|
export type KeyboardOverlayShortcutEvent = {
|
|
64
82
|
/** `ShortcutDef.id` when set, otherwise the matched `key`. */
|
|
65
83
|
shortcutId: string;
|
|
66
84
|
key: string;
|
|
67
85
|
modifiers: Modifier[];
|
|
68
|
-
|
|
86
|
+
dataValue?: string;
|
|
69
87
|
cursorX: number;
|
|
70
88
|
cursorY: number;
|
|
71
89
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboardOverlay.types.js","names":[],"sources":["../../../src/cards/keyboardOverlay/keyboardOverlay.types.ts"],"sourcesContent":["import {\n type PiCardRef,\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const KEYBOARD_OVERLAY_CARD = \"keyboard-overlay\";\n\nexport const KeyboardOverlay = createCardDeclaration<\n KeyboardOverlayProps,\n KeyboardOverlayEvents\n>(KEYBOARD_OVERLAY_CARD);\n\nexport const KEYBOARD_OVERLAY_ACTION = registerActions(KEYBOARD_OVERLAY_CARD, [\n \"shortcut\",\n]);\n\nexport const onKeyboardShortcut = createOnAction<KeyboardOverlayShortcutEvent>(\n KEYBOARD_OVERLAY_ACTION.SHORTCUT,\n);\n\n/** Modifier keys that can be part of a shortcut definition. */\nexport type Modifier = \"ctrl\" | \"shift\" | \"alt\" | \"meta\";\n\n/**\n * A single keyboard shortcut to intercept.\n *\n * `key` matches `KeyboardEvent.key` (e.g. `\"k\"`, `\"Escape\"`, `\"ArrowUp\"`) or\n * `KeyboardEvent.code` (e.g. `\"KeyK\"`).\n *\n * `modifiers` lists the modifier keys that **must** be active; any modifier\n * not listed is expected to be **inactive** (strict matching).\n *\n * `id` is an optional caller-defined label that is forwarded in the event so\n * you can distinguish shortcuts without pattern-matching on key+modifiers.\n *\n * `propagate` controls whether the key event continues down the DOM tree after\n * the overlay handles it. Defaults to `false` (the event is consumed here).\n * Set to `true` to fire the pihanga event **and** still let the browser /\n * focused child element see the keystroke.\n */\nexport type ShortcutDef = {\n key: string;\n modifiers?: Modifier[];\n id?: string;\n /** When `true` the key event is NOT stopped — it propagates normally after\n * the pihanga `onShortcut` event fires. Default: `false`. */\n propagate?: boolean;\n};\n\nexport type KeyboardOverlayProps = {\n /** Child card to render beneath the overlay. */\n content: PiCardRef;\n /** Shortcuts to intercept; all other key events pass through. */\n shortcuts: ShortcutDef[];\n /**\n * Extra CSS classes applied to the wrapper `<div>`.\n *\n * Useful for layout constraints, e.g. `\"h-full w-full flex\"`.\n * `position: relative` is always applied and cannot be overridden.\n */\n className?: string;\n /**\n * Inline styles merged onto the wrapper `<div>`.\n *\n * `position: relative` is always applied and takes precedence.\n */\n style?: React.CSSProperties;\n};\n\n/**\n * Payload emitted by `onShortcut` whenever a registered shortcut fires.\n *\n * `
|
|
1
|
+
{"version":3,"file":"keyboardOverlay.types.js","names":[],"sources":["../../../src/cards/keyboardOverlay/keyboardOverlay.types.ts"],"sourcesContent":["import {\n type PiCardRef,\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const KEYBOARD_OVERLAY_CARD = \"keyboard-overlay\";\n\nexport const KeyboardOverlay = createCardDeclaration<\n KeyboardOverlayProps,\n KeyboardOverlayEvents\n>(KEYBOARD_OVERLAY_CARD);\n\nexport const KEYBOARD_OVERLAY_ACTION = registerActions(KEYBOARD_OVERLAY_CARD, [\n \"shortcut\",\n]);\n\nexport const onKeyboardShortcut = createOnAction<KeyboardOverlayShortcutEvent>(\n KEYBOARD_OVERLAY_ACTION.SHORTCUT,\n);\n\n/** Modifier keys that can be part of a shortcut definition. */\nexport type Modifier = \"ctrl\" | \"shift\" | \"alt\" | \"meta\";\n\n/**\n * A single keyboard shortcut to intercept.\n *\n * `key` matches `KeyboardEvent.key` (e.g. `\"k\"`, `\"Escape\"`, `\"ArrowUp\"`) or\n * `KeyboardEvent.code` (e.g. `\"KeyK\"`).\n *\n * `modifiers` lists the modifier keys that **must** be active; any modifier\n * not listed is expected to be **inactive** (strict matching).\n *\n * `id` is an optional caller-defined label that is forwarded in the event so\n * you can distinguish shortcuts without pattern-matching on key+modifiers.\n *\n * `propagate` controls whether the key event continues down the DOM tree after\n * the overlay handles it. Defaults to `false` (the event is consumed here).\n * Set to `true` to fire the pihanga event **and** still let the browser /\n * focused child element see the keystroke.\n */\nexport type ShortcutDef = {\n key: string;\n modifiers?: Modifier[];\n id?: string;\n /** When `true` the key event is NOT stopped — it propagates normally after\n * the pihanga `onShortcut` event fires. Default: `false`. */\n propagate?: boolean;\n};\n\nexport type KeyboardOverlayProps = {\n /** Child card to render beneath the overlay. */\n content: PiCardRef;\n /** Shortcuts to intercept; all other key events pass through. */\n shortcuts: ShortcutDef[];\n /**\n * When `true` (default) the wrapper `<div>` is made focusable (`tabIndex -1`)\n * and automatically focused on mount, giving it the keyboard context so that\n * shortcuts are reliably delivered even inside iframes or when an outer element\n * would otherwise hold focus.\n *\n * Set to `false` only if you need focus to stay on a specific child element\n * (e.g. a text input) right from the start; the document-level capture listener\n * will still handle shortcuts, but without focus ownership.\n */\n captureFocus?: boolean;\n /**\n * When set, the overlay walks up the DOM from the cursor position looking for\n * `data-{dataKey}` instead of the default `data-pihanga` attribute.\n * The found value is forwarded as `dataValue` in the shortcut event.\n */\n dataKey?: string;\n /**\n * Extra CSS classes applied to the wrapper `<div>`.\n *\n * Useful for layout constraints, e.g. `\"h-full w-full flex\"`.\n * `position: relative` is always applied and cannot be overridden.\n */\n className?: string;\n /**\n * Inline styles merged onto the wrapper `<div>`.\n *\n * `position: relative` is always applied and takes precedence.\n */\n style?: React.CSSProperties;\n};\n\n/**\n * Payload emitted by `onShortcut` whenever a registered shortcut fires.\n *\n * `dataValue` is the value of the `data-{dataKey}` attribute (defaulting to\n * `data-pihanga`) of the **lowest** DOM element under the cursor at the moment\n * the key was pressed, walking up the tree until a match is found.\n * `undefined` when no matching ancestor exists.\n */\nexport type KeyboardOverlayShortcutEvent = {\n /** `ShortcutDef.id` when set, otherwise the matched `key`. */\n shortcutId: string;\n key: string;\n modifiers: Modifier[];\n dataValue?: string;\n cursorX: number;\n cursorY: number;\n};\n\nexport type KeyboardOverlayEvents = {\n onShortcut: KeyboardOverlayShortcutEvent;\n};\n"],"mappings":";;AAOA,IAAa,IAAwB,oBAExB,IAAkB,EAG7B,CAAqB,GAEV,IAA0B,EAAgB,GAAuB,CAC5E,UACF,CAAC,GAEY,IAAqB,EAChC,EAAwB,QAC1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.overlay-wrapper{flex-direction:column;width:100%;min-width:0;display:flex;position:relative}.overlay-wrapper.is-loading.fill-parent{min-height:12rem}.overlay-content{flex-direction:column;flex-grow:1;width:100%;min-width:0;display:flex;position:relative;overflow-y:hidden}.overlay-wrapper.fill-parent .overlay-content{min-height:inherit}.loading-overlay{opacity:0;pointer-events:none;z-index:2;
|
|
1
|
+
.overlay-wrapper{flex-direction:column;width:100%;min-width:0;display:flex;position:relative}.overlay-wrapper.is-loading.fill-parent{min-height:12rem}.overlay-content{flex-direction:column;flex-grow:1;width:100%;min-width:0;display:flex;position:relative;overflow-y:hidden}.overlay-wrapper.fill-parent .overlay-content{min-height:inherit}.loading-overlay{background:color-mix(in srgb, var(--background) 70%, transparent);opacity:0;pointer-events:none;z-index:2;justify-content:center;align-items:center;transition:opacity .15s;display:flex;position:absolute;inset:0}.overlay-wrapper.viewport-centered .loading-overlay{z-index:50;position:fixed;inset:0}.overlay-wrapper.is-loading .loading-overlay{opacity:1;pointer-events:auto}.loading-overlay__content{color:var(--muted-foreground);align-items:center;gap:.5rem;font-size:.875rem;display:flex}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mode-toggle.component.js","names":[],"sources":["../../../src/cards/modeToggle/mode-toggle.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {Moon, Sun} from \"lucide-react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {Button} from \"@/components/ui/button\";\nimport {useTheme} from \"@/components/theme-provider\";\nimport type {ModeToggleEvents, ModeToggleProps} from \"./mode-toggle.types\";\n\nexport const ModeToggleComponent = (\n props: PiCardProps<ModeToggleProps, ModeToggleEvents>,\n): React.ReactNode => {\n const {setTheme, theme} = useTheme();\n const {variant, className, onModeChanged, cardName} = props;\n\n function onClick() {\n const mode = theme === \"dark\" ? \"light\" : \"dark\";\n setTheme(mode);\n onModeChanged({mode});\n }\n\n return (\n <Button\n variant={variant ?? \"
|
|
1
|
+
{"version":3,"file":"mode-toggle.component.js","names":[],"sources":["../../../src/cards/modeToggle/mode-toggle.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {Moon, Sun} from \"lucide-react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {Button} from \"@/components/ui/button\";\nimport {useTheme} from \"@/components/theme-provider\";\nimport type {ModeToggleEvents, ModeToggleProps} from \"./mode-toggle.types\";\n\nexport const ModeToggleComponent = (\n props: PiCardProps<ModeToggleProps, ModeToggleEvents>,\n): React.ReactNode => {\n const {setTheme, theme} = useTheme();\n const {variant, className, onModeChanged, cardName} = props;\n\n function onClick() {\n const mode = theme === \"dark\" ? \"light\" : \"dark\";\n setTheme(mode);\n onModeChanged({mode});\n }\n\n return (\n <Button\n variant={variant ?? \"ghost\"}\n size=\"icon\"\n onClick={onClick}\n className={className}\n data-pihanga={cardName}\n >\n <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n <span className=\"sr-only\">Toggle theme</span>\n </Button>\n );\n};\n"],"mappings":";;;;;;AAOA,IAAa,KACX,MACoB;CACpB,IAAM,EAAC,aAAU,aAAS,EAAS,GAC7B,EAAC,YAAS,cAAW,kBAAe,gBAAY;CAEtD,SAAS,IAAU;EACjB,IAAM,IAAO,MAAU,SAAS,UAAU;EAE1C,AADA,EAAS,CAAI,GACb,EAAc,EAAC,QAAI,CAAC;CACtB;CAEA,OACE,kBAAC,GAAD;EACE,SAAS,KAAW;EACpB,MAAK;EACI;EACE;EACX,gBAAc;YALhB;GAOE,kBAAC,GAAD,EAAK,WAAU,uFAAwF,CAAA;GACvG,kBAAC,GAAD,EAAM,WAAU,+FAAgG,CAAA;GAChH,kBAAC,QAAD;IAAM,WAAU;cAAU;GAAkB,CAAA;EACtC;;AAEZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.window{justify-content:center;width:100%;height:100%;display:flex}.page{flex-direction:column;flex:1;width:100%;height:100%;display:flex}.header{border-bottom:1px solid var(--border);background-color:var(--background);flex-shrink:0;align-items:center;gap:1rem;padding
|
|
1
|
+
.window{justify-content:center;width:100%;height:100%;display:flex}.page{flex-direction:column;flex:1;width:100%;height:100%;display:flex}.header{border-bottom:1px solid var(--border);background-color:var(--background);flex-shrink:0;align-items:center;gap:1rem;padding:.75rem 1rem;display:flex}@media (width>=768px){.header{padding-left:1.5rem;padding-right:1.5rem}}.title-section{align-items:center;gap:.5rem;display:inline-flex}.title-section .title-icon{flex-shrink:0;width:1.5rem;height:1.5rem}.title-section .title-text{color:var(--foreground);font-size:1.125rem;font-weight:600;line-height:1.75rem}.nav-md{display:none}@media (width>=768px){.nav-md{flex-direction:row;align-items:center;gap:1.25rem;display:flex}}@media (width>=1024px){.nav-md{gap:1.5rem}}.nav-md .nav-link{color:var(--muted-foreground);padding-left:0;padding-right:0;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.nav-md .nav-link:hover{color:var(--foreground)}.nav-sm{gap:1.5rem;padding:1rem;font-size:1.125rem;font-weight:500;line-height:1.75rem;display:grid}.nav-sm .nav-link{color:var(--muted-foreground);justify-content:flex-start;padding-left:0;padding-right:0;font-size:1.125rem;line-height:1.75rem}.nav-sm .nav-link:hover{color:var(--foreground)}.header-actions{align-items:center;gap:.5rem;margin-left:auto;display:flex}@media (width>=1024px){.header-actions{gap:1rem}}.main{background-color:var(--background);flex:1;min-height:0;padding:.5rem 1rem;overflow-y:auto}@media (width>=768px){.main{padding:1rem 2rem}}.footer{background-color:var(--background);color:var(--muted-foreground);flex-shrink:0;padding:.5rem 1rem}@media (width>=768px){.footer{padding:1rem 2rem}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.pihanga-paste-target{border:2px
|
|
1
|
+
.pihanga-paste-target{border:dashed 2px var(--border)}.pihanga-paste-target .paste-target-msg-title{font-size:1.2em}.pihanga-paste-target textarea:focus{outline:none}.pihanga-paste-target-focused{border-color:var(--ring);border-width:3px}.pihanga-paste-target-focused .paste-target-focus-reminder{display:none}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './resizableColumns.types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ResizableColumnsComponent as e } from "./resizableColumns.component.js";
|
|
2
|
+
import { RESIZABLE_COLUMNS_CARD as t, ResizableColumns as n } from "./resizableColumns.types.js";
|
|
3
|
+
import { registerCardComponent as r } from "@pihanga2/core";
|
|
4
|
+
//#region src/cards/resizableColumns/index.ts
|
|
5
|
+
r({
|
|
6
|
+
name: t,
|
|
7
|
+
component: e
|
|
8
|
+
});
|
|
9
|
+
//#endregion
|
|
10
|
+
export { t as RESIZABLE_COLUMNS_CARD, n as ResizableColumns };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/resizableColumns/index.ts"],"sourcesContent":["import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {ResizableColumnsComponent} from \"./resizableColumns.component\";\nimport {RESIZABLE_COLUMNS_CARD} from \"./resizableColumns.types\";\n\nexport * from \"./resizableColumns.types\";\n\nregisterCardComponent({\n name: RESIZABLE_COLUMNS_CARD,\n component: ResizableColumnsComponent,\n});\n"],"mappings":";;;;AAOA,EAAsB;CACpB,MAAM;CACN,WAAW;AACb,CAAC"}
|