@pihanga2/shadcn 0.2.0 → 0.2.2
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 +208 -3
- package/AGENT.md +66 -4
- package/AGENT.using-cards.md +197 -250
- package/README.md +4 -1
- package/cards/badge/badge.component.js +1 -0
- package/cards/badge/badge.component.js.map +1 -1
- package/cards/box/box.component.js +2 -2
- package/cards/box/box.component.js.map +1 -1
- package/cards/box/box.types.d.ts +0 -8
- package/cards/box/box.types.js.map +1 -1
- package/cards/button/button.types.d.ts +4 -0
- package/cards/button/button.types.js +2 -2
- package/cards/button/button.types.js.map +1 -1
- package/cards/button/index.js +5 -5
- package/cards/chart/chart.component.d.ts +4 -0
- package/cards/chart/chart.types.d.ts +223 -0
- package/cards/chart/index.d.ts +1 -0
- package/cards/checkbox/checkbox.component.js +1 -0
- package/cards/checkbox/checkbox.component.js.map +1 -1
- package/cards/checkbox/checkbox.types.d.ts +4 -0
- package/cards/checkbox/checkbox.types.js +2 -2
- package/cards/checkbox/checkbox.types.js.map +1 -1
- package/cards/checkbox/index.js +5 -5
- package/cards/conditional/conditional.component.d.ts +14 -3
- package/cards/conditional/conditional.component.js +17 -9
- package/cards/conditional/conditional.component.js.map +1 -1
- package/cards/conditional/conditional.types.d.ts +115 -8
- package/cards/conditional/conditional.types.js.map +1 -1
- package/cards/core-index.d.ts +3 -0
- package/cards/core-index.js +38 -31
- package/cards/dataTable/dataTable.types.js.map +1 -1
- package/cards/dataTable/dataTableRowDetail.component.js +1 -0
- package/cards/dataTable/dataTableRowDetail.component.js.map +1 -1
- package/cards/field/field.types.js.map +1 -1
- package/cards/fileDrop/fileDrop.component.js +90 -0
- package/cards/fileDrop/fileDrop.component.js.map +1 -0
- package/cards/fileDrop/fileDrop.types.js +11 -0
- package/cards/fileDrop/fileDrop.types.js.map +1 -0
- package/cards/fileDrop/index.js +13 -0
- package/cards/fileDrop/index.js.map +1 -0
- package/cards/flexGrid/flexGrid.types.js.map +1 -1
- package/cards/form/form.types.d.ts +4 -0
- package/cards/form/form.types.js +2 -2
- package/cards/form/form.types.js.map +1 -1
- package/cards/form/index.js +5 -5
- package/cards/graphin/contextMenu.component.d.ts +28 -0
- package/cards/graphin/eventDispatcher.component.d.ts +8 -0
- package/cards/graphin/graphin.component.d.ts +2 -2
- package/cards/graphin/graphin.playground-cards.d.ts +1 -0
- package/cards/graphin/graphin.types.d.ts +477 -4
- package/cards/graphin/graphinOpHandler.component.d.ts +8 -0
- package/cards/graphin/index.d.ts +1 -0
- package/cards/graphin/tooltip.component.d.ts +13 -1
- package/cards/input/index.js +5 -5
- package/cards/input/input.component.js +1 -0
- package/cards/input/input.component.js.map +1 -1
- package/cards/input/input.types.d.ts +10 -2
- package/cards/input/input.types.js +2 -2
- package/cards/input/input.types.js.map +1 -1
- package/cards/list/list.types.js +1 -0
- package/cards/list/list.types.js.map +1 -1
- package/cards/pageWithNavbar/pageWithNavbar.component.d.ts +5 -7
- package/cards/pageWithNavbar/pageWithNavbar.component.js +1 -1
- package/cards/pageWithNavbar/pageWithNavbar.component.js.map +1 -1
- package/cards/pasteTarget/pasteTarget.component.js.map +1 -1
- package/cards/select/index.js +5 -5
- package/cards/select/select.component.js +1 -0
- package/cards/select/select.component.js.map +1 -1
- package/cards/select/select.types.d.ts +12 -0
- package/cards/select/select.types.js +2 -2
- package/cards/select/select.types.js.map +1 -1
- package/cards/slider/index.d.ts +1 -0
- package/cards/slider/index.js +13 -0
- package/cards/slider/index.js.map +1 -0
- package/cards/slider/slider.component.d.ts +4 -0
- package/cards/slider/slider.component.js +64 -0
- package/cards/slider/slider.component.js.map +1 -0
- package/cards/slider/slider.types.d.ts +110 -0
- package/cards/slider/slider.types.js +7 -0
- package/cards/slider/slider.types.js.map +1 -0
- package/cards/sliderValue/index.d.ts +1 -0
- package/cards/sliderValue/index.js +12 -0
- package/cards/sliderValue/index.js.map +1 -0
- package/cards/sliderValue/sliderValue.component.d.ts +15 -0
- package/cards/sliderValue/sliderValue.component.js +33 -0
- package/cards/sliderValue/sliderValue.component.js.map +1 -0
- package/cards/sliderValue/sliderValue.types.d.ts +21 -0
- package/cards/sliderValue/sliderValue.types.js +7 -0
- package/cards/sliderValue/sliderValue.types.js.map +1 -0
- package/cards/stack/stack.component.js +1 -1
- package/cards/stack/stack.component.js.map +1 -1
- package/cards/stack/stack.types.js.map +1 -1
- package/cards/switch/index.js +5 -5
- package/cards/switch/switch.component.js +1 -0
- package/cards/switch/switch.component.js.map +1 -1
- package/cards/switch/switch.types.d.ts +4 -0
- package/cards/switch/switch.types.js +2 -2
- package/cards/switch/switch.types.js.map +1 -1
- package/cards/tabs/tabs.component.js +51 -25
- package/cards/tabs/tabs.component.js.map +1 -1
- package/cards/tabs/tabs.types.d.ts +10 -0
- package/cards/tabs/tabs.types.js.map +1 -1
- package/cards/textField/index.js +5 -5
- package/cards/textField/textField.component.js +1 -0
- package/cards/textField/textField.component.js.map +1 -1
- package/cards/textField/textField.types.d.ts +4 -0
- package/cards/textField/textField.types.js +2 -2
- package/cards/textField/textField.types.js.map +1 -1
- package/cards/toast/index.js +7 -7
- package/cards/toast/toast.component.js.map +1 -1
- package/cards/toast/toast.types.d.ts +4 -0
- package/cards/toast/toast.types.js +2 -2
- package/cards/toast/toast.types.js.map +1 -1
- package/cards/toggleGroup/index.js +5 -5
- package/cards/toggleGroup/toggleGroup.component.js +1 -0
- package/cards/toggleGroup/toggleGroup.component.js.map +1 -1
- package/cards/toggleGroup/toggleGroup.types.d.ts +4 -0
- package/cards/toggleGroup/toggleGroup.types.js +2 -2
- package/cards/toggleGroup/toggleGroup.types.js.map +1 -1
- package/cards/typography/typography.types.js +1 -0
- package/cards/typography/typography.types.js.map +1 -1
- package/components/hooks/use-breakpoint.d.ts +49 -0
- package/components/hooks/use-breakpoint.js +73 -0
- package/components/hooks/use-breakpoint.js.map +1 -0
- package/components/ui/slider.js +36 -0
- package/components/ui/slider.js.map +1 -0
- package/components/ui/toggle-group.js +1 -0
- package/components/ui/toggle-group.js.map +1 -1
- package/package.json +19 -2
- package/pihanga-shadcn.css +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.types.js","names":[],"sources":["../../../src/cards/list/list.types.ts"],"sourcesContent":["import * as React from \"react\";\nimport {\n PiCardRef,\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const LIST_CARD = \"shad/list\";\n\nexport const List = createCardDeclaration<ListProps, ListEvents>(LIST_CARD);\n\nexport const LIST_ACTION = registerActions(LIST_CARD, [\"item_clicked\"]);\n\nexport const onListItemClicked = createOnAction<ItemClickedEvent>(\n LIST_ACTION.ITEM_CLICKED,\n);\n\n// Decorator shown before or after an item's title/subtitle block.\nexport type IconDecorator = {\n type: \"icon\";\n /** Name registered via registerIcon() in src/cards/icons.ts */\n name: string;\n className?: string;\n};\n\nexport type AvatarDecorator = {\n type: \"avatar\";\n src: string;\n fallback?: string;\n className?: string;\n};\n\nexport type ChipDecorator = {\n type: \"chip\";\n text: string;\n className?: string;\n};\n\nexport type CardDecorator = {\n type: \"card\";\n cardName: PiCardRef;\n};\n\nexport type DecoratorT =\n | IconDecorator\n | AvatarDecorator\n | ChipDecorator\n | CardDecorator;\n\nexport type ListItem = {\n /** React key — must be unique within the list. */\n id: string | number;\n isSelected?: boolean;\n title: string;\n subTitle?: string;\n /** When present the item becomes a collapsible parent. */\n nested?: ListItem[];\n startDecorator?: DecoratorT;\n endDecorator?: DecoratorT;\n /** Extra CSS classes on this item's button. */\n className?: string;\n};\n\nexport type ListProps = {\n items: ListItem[];\n /** Visual density — defaults to \"md\". */\n size?: \"sm\" | \"md\" | \"lg\";\n /**\n * CSS `list-style-type` value (e.g. \"disc\", \"decimal\", \"none\").\n * Defaults to \"none\" (plain list without markers).\n */\n marker?: string;\n /** CSS classes applied directly to the root `<ul>`. */\n className?: string;\n /** Inline styles applied directly to the root `<ul>`. */\n style?: React.CSSProperties;\n};\n\nexport type ItemClickedEvent = {\n itemID: string | number;\n};\n\nexport type ListEvents = {\n onItemClicked: ItemClickedEvent;\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"list.types.js","names":[],"sources":["../../../src/cards/list/list.types.ts"],"sourcesContent":["import * as React from \"react\";\nimport {\n type PiCardRef,\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const LIST_CARD = \"shad/list\";\n\nexport const List = createCardDeclaration<ListProps, ListEvents>(LIST_CARD);\n\nexport const LIST_ACTION = registerActions(LIST_CARD, [\"item_clicked\"]);\n\nexport const onListItemClicked = createOnAction<ItemClickedEvent>(\n LIST_ACTION.ITEM_CLICKED,\n);\n\n// Decorator shown before or after an item's title/subtitle block.\nexport type IconDecorator = {\n type: \"icon\";\n /** Name registered via registerIcon() in src/cards/icons.ts */\n name: string;\n className?: string;\n};\n\nexport type AvatarDecorator = {\n type: \"avatar\";\n src: string;\n fallback?: string;\n className?: string;\n};\n\nexport type ChipDecorator = {\n type: \"chip\";\n text: string;\n className?: string;\n};\n\nexport type CardDecorator = {\n type: \"card\";\n cardName: PiCardRef;\n};\n\nexport type DecoratorT =\n | IconDecorator\n | AvatarDecorator\n | ChipDecorator\n | CardDecorator;\n\nexport type ListItem = {\n /** React key — must be unique within the list. */\n id: string | number;\n isSelected?: boolean;\n title: string;\n subTitle?: string;\n /** When present the item becomes a collapsible parent. */\n nested?: ListItem[];\n startDecorator?: DecoratorT;\n endDecorator?: DecoratorT;\n /** Extra CSS classes on this item's button. */\n className?: string;\n};\n\nexport type ListProps = {\n items: ListItem[];\n /** Visual density — defaults to \"md\". */\n size?: \"sm\" | \"md\" | \"lg\";\n /**\n * CSS `list-style-type` value (e.g. \"disc\", \"decimal\", \"none\").\n * Defaults to \"none\" (plain list without markers).\n */\n marker?: string;\n /** CSS classes applied directly to the root `<ul>`. */\n className?: string;\n /** Inline styles applied directly to the root `<ul>`. */\n style?: React.CSSProperties;\n};\n\nexport type ItemClickedEvent = {\n itemID: string | number;\n};\n\nexport type ListEvents = {\n onItemClicked: ItemClickedEvent;\n};\n"],"mappings":";;;AAQA,IAAa,IAAY,aAEZ,IAAO,EAA6C,CAAS,GAE7D,IAAc,EAAgB,GAAW,CAAC,cAAc,CAAC,GAEzD,IAAoB,EAC/B,EAAY,YACd"}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { PiCardProps } from '@pihanga2/core';
|
|
2
2
|
import { PageWithNavbarEvents, PageWithNavbarProps } from './pageWithNavbar.type';
|
|
3
3
|
export type ShadStyle = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
footer?: string;
|
|
10
|
-
};
|
|
4
|
+
window?: string;
|
|
5
|
+
page?: string;
|
|
6
|
+
header?: string;
|
|
7
|
+
main?: string;
|
|
8
|
+
footer?: string;
|
|
11
9
|
};
|
|
12
10
|
export declare const Component: (props: PiCardProps<PageWithNavbarProps<ShadStyle>, PageWithNavbarEvents>) => React.ReactNode;
|
|
@@ -38,7 +38,7 @@ function y(e, t) {
|
|
|
38
38
|
return e.default ?? void 0;
|
|
39
39
|
}
|
|
40
40
|
var b = (a) => {
|
|
41
|
-
let { title: o, iconName: s, main: d, footer: g, navLinks: v = [], scrollResetKey: b, headerLeftCard: S, headerRightCard: C, style: w, onNavigateTo: T, cardName: E } = a, D = _(), O = w
|
|
41
|
+
let { title: o, iconName: s, main: d, footer: g, navLinks: v = [], scrollResetKey: b, headerLeftCard: S, headerRightCard: C, style: w, onNavigateTo: T, cardName: E } = a, D = _(), O = w ?? {}, k = u(null);
|
|
42
42
|
l(() => {
|
|
43
43
|
k.current?.scrollTo({
|
|
44
44
|
top: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageWithNavbar.component.js","names":[],"sources":["../../../src/cards/pageWithNavbar/pageWithNavbar.component.tsx"],"sourcesContent":["import {Menu} from \"lucide-react\";\nimport clsx from \"clsx\";\nimport {useEffect, useRef, useState} from \"react\";\n\nimport {Button} from \"@/components/ui/button\";\nimport {Sheet, SheetContent, SheetTrigger} from \"@/components/ui/sheet\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {getIconElement} from \"@/cards/icons\";\nimport {ScreenSize, SCREEN_SIZE_ORDER, SCREEN_SIZE_WIDTHS} from \"@/cards/types\";\nimport type {\n PageWithNavbarEvents,\n PageWithNavbarProps,\n ResponsiveCardRef,\n} from \"./pageWithNavbar.type\";\nimport type {SizeMap} from \"@/cards/types\";\nimport type {PiCardRef} from \"@pihanga2/core\";\n\nimport \"./pageWithNavbar.css\";\n\n// ---------------------------------------------------------------------------\n// Style override type — consumers can extend this to add Tailwind class names\n// to any structural section of the layout.\n// ---------------------------------------------------------------------------\n\nexport type ShadStyle = {\n shad?: {\n window?: string;\n page?: string;\n header?: string;\n main?: string;\n footer?: string;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Screen-size hook\n// ---------------------------------------------------------------------------\n\nfunction getCurrentScreenSize(): ScreenSize {\n const w = typeof window !== \"undefined\" ? window.innerWidth : 0;\n // Walk breakpoints from largest to smallest\n for (let i = SCREEN_SIZE_ORDER.length - 1; i >= 0; i--) {\n const size = SCREEN_SIZE_ORDER[i];\n if (w >= SCREEN_SIZE_WIDTHS[size]) return size;\n }\n return ScreenSize.XS;\n}\n\nfunction useScreenSize(): ScreenSize {\n const [size, setSize] = useState<ScreenSize>(getCurrentScreenSize);\n\n useEffect(() => {\n const handleResize = () => setSize(getCurrentScreenSize());\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n return size;\n}\n\n// ---------------------------------------------------------------------------\n// Responsive card-ref resolution\n// ---------------------------------------------------------------------------\n\nfunction isSizeMap(ref: ResponsiveCardRef): ref is SizeMap<PiCardRef> {\n return typeof ref === \"object\" && ref !== null;\n}\n\n/**\n * Resolve a {@link ResponsiveCardRef} to a concrete `PiCardRef` (or\n * `undefined`) for the given screen size.\n *\n * Uses a **mobile-first cascade**: the entry with the largest breakpoint\n * that is still ≤ `currentSize` wins; falls back to `default` otherwise.\n */\nfunction resolveCardRef(\n ref: ResponsiveCardRef | undefined,\n currentSize: ScreenSize,\n): PiCardRef | undefined {\n if (!ref) return undefined;\n if (!isSizeMap(ref)) return ref; // plain PiCardRef (string or PiCardDef)\n\n // Cascade: find the highest breakpoint that is ≤ current size\n const currentIdx = SCREEN_SIZE_ORDER.indexOf(currentSize);\n for (let i = currentIdx; i >= 0; i--) {\n const size = SCREEN_SIZE_ORDER[i];\n if (Object.prototype.hasOwnProperty.call(ref, size)) {\n const val = ref[size];\n // null means \"explicitly hidden at this breakpoint\"\n return val ?? undefined;\n }\n }\n // Fall back to the default entry\n return ref.default ?? undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Component = (\n props: PiCardProps<PageWithNavbarProps<ShadStyle>, PageWithNavbarEvents>,\n): React.ReactNode => {\n const {\n title,\n iconName,\n main,\n footer,\n navLinks = [],\n scrollResetKey,\n headerLeftCard,\n headerRightCard,\n style,\n onNavigateTo,\n cardName,\n } = props;\n\n const screenSize = useScreenSize();\n const cl = style?.shad ?? {};\n const mainRef = useRef<HTMLElement | null>(null);\n\n // Scroll main back to top when the logical \"page\" changes\n useEffect(() => {\n mainRef.current?.scrollTo({top: 0, behavior: \"auto\"});\n }, [scrollResetKey]);\n\n const resolvedLeftCard = resolveCardRef(headerLeftCard, screenSize);\n const resolvedRightCard = resolveCardRef(headerRightCard, screenSize);\n const hasNavLinks = navLinks.length > 0;\n\n // ------------------------------------------------------------------\n // Sub-components\n // ------------------------------------------------------------------\n\n /** Title section: optional icon + text */\n function TitleSection({className}: {className?: string}) {\n const IconEl = iconName ? getIconElement(iconName) : undefined;\n return (\n <div className={clsx(\"title-section\", className)}>\n {IconEl && <IconEl className=\"title-icon\" aria-hidden />}\n <span className=\"title-text\">{title}</span>\n </div>\n );\n }\n\n /** Nav bar rendered on medium screens and above */\n function NavMd() {\n return (\n <nav className=\"nav-md\" aria-label=\"Main navigation\">\n <TitleSection />\n {navLinks.map((el) => (\n <Button\n key={el.id}\n onClick={() => onNavigateTo({id: el.id})}\n variant=\"link\"\n className=\"nav-link\"\n >\n {el.title ?? capitalize(el.id)}\n </Button>\n ))}\n </nav>\n );\n }\n\n /** Hamburger trigger that opens a slide-out sheet on small screens */\n function NavSm() {\n return (\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"shrink-0 md:hidden\"\n aria-label=\"Toggle navigation menu\"\n >\n <Menu className=\"h-5 w-5\" aria-hidden />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\">\n <nav className=\"nav-sm\" aria-label=\"Mobile navigation\">\n <TitleSection />\n {navLinks.map((el) => (\n <Button\n key={el.id}\n onClick={() => onNavigateTo({id: el.id})}\n variant=\"link\"\n className=\"nav-link\"\n >\n {el.title ?? capitalize(el.id)}\n </Button>\n ))}\n </nav>\n </SheetContent>\n </Sheet>\n );\n }\n\n // ------------------------------------------------------------------\n // Layout\n // ------------------------------------------------------------------\n\n return (\n <div className={clsx(\"window\", cl.window)} data-pihanga={cardName}>\n <div className={clsx(\"page\", cl.page)}>\n {/* ── Header ─────────────────────────────────────────────── */}\n <header className={clsx(\"header\", cl.header)}>\n {/* Medium+ nav (title + links) */}\n <NavMd />\n\n {/* Small-screen hamburger (only when there are nav links) */}\n {hasNavLinks && <NavSm />}\n\n {/* On small screens with no nav links, show the title inline.\n Wrap in a plain div so md:hidden isn't overridden by the\n .title-section CSS class that sets display:inline-flex. */}\n {!hasNavLinks && (\n <div className=\"md:hidden\">\n <TitleSection />\n </div>\n )}\n\n {/* Push action cards to the right */}\n <div className=\"header-actions\">\n {resolvedLeftCard && (\n <Card cardName={resolvedLeftCard} parentCard={cardName} />\n )}\n {resolvedRightCard && (\n <Card cardName={resolvedRightCard} parentCard={cardName} />\n )}\n </div>\n </header>\n\n {/* ── Main ───────────────────────────────────────────────── */}\n <main className={clsx(\"main\", cl.main)} ref={mainRef}>\n <Card cardName={main} parentCard={cardName} />\n </main>\n\n {/* ── Footer (optional) ──────────────────────────────────── */}\n {footer && (\n <footer className={clsx(\"footer\", cl.footer)}>\n <Card cardName={footer} parentCard={cardName} />\n </footer>\n )}\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction capitalize(str: string): string {\n if (!str) return \"\";\n return str.charAt(0).toLocaleUpperCase() + str.slice(1);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pageWithNavbar.component.js","names":[],"sources":["../../../src/cards/pageWithNavbar/pageWithNavbar.component.tsx"],"sourcesContent":["import {Menu} from \"lucide-react\";\nimport clsx from \"clsx\";\nimport {useEffect, useRef, useState} from \"react\";\n\nimport {Button} from \"@/components/ui/button\";\nimport {Sheet, SheetContent, SheetTrigger} from \"@/components/ui/sheet\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {getIconElement} from \"@/cards/icons\";\nimport {ScreenSize, SCREEN_SIZE_ORDER, SCREEN_SIZE_WIDTHS} from \"@/cards/types\";\nimport type {\n PageWithNavbarEvents,\n PageWithNavbarProps,\n ResponsiveCardRef,\n} from \"./pageWithNavbar.type\";\nimport type {SizeMap} from \"@/cards/types\";\nimport type {PiCardRef} from \"@pihanga2/core\";\n\nimport \"./pageWithNavbar.css\";\n\n// ---------------------------------------------------------------------------\n// Style override type — consumers can extend this to add Tailwind class names\n// to any structural section of the layout.\n// ---------------------------------------------------------------------------\n\nexport type ShadStyle = {\n window?: string;\n page?: string;\n header?: string;\n main?: string;\n footer?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Screen-size hook\n// ---------------------------------------------------------------------------\n\nfunction getCurrentScreenSize(): ScreenSize {\n const w = typeof window !== \"undefined\" ? window.innerWidth : 0;\n // Walk breakpoints from largest to smallest\n for (let i = SCREEN_SIZE_ORDER.length - 1; i >= 0; i--) {\n const size = SCREEN_SIZE_ORDER[i];\n if (w >= SCREEN_SIZE_WIDTHS[size]) return size;\n }\n return ScreenSize.XS;\n}\n\nfunction useScreenSize(): ScreenSize {\n const [size, setSize] = useState<ScreenSize>(getCurrentScreenSize);\n\n useEffect(() => {\n const handleResize = () => setSize(getCurrentScreenSize());\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n return size;\n}\n\n// ---------------------------------------------------------------------------\n// Responsive card-ref resolution\n// ---------------------------------------------------------------------------\n\nfunction isSizeMap(ref: ResponsiveCardRef): ref is SizeMap<PiCardRef> {\n return typeof ref === \"object\" && ref !== null;\n}\n\n/**\n * Resolve a {@link ResponsiveCardRef} to a concrete `PiCardRef` (or\n * `undefined`) for the given screen size.\n *\n * Uses a **mobile-first cascade**: the entry with the largest breakpoint\n * that is still ≤ `currentSize` wins; falls back to `default` otherwise.\n */\nfunction resolveCardRef(\n ref: ResponsiveCardRef | undefined,\n currentSize: ScreenSize,\n): PiCardRef | undefined {\n if (!ref) return undefined;\n if (!isSizeMap(ref)) return ref; // plain PiCardRef (string or PiCardDef)\n\n // Cascade: find the highest breakpoint that is ≤ current size\n const currentIdx = SCREEN_SIZE_ORDER.indexOf(currentSize);\n for (let i = currentIdx; i >= 0; i--) {\n const size = SCREEN_SIZE_ORDER[i];\n if (Object.prototype.hasOwnProperty.call(ref, size)) {\n const val = ref[size];\n // null means \"explicitly hidden at this breakpoint\"\n return val ?? undefined;\n }\n }\n // Fall back to the default entry\n return ref.default ?? undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Component = (\n props: PiCardProps<PageWithNavbarProps<ShadStyle>, PageWithNavbarEvents>,\n): React.ReactNode => {\n const {\n title,\n iconName,\n main,\n footer,\n navLinks = [],\n scrollResetKey,\n headerLeftCard,\n headerRightCard,\n style,\n onNavigateTo,\n cardName,\n } = props;\n\n const screenSize = useScreenSize();\n const cl = style ?? {};\n const mainRef = useRef<HTMLElement | null>(null);\n\n // Scroll main back to top when the logical \"page\" changes\n useEffect(() => {\n mainRef.current?.scrollTo({top: 0, behavior: \"auto\"});\n }, [scrollResetKey]);\n\n const resolvedLeftCard = resolveCardRef(headerLeftCard, screenSize);\n const resolvedRightCard = resolveCardRef(headerRightCard, screenSize);\n const hasNavLinks = navLinks.length > 0;\n\n // ------------------------------------------------------------------\n // Sub-components\n // ------------------------------------------------------------------\n\n /** Title section: optional icon + text */\n function TitleSection({className}: {className?: string}) {\n const IconEl = iconName ? getIconElement(iconName) : undefined;\n return (\n <div className={clsx(\"title-section\", className)}>\n {IconEl && <IconEl className=\"title-icon\" aria-hidden />}\n <span className=\"title-text\">{title}</span>\n </div>\n );\n }\n\n /** Nav bar rendered on medium screens and above */\n function NavMd() {\n return (\n <nav className=\"nav-md\" aria-label=\"Main navigation\">\n <TitleSection />\n {navLinks.map((el) => (\n <Button\n key={el.id}\n onClick={() => onNavigateTo({id: el.id})}\n variant=\"link\"\n className=\"nav-link\"\n >\n {el.title ?? capitalize(el.id)}\n </Button>\n ))}\n </nav>\n );\n }\n\n /** Hamburger trigger that opens a slide-out sheet on small screens */\n function NavSm() {\n return (\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"shrink-0 md:hidden\"\n aria-label=\"Toggle navigation menu\"\n >\n <Menu className=\"h-5 w-5\" aria-hidden />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\">\n <nav className=\"nav-sm\" aria-label=\"Mobile navigation\">\n <TitleSection />\n {navLinks.map((el) => (\n <Button\n key={el.id}\n onClick={() => onNavigateTo({id: el.id})}\n variant=\"link\"\n className=\"nav-link\"\n >\n {el.title ?? capitalize(el.id)}\n </Button>\n ))}\n </nav>\n </SheetContent>\n </Sheet>\n );\n }\n\n // ------------------------------------------------------------------\n // Layout\n // ------------------------------------------------------------------\n\n return (\n <div className={clsx(\"window\", cl.window)} data-pihanga={cardName}>\n <div className={clsx(\"page\", cl.page)}>\n {/* ── Header ─────────────────────────────────────────────── */}\n <header className={clsx(\"header\", cl.header)}>\n {/* Medium+ nav (title + links) */}\n <NavMd />\n\n {/* Small-screen hamburger (only when there are nav links) */}\n {hasNavLinks && <NavSm />}\n\n {/* On small screens with no nav links, show the title inline.\n Wrap in a plain div so md:hidden isn't overridden by the\n .title-section CSS class that sets display:inline-flex. */}\n {!hasNavLinks && (\n <div className=\"md:hidden\">\n <TitleSection />\n </div>\n )}\n\n {/* Push action cards to the right */}\n <div className=\"header-actions\">\n {resolvedLeftCard && (\n <Card cardName={resolvedLeftCard} parentCard={cardName} />\n )}\n {resolvedRightCard && (\n <Card cardName={resolvedRightCard} parentCard={cardName} />\n )}\n </div>\n </header>\n\n {/* ── Main ───────────────────────────────────────────────── */}\n <main className={clsx(\"main\", cl.main)} ref={mainRef}>\n <Card cardName={main} parentCard={cardName} />\n </main>\n\n {/* ── Footer (optional) ──────────────────────────────────── */}\n {footer && (\n <footer className={clsx(\"footer\", cl.footer)}>\n <Card cardName={footer} parentCard={cardName} />\n </footer>\n )}\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction capitalize(str: string): string {\n if (!str) return \"\";\n return str.charAt(0).toLocaleUpperCase() + str.slice(1);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pasteTarget.component.js","names":[],"sources":["../../../src/cards/pasteTarget/pasteTarget.component.tsx"],"sourcesContent":["import {\n DEF_ERROR_COLOR,\n DEF_EVENT_DURATION_SECONDS,\n DEF_HEIGHT,\n DEF_OVERLAY_OPACITY,\n DEF_PASTE_FIRST_REMINDER,\n DEF_SUCCESS_COLOR,\n PasteItem,\n PasteTargetEvents,\n PasteTargetProps,\n} from \"@/cards/pasteTarget/pasteTarget.types\";\nimport {PiCardProps} from \"@pihanga2/core\";\nimport React, {useRef, useState} from \"react\";\n\nimport \"./pasteTarget.css\";\nimport clsx from \"clsx\";\nimport {Button} from \"@/registry/ui/button\";\n\n// /* 3. Stretch the child to cover the parent entirely */\n// top: 0,\n// left: 0,\n// right: 0,\n// bottom: 0,\n\n// /* 4. Ensure the child is visually stacked above the parent's content */\n// z-index: 10,\n\n// /* Optional: Make the overlay visible (e.g., semi-transparent black) */\n// overlayOpacity: \"rgba(0, 0, 0, 0.5)\";\n// }\n\nexport const PasteTargetComponent = (\n props: PiCardProps<PasteTargetProps, PasteTargetEvents>,\n): React.ReactNode => {\n const {\n title, // = \"Paste content right here\",\n description,\n pasteFirstReminder = DEF_PASTE_FIRST_REMINDER,\n withUpload,\n fileTypes,\n\n height = DEF_HEIGHT,\n\n onPastedContent,\n\n successColor = DEF_SUCCESS_COLOR,\n errorColor = DEF_ERROR_COLOR,\n eventDurationSeconds = DEF_EVENT_DURATION_SECONDS,\n eventOpacity = DEF_OVERLAY_OPACITY,\n cardName,\n className,\n _cls,\n } = props;\n const [overlayOpacity, setOverlayOpacity] = useState<number>(0);\n const [overlayBackground, setOverlayBackground] = useState<string>(\"gray\");\n const timerRef = React.useRef<number | null>(null);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isFocused, setIsFocused] = useState<boolean>(false);\n\n const overlayStyle: React.CSSProperties = {\n backgroundColor: overlayBackground,\n opacity: overlayOpacity,\n };\n\n const handlePaste = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n }\n\n event.preventDefault();\n const clipboardData =\n event.clipboardData ||\n (window as Window & {clipboardData?: DataTransfer}).clipboardData;\n\n if (clipboardData) {\n const pItems = clipboardData.items;\n const items: PasteItem[] = [];\n for (let i = 0; i < pItems.length; i++) {\n const pi = pItems[i];\n const mimeType = pi.type;\n const content = clipboardData.getData(pi.type);\n if (!(content == \"\" || content == \"\\n\")) {\n items.push({mimeType, content});\n }\n }\n onPastedContent({items});\n setOverlayBackground(successColor);\n } else {\n setOverlayBackground(errorColor);\n }\n setOverlayOpacity(eventOpacity);\n if (textareaRef.current) textareaRef.current.blur();\n\n // Set a timeout to switch the color back after 'n' seconds\n const newTimerId = setTimeout(() => {\n setOverlayOpacity(0);\n timerRef.current = null; // Clear the ref after the timeout executes\n }, eventDurationSeconds * 1000); // Convert seconds to milliseconds\n\n // Save the new timer ID to the ref (unknown necessary type casting)\n timerRef.current = newTimerId as unknown as number;\n };\n\n // Cleanup function to clear the timer when the component unmounts\n React.useEffect(() => {\n return () => {\n const ref = timerRef.current;\n if (ref) {\n clearTimeout(ref);\n }\n };\n }, []); // runs only on mount/unmount\n\n function renderPasteHandler() {\n return (\n <textarea\n onPaste={handlePaste}\n onFocus={() => {\n setIsFocused(true);\n }}\n onBlur={() => setIsFocused(false)}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n resize: \"none\",\n background: \"transparent\",\n cursor: isFocused ? \"copy\" : \"pointer\",\n ...overlayStyle,\n }}\n // 🔑 CRUCIAL: Focus the element before pasting so it receives the paste event.\n // autoFocus\n readOnly\n ref={textareaRef}\n />\n );\n }\n\n /** Map friendly file-type labels to accept= strings for the hidden input. */\n function buildAccept(): string | undefined {\n if (!fileTypes?.length) return undefined;\n return fileTypes\n .map((t) => {\n const ext = t.toLowerCase();\n if (ext === \"jpg\" || ext === \"jpeg\") return \".jpg,.jpeg\";\n return `.${ext}`;\n })\n .join(\",\");\n }\n\n function flashOverlay(color: string) {\n if (timerRef.current) clearTimeout(timerRef.current);\n setOverlayBackground(color);\n setOverlayOpacity(eventOpacity);\n const id = setTimeout(() => {\n setOverlayOpacity(0);\n timerRef.current = null;\n }, eventDurationSeconds * 1000);\n timerRef.current = id as unknown as number;\n }\n\n function handleFileChange(ev: React.ChangeEvent<HTMLInputElement>) {\n const file = ev.target.files?.[0];\n // Reset input so the same file can be re-selected later.\n ev.target.value = \"\";\n if (!file) return;\n\n const reader = new FileReader();\n reader.onload = (e) => {\n const content = (e.target?.result as string) ?? \"\";\n onPastedContent({items: [{mimeType: file.type, content}]});\n flashOverlay(successColor);\n };\n reader.onerror = () => {\n flashOverlay(errorColor);\n };\n reader.readAsDataURL(file);\n }\n\n function onUpload(ev: React.MouseEvent<HTMLButtonElement>) {\n ev.stopPropagation();\n ev.preventDefault();\n fileInputRef.current?.click();\n }\n\n function renderTitle() {\n const uploadBtn = !isFocused && withUpload && (\n <Button\n onClick={onUpload}\n style={{position: \"relative\", zIndex: 30, pointerEvents: \"auto\"}}\n >\n Upload\n </Button>\n );\n\n if (title) {\n return (\n <div className=\"paste-target-msg-title\">\n <h2>{title}</h2>\n {uploadBtn}\n </div>\n );\n } else {\n return (\n <div className=\"paste-target-msg-title\">\n Paste\n {uploadBtn && (\n <>\n or \n {uploadBtn}\n </>\n )}\n here\n </div>\n );\n }\n }\n\n const cn = _cls(\"root\", className || \"pihanga-paste-target\");\n\n return (\n <div\n className={clsx(cn, isFocused && \"pihanga-paste-target-focused\")}\n data-pihanga={cardName}\n style={{position: \"relative\", height, width: \"100%\"}}\n >\n <div\n className=\"paste-target-label\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n textAlign: \"center\",\n // zIndex must be above textarea (10) so the Upload button is clickable.\n // pointer-events: none lets click/focus pass through to the textarea,\n // EXCEPT on children that override with pointer-events: auto (the button).\n pointerEvents: \"none\",\n zIndex: 20,\n }}\n >\n {renderTitle()}\n {description && (\n <span className=\"paste-target-msg-desc\">{description}</span>\n )}\n <div className=\"paste-target-focus-reminder\">{pasteFirstReminder}</div>\n </div>\n {renderPasteHandler()}\n {/* Hidden file input — triggered programmatically by the Upload button */}\n {withUpload && (\n <input\n type=\"file\"\n ref={fileInputRef}\n accept={buildAccept()}\n onChange={handleFileChange}\n style={{display: \"none\"}}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pasteTarget.component.js","names":[],"sources":["../../../src/cards/pasteTarget/pasteTarget.component.tsx"],"sourcesContent":["import {\n DEF_ERROR_COLOR,\n DEF_EVENT_DURATION_SECONDS,\n DEF_HEIGHT,\n DEF_OVERLAY_OPACITY,\n DEF_PASTE_FIRST_REMINDER,\n DEF_SUCCESS_COLOR,\n type PasteItem,\n type PasteTargetEvents,\n type PasteTargetProps,\n} from \"@/cards/pasteTarget/pasteTarget.types\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport React, {useRef, useState} from \"react\";\n\nimport \"./pasteTarget.css\";\nimport clsx from \"clsx\";\nimport {Button} from \"@/registry/ui/button\";\n\n// /* 3. Stretch the child to cover the parent entirely */\n// top: 0,\n// left: 0,\n// right: 0,\n// bottom: 0,\n\n// /* 4. Ensure the child is visually stacked above the parent's content */\n// z-index: 10,\n\n// /* Optional: Make the overlay visible (e.g., semi-transparent black) */\n// overlayOpacity: \"rgba(0, 0, 0, 0.5)\";\n// }\n\nexport const PasteTargetComponent = (\n props: PiCardProps<PasteTargetProps, PasteTargetEvents>,\n): React.ReactNode => {\n const {\n title, // = \"Paste content right here\",\n description,\n pasteFirstReminder = DEF_PASTE_FIRST_REMINDER,\n withUpload,\n fileTypes,\n\n height = DEF_HEIGHT,\n\n onPastedContent,\n\n successColor = DEF_SUCCESS_COLOR,\n errorColor = DEF_ERROR_COLOR,\n eventDurationSeconds = DEF_EVENT_DURATION_SECONDS,\n eventOpacity = DEF_OVERLAY_OPACITY,\n cardName,\n className,\n _cls,\n } = props;\n const [overlayOpacity, setOverlayOpacity] = useState<number>(0);\n const [overlayBackground, setOverlayBackground] = useState<string>(\"gray\");\n const timerRef = React.useRef<number | null>(null);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [isFocused, setIsFocused] = useState<boolean>(false);\n\n const overlayStyle: React.CSSProperties = {\n backgroundColor: overlayBackground,\n opacity: overlayOpacity,\n };\n\n const handlePaste = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n }\n\n event.preventDefault();\n const clipboardData =\n event.clipboardData ||\n (window as Window & {clipboardData?: DataTransfer}).clipboardData;\n\n if (clipboardData) {\n const pItems = clipboardData.items;\n const items: PasteItem[] = [];\n for (let i = 0; i < pItems.length; i++) {\n const pi = pItems[i];\n const mimeType = pi.type;\n const content = clipboardData.getData(pi.type);\n if (!(content == \"\" || content == \"\\n\")) {\n items.push({mimeType, content});\n }\n }\n onPastedContent({items});\n setOverlayBackground(successColor);\n } else {\n setOverlayBackground(errorColor);\n }\n setOverlayOpacity(eventOpacity);\n if (textareaRef.current) textareaRef.current.blur();\n\n // Set a timeout to switch the color back after 'n' seconds\n const newTimerId = setTimeout(() => {\n setOverlayOpacity(0);\n timerRef.current = null; // Clear the ref after the timeout executes\n }, eventDurationSeconds * 1000); // Convert seconds to milliseconds\n\n // Save the new timer ID to the ref (unknown necessary type casting)\n timerRef.current = newTimerId as unknown as number;\n };\n\n // Cleanup function to clear the timer when the component unmounts\n React.useEffect(() => {\n return () => {\n const ref = timerRef.current;\n if (ref) {\n clearTimeout(ref);\n }\n };\n }, []); // runs only on mount/unmount\n\n function renderPasteHandler() {\n return (\n <textarea\n onPaste={handlePaste}\n onFocus={() => {\n setIsFocused(true);\n }}\n onBlur={() => setIsFocused(false)}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10,\n resize: \"none\",\n background: \"transparent\",\n cursor: isFocused ? \"copy\" : \"pointer\",\n ...overlayStyle,\n }}\n // 🔑 CRUCIAL: Focus the element before pasting so it receives the paste event.\n // autoFocus\n readOnly\n ref={textareaRef}\n />\n );\n }\n\n /** Map friendly file-type labels to accept= strings for the hidden input. */\n function buildAccept(): string | undefined {\n if (!fileTypes?.length) return undefined;\n return fileTypes\n .map((t) => {\n const ext = t.toLowerCase();\n if (ext === \"jpg\" || ext === \"jpeg\") return \".jpg,.jpeg\";\n return `.${ext}`;\n })\n .join(\",\");\n }\n\n function flashOverlay(color: string) {\n if (timerRef.current) clearTimeout(timerRef.current);\n setOverlayBackground(color);\n setOverlayOpacity(eventOpacity);\n const id = setTimeout(() => {\n setOverlayOpacity(0);\n timerRef.current = null;\n }, eventDurationSeconds * 1000);\n timerRef.current = id as unknown as number;\n }\n\n function handleFileChange(ev: React.ChangeEvent<HTMLInputElement>) {\n const file = ev.target.files?.[0];\n // Reset input so the same file can be re-selected later.\n ev.target.value = \"\";\n if (!file) return;\n\n const reader = new FileReader();\n reader.onload = (e) => {\n const content = (e.target?.result as string) ?? \"\";\n onPastedContent({items: [{mimeType: file.type, content}]});\n flashOverlay(successColor);\n };\n reader.onerror = () => {\n flashOverlay(errorColor);\n };\n reader.readAsDataURL(file);\n }\n\n function onUpload(ev: React.MouseEvent<HTMLButtonElement>) {\n ev.stopPropagation();\n ev.preventDefault();\n fileInputRef.current?.click();\n }\n\n function renderTitle() {\n const uploadBtn = !isFocused && withUpload && (\n <Button\n onClick={onUpload}\n style={{position: \"relative\", zIndex: 30, pointerEvents: \"auto\"}}\n >\n Upload\n </Button>\n );\n\n if (title) {\n return (\n <div className=\"paste-target-msg-title\">\n <h2>{title}</h2>\n {uploadBtn}\n </div>\n );\n } else {\n return (\n <div className=\"paste-target-msg-title\">\n Paste\n {uploadBtn && (\n <>\n or \n {uploadBtn}\n </>\n )}\n here\n </div>\n );\n }\n }\n\n const cn = _cls(\"root\", className || \"pihanga-paste-target\");\n\n return (\n <div\n className={clsx(cn, isFocused && \"pihanga-paste-target-focused\")}\n data-pihanga={cardName}\n style={{position: \"relative\", height, width: \"100%\"}}\n >\n <div\n className=\"paste-target-label\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n textAlign: \"center\",\n // zIndex must be above textarea (10) so the Upload button is clickable.\n // pointer-events: none lets click/focus pass through to the textarea,\n // EXCEPT on children that override with pointer-events: auto (the button).\n pointerEvents: \"none\",\n zIndex: 20,\n }}\n >\n {renderTitle()}\n {description && (\n <span className=\"paste-target-msg-desc\">{description}</span>\n )}\n <div className=\"paste-target-focus-reminder\">{pasteFirstReminder}</div>\n </div>\n {renderPasteHandler()}\n {/* Hidden file input — triggered programmatically by the Upload button */}\n {withUpload && (\n <input\n type=\"file\"\n ref={fileInputRef}\n accept={buildAccept()}\n onChange={handleFileChange}\n style={{display: \"none\"}}\n />\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;AA+BA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,UACA,gBACA,wBAAqB,GACrB,eACA,cAEA,YAAS,GAET,oBAEA,kBAAe,GACf,gBAAa,GACb,0BAAA,GACA,kBAAe,GACf,aACA,cACA,YACE,GACE,CAAC,GAAgB,KAAqB,EAAiB,CAAC,GACxD,CAAC,GAAmB,KAAwB,EAAiB,MAAM,GACnE,IAAW,EAAM,OAAsB,IAAI,GAC3C,IAAc,EAA4B,IAAI,GAC9C,IAAe,EAAyB,IAAI,GAC5C,CAAC,GAAW,KAAgB,EAAkB,EAAK,GAEnD,IAAoC;EACxC,iBAAiB;EACjB,SAAS;CACX,GAEM,KAAe,MAAqD;EAKxE,AAJI,EAAS,WACX,aAAa,EAAS,OAAO,GAG/B,EAAM,eAAe;EACrB,IAAM,IACJ,EAAM,iBACL,OAAmD;EAEtD,IAAI,GAAe;GACjB,IAAM,IAAS,EAAc,OACvB,IAAqB,CAAC;GAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,EAAO,QAAQ,KAAK;IACtC,IAAM,IAAK,EAAO,IACZ,IAAW,EAAG,MACd,IAAU,EAAc,QAAQ,EAAG,IAAI;IAC7C,AAAM,KAAW,MAAM,KAAW,QAChC,EAAM,KAAK;KAAC;KAAU;IAAO,CAAC;GAElC;GAEA,AADA,EAAgB,EAAC,SAAK,CAAC,GACvB,EAAqB,CAAY;EACnC,OACE,EAAqB,CAAU;EAYjC,AAVA,EAAkB,CAAY,GAC1B,EAAY,WAAS,EAAY,QAAQ,KAAK,GASlD,EAAS,UANU,iBAAiB;GAElC,AADA,EAAkB,CAAC,GACnB,EAAS,UAAU;EACrB,GAAG,IAAuB,GAGP;CACrB;CAGA,EAAM,sBACS;EACX,IAAM,IAAM,EAAS;EACrB,AAAI,KACF,aAAa,CAAG;CAEpB,GACC,CAAC,CAAC;CAEL,SAAS,IAAqB;EAC5B,OACE,kBAAC,YAAD;GACE,SAAS;GACT,eAAe;IACb,EAAa,EAAI;GACnB;GACA,cAAc,EAAa,EAAK;GAChC,OAAO;IACL,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,YAAY;IACZ,QAAQ,IAAY,SAAS;IAC7B,GAAG;GACL;GAGA,UAAA;GACA,KAAK;EACN,CAAA;CAEL;CAGA,SAAS,IAAkC;EACpC,OAAW,QAChB,OAAO,EACJ,KAAK,MAAM;GACV,IAAM,IAAM,EAAE,YAAY;GAE1B,OADI,MAAQ,SAAS,MAAQ,SAAe,eACrC,IAAI;EACb,CAAC,EACA,KAAK,GAAG;CACb;CAEA,SAAS,EAAa,GAAe;EAQnC,AAPI,EAAS,WAAS,aAAa,EAAS,OAAO,GACnD,EAAqB,CAAK,GAC1B,EAAkB,CAAY,GAK9B,EAAS,UAJE,iBAAiB;GAE1B,AADA,EAAkB,CAAC,GACnB,EAAS,UAAU;EACrB,GAAG,IAAuB,GACP;CACrB;CAEA,SAAS,EAAiB,GAAyC;EACjE,IAAM,IAAO,EAAG,OAAO,QAAQ;EAG/B,IADA,EAAG,OAAO,QAAQ,IACd,CAAC,GAAM;EAEX,IAAM,IAAS,IAAI,WAAW;EAS9B,AARA,EAAO,UAAU,MAAM;GACrB,IAAM,IAAW,EAAE,QAAQ,UAAqB;GAEhD,AADA,EAAgB,EAAC,OAAO,CAAC;IAAC,UAAU,EAAK;IAAM;GAAO,CAAC,EAAC,CAAC,GACzD,EAAa,CAAY;EAC3B,GACA,EAAO,gBAAgB;GACrB,EAAa,CAAU;EACzB,GACA,EAAO,cAAc,CAAI;CAC3B;CAEA,SAAS,EAAS,GAAyC;EAGzD,AAFA,EAAG,gBAAgB,GACnB,EAAG,eAAe,GAClB,EAAa,SAAS,MAAM;CAC9B;CAEA,SAAS,IAAc;EACrB,IAAM,IAAY,CAAC,KAAa,KAC9B,kBAAC,GAAD;GACE,SAAS;GACT,OAAO;IAAC,UAAU;IAAY,QAAQ;IAAI,eAAe;GAAM;aAChE;EAEO,CAAA;EAWR,OARE,IAEA,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD,EAAA,UAAK,EAAU,CAAA,GACd,CACE;OAIL,kBAAC,OAAD;GAAK,WAAU;aAAf;IAAwC;IAErC,KACC,kBAAA,GAAA,EAAA,UAAA,CAAE,cAEC,CACD,EAAA,CAAA;IACF;GAEC;;CAGX;CAIA,OACE,kBAAC,OAAD;EACE,WAAW,EAJJ,EAAK,QAAQ,KAAa,sBAIjB,GAAI,KAAa,8BAA8B;EAC/D,gBAAc;EACd,OAAO;GAAC,UAAU;GAAY;GAAQ,OAAO;EAAM;YAHrD;GAKE,kBAAC,OAAD;IACE,WAAU;IACV,OAAO;KACL,UAAU;KACV,KAAK;KACL,MAAM;KACN,OAAO;KACP,QAAQ;KACR,SAAS;KACT,eAAe;KACf,gBAAgB;KAChB,YAAY;KACZ,WAAW;KAIX,eAAe;KACf,QAAQ;IACV;cAlBF;KAoBG,EAAY;KACZ,KACC,kBAAC,QAAD;MAAM,WAAU;gBAAyB;KAAkB,CAAA;KAE7D,kBAAC,OAAD;MAAK,WAAU;gBAA+B;KAAwB,CAAA;IACnE;;GACJ,EAAmB;GAEnB,KACC,kBAAC,SAAD;IACE,MAAK;IACL,KAAK;IACL,QAAQ,EAAY;IACpB,UAAU;IACV,OAAO,EAAC,SAAS,OAAM;GACxB,CAAA;EAEA;;AAET"}
|
package/cards/select/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { SelectComponent as e } from "./select.component.js";
|
|
2
|
-
import { PI_SELECT_ACTION as t, PI_SELECT_CARD as n, Select as r, onPiSelectChanged as i, onPiSelectClosed as a, onPiSelectOpened as o } from "./select.types.js";
|
|
3
|
-
import { actionTypesToEvents as
|
|
2
|
+
import { PI_SELECT_ACTION as t, PI_SELECT_CARD as n, Select as r, onPiSelectChanged as i, onPiSelectClosed as a, onPiSelectOpened as o, onSelectChanged as s, onSelectClosed as c, onSelectOpened as l } from "./select.types.js";
|
|
3
|
+
import { actionTypesToEvents as u, registerCardComponent as d } from "@pihanga2/core";
|
|
4
4
|
//#region src/cards/select/index.ts
|
|
5
|
-
|
|
5
|
+
d({
|
|
6
6
|
name: n,
|
|
7
7
|
component: e,
|
|
8
|
-
events:
|
|
8
|
+
events: u(t)
|
|
9
9
|
});
|
|
10
10
|
//#endregion
|
|
11
|
-
export { t as PI_SELECT_ACTION, n as PI_SELECT_CARD, r as Select, i as onPiSelectChanged, a as onPiSelectClosed, o as onPiSelectOpened };
|
|
11
|
+
export { t as PI_SELECT_ACTION, n as PI_SELECT_CARD, r as Select, i as onPiSelectChanged, a as onPiSelectClosed, o as onPiSelectOpened, s as onSelectChanged, c as onSelectClosed, l as onSelectOpened };
|
|
12
12
|
|
|
13
13
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useFormContext as e } from "../form/form.context.js";
|
|
2
2
|
import { Select as t, SelectContent as n, SelectGroup as r, SelectItem as i, SelectTrigger as a, SelectValue as o } from "../../components/ui/select.js";
|
|
3
|
+
import "@pihanga2/core";
|
|
3
4
|
import { useState as s } from "react";
|
|
4
5
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
5
6
|
//#region src/cards/select/select.component.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.component.js","names":[],"sources":["../../../src/cards/select/select.component.tsx"],"sourcesContent":["import React, {useState} from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {\n Select as RadixSelect,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {PiSelectEvents, PiSelectProps} from \"./select.types\";\n\nexport const SelectComponent = (\n props: PiCardProps<PiSelectProps, PiSelectEvents>,\n): React.ReactNode => {\n const {\n name,\n value: propValue = \"\",\n defaultValue,\n selfManaged = false,\n options,\n placeholder,\n required,\n disabled,\n ariaLabel,\n cardName,\n onChanged,\n onOpened,\n onClosed,\n } = props;\n\n // `id` and `invalid` may be injected by a parent pi/field card via\n // Pihanga's extra-prop forwarding. They are not in the official type so\n // we read them through an escape hatch.\n const injectedId = (props as {id?: string}).id;\n const invalid = Boolean((props as {invalid?: boolean}).invalid);\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // Internal state used only when selfManaged=true and not bound to a form.\n const [managedValue, setManagedValue] = useState<string>(\n defaultValue ?? propValue,\n );\n\n const value = useFormData\n ? ((form.formData[name!] as string | undefined) ?? \"\")\n : selfManaged\n ? managedValue\n : propValue;\n\n function handleChange(newValue: string) {\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n if (selfManaged) {\n setManagedValue(newValue);\n }\n onChanged({name, value: newValue});\n }\n }\n\n function handleOpenChange(open: boolean) {\n if (open) {\n onOpened({});\n } else {\n onClosed({});\n }\n }\n\n // Use the id injected by pi/field; fall back to a local id when standalone.\n const selfId = `${cardName}-${name ?? \"select\"}`;\n const fieldId = injectedId ?? selfId;\n\n return (\n <RadixSelect\n value={value}\n defaultValue={defaultValue}\n onValueChange={handleChange}\n onOpenChange={handleOpenChange}\n disabled={disabled}\n required={required}\n aria-label={ariaLabel}\n >\n <SelectTrigger\n id={fieldId}\n aria-invalid={invalid || undefined}\n data-pihanga={cardName}\n >\n <SelectValue placeholder={placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n {options.map((opt) => (\n <SelectItem\n key={opt.value}\n value={opt.value}\n disabled={opt.disabled}\n >\n {opt.label}\n </SelectItem>\n ))}\n </SelectGroup>\n </SelectContent>\n </RadixSelect>\n );\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"select.component.js","names":[],"sources":["../../../src/cards/select/select.component.tsx"],"sourcesContent":["import React, {useState} from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {\n Select as RadixSelect,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {PiSelectEvents, PiSelectProps} from \"./select.types\";\n\nexport const SelectComponent = (\n props: PiCardProps<PiSelectProps, PiSelectEvents>,\n): React.ReactNode => {\n const {\n name,\n value: propValue = \"\",\n defaultValue,\n selfManaged = false,\n options,\n placeholder,\n required,\n disabled,\n ariaLabel,\n cardName,\n onChanged,\n onOpened,\n onClosed,\n } = props;\n\n // `id` and `invalid` may be injected by a parent pi/field card via\n // Pihanga's extra-prop forwarding. They are not in the official type so\n // we read them through an escape hatch.\n const injectedId = (props as {id?: string}).id;\n const invalid = Boolean((props as {invalid?: boolean}).invalid);\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // Internal state used only when selfManaged=true and not bound to a form.\n const [managedValue, setManagedValue] = useState<string>(\n defaultValue ?? propValue,\n );\n\n const value = useFormData\n ? ((form.formData[name!] as string | undefined) ?? \"\")\n : selfManaged\n ? managedValue\n : propValue;\n\n function handleChange(newValue: string) {\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n if (selfManaged) {\n setManagedValue(newValue);\n }\n onChanged({name, value: newValue});\n }\n }\n\n function handleOpenChange(open: boolean) {\n if (open) {\n onOpened({});\n } else {\n onClosed({});\n }\n }\n\n // Use the id injected by pi/field; fall back to a local id when standalone.\n const selfId = `${cardName}-${name ?? \"select\"}`;\n const fieldId = injectedId ?? selfId;\n\n return (\n <RadixSelect\n value={value}\n defaultValue={defaultValue}\n onValueChange={handleChange}\n onOpenChange={handleOpenChange}\n disabled={disabled}\n required={required}\n aria-label={ariaLabel}\n >\n <SelectTrigger\n id={fieldId}\n aria-invalid={invalid || undefined}\n data-pihanga={cardName}\n >\n <SelectValue placeholder={placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n {options.map((opt) => (\n <SelectItem\n key={opt.value}\n value={opt.value}\n disabled={opt.disabled}\n >\n {opt.label}\n </SelectItem>\n ))}\n </SelectGroup>\n </SelectContent>\n </RadixSelect>\n );\n};\n"],"mappings":";;;;;;AAaA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,SACA,OAAO,IAAY,IACnB,iBACA,iBAAc,IACd,YACA,gBACA,aACA,aACA,cACA,aACA,cACA,aACA,gBACE,GAKE,IAAc,EAAwB,IACtC,IAAU,EAAS,EAA8B,SAGjD,IAAO,EAAe,GACtB,IAAc,EAAK,YAAY,EAAQ,GAGvC,CAAC,GAAc,KAAmB,EACtC,KAAgB,CAClB,GAEM,IAAQ,IACR,EAAK,SAAS,MAAiC,KACjD,IACE,IACA;CAEN,SAAS,EAAa,GAAkB;EACtC,AAAI,IACF,EAAK,aAAa,GAAO,CAAQ,KAE7B,KACF,EAAgB,CAAQ,GAE1B,EAAU;GAAC;GAAM,OAAO;EAAQ,CAAC;CAErC;CAEA,SAAS,EAAiB,GAAe;EACvC,AAAI,IACF,EAAS,CAAC,CAAC,IAEX,EAAS,CAAC,CAAC;CAEf;CAGA,IAAM,IAAS,GAAG,EAAS,GAAG,KAAQ;CAGtC,OACE,kBAAC,GAAD;EACS;EACO;EACd,eAAe;EACf,cAAc;EACJ;EACA;EACV,cAAY;YAPd,CASE,kBAAC,GAAD;GACE,IAbU,KAAc;GAcxB,gBAAc,KAAW,KAAA;GACzB,gBAAc;aAEd,kBAAC,GAAD,EAAa,aAAa,KAAe,UAAY,CAAA;EACxC,CAAA,GACf,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,GAAD;GAEE,OAAO,EAAI;GACX,UAAU,EAAI;aAEb,EAAI;EACK,GALL,EAAI,KAKC,CACb,EACU,CAAA,EACA,CAAA,CACJ;;AAEjB"}
|
|
@@ -5,12 +5,24 @@ export declare const PI_SELECT_ACTION: {
|
|
|
5
5
|
CLOSED: string;
|
|
6
6
|
OPENED: string;
|
|
7
7
|
};
|
|
8
|
+
export declare const onSelectChanged: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
9
|
+
cardID: string;
|
|
10
|
+
} & PiSelectChangedEvent>) => void;
|
|
11
|
+
/** @deprecated Use `onSelectChanged` instead. */
|
|
8
12
|
export declare const onPiSelectChanged: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
9
13
|
cardID: string;
|
|
10
14
|
} & PiSelectChangedEvent>) => void;
|
|
15
|
+
export declare const onSelectOpened: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
16
|
+
cardID: string;
|
|
17
|
+
} & PiSelectOpenedEvent>) => void;
|
|
18
|
+
/** @deprecated Use `onSelectOpened` instead. */
|
|
11
19
|
export declare const onPiSelectOpened: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
12
20
|
cardID: string;
|
|
13
21
|
} & PiSelectOpenedEvent>) => void;
|
|
22
|
+
export declare const onSelectClosed: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
23
|
+
cardID: string;
|
|
24
|
+
} & PiSelectClosedEvent>) => void;
|
|
25
|
+
/** @deprecated Use `onSelectClosed` instead. */
|
|
14
26
|
export declare const onPiSelectClosed: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
15
27
|
cardID: string;
|
|
16
28
|
} & PiSelectClosedEvent>) => void;
|
|
@@ -4,8 +4,8 @@ var r = "pi/select", i = e(r), a = n(r, [
|
|
|
4
4
|
"changed",
|
|
5
5
|
"opened",
|
|
6
6
|
"closed"
|
|
7
|
-
]), o = t(a.CHANGED), s = t(a.OPENED), c = t(a.CLOSED);
|
|
7
|
+
]), o = t(a.CHANGED), s = o, c = t(a.OPENED), l = c, u = t(a.CLOSED), d = u;
|
|
8
8
|
//#endregion
|
|
9
|
-
export { a as PI_SELECT_ACTION, r as PI_SELECT_CARD, i as Select,
|
|
9
|
+
export { a as PI_SELECT_ACTION, r as PI_SELECT_CARD, i as Select, s as onPiSelectChanged, d as onPiSelectClosed, l as onPiSelectOpened, o as onSelectChanged, u as onSelectClosed, c as onSelectOpened };
|
|
10
10
|
|
|
11
11
|
//# sourceMappingURL=select.types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.types.js","names":[],"sources":["../../../src/cards/select/select.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_SELECT_CARD = \"pi/select\";\n\nexport const Select = createCardDeclaration<PiSelectProps, PiSelectEvents>(\n PI_SELECT_CARD,\n);\n\nexport const PI_SELECT_ACTION = registerActions(PI_SELECT_CARD, [\n \"changed\",\n \"opened\",\n \"closed\",\n]);\n\nexport const
|
|
1
|
+
{"version":3,"file":"select.types.js","names":[],"sources":["../../../src/cards/select/select.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_SELECT_CARD = \"pi/select\";\n\nexport const Select = createCardDeclaration<PiSelectProps, PiSelectEvents>(\n PI_SELECT_CARD,\n);\n\nexport const PI_SELECT_ACTION = registerActions(PI_SELECT_CARD, [\n \"changed\",\n \"opened\",\n \"closed\",\n]);\n\nexport const onSelectChanged = createOnAction<PiSelectChangedEvent>(\n PI_SELECT_ACTION.CHANGED,\n);\n\n/** @deprecated Use `onSelectChanged` instead. */\nexport const onPiSelectChanged = onSelectChanged;\n\nexport const onSelectOpened = createOnAction<PiSelectOpenedEvent>(\n PI_SELECT_ACTION.OPENED,\n);\n\n/** @deprecated Use `onSelectOpened` instead. */\nexport const onPiSelectOpened = onSelectOpened;\n\nexport const onSelectClosed = createOnAction<PiSelectClosedEvent>(\n PI_SELECT_ACTION.CLOSED,\n);\n\n/** @deprecated Use `onSelectClosed` instead. */\nexport const onPiSelectClosed = onSelectClosed;\n\nexport type PiSelectOption = {\n /** The value stored in form state / dispatched in events. */\n value: string;\n /** Human-readable label shown in the dropdown. */\n label: string;\n /** When true, the option is shown but cannot be selected. */\n disabled?: boolean;\n};\n\nexport type PiSelectProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its selected value from form data and\n * writes back via form.handleChange.\n */\n name?: string;\n\n /**\n * Controlled value used in standalone mode (outside a Form).\n * Ignored when `name` is set and the component is inside a pi/form.\n */\n value?: string;\n\n /**\n * When true the component manages its own selected-value state internally.\n * Selecting an item will still dispatch the normal `onChanged` action, but\n * the UI will immediately reflect the new selection without waiting for the\n * host application to update `value` via a reducer.\n *\n * Has no effect when the component is bound to a pi/form via `name`.\n */\n selfManaged?: boolean;\n\n /**\n * Default (uncontrolled) value. Mirrors Radix `defaultValue`.\n */\n defaultValue?: string;\n\n /** Available options to display in the dropdown. */\n options: PiSelectOption[];\n\n /** Placeholder text shown when no option is selected. */\n placeholder?: string;\n\n /** When true, the select is required (HTML form attribute). */\n required?: boolean;\n\n /** When true, the select is disabled and non-interactive. */\n disabled?: boolean;\n\n /** Accessible label for screen readers. */\n ariaLabel?: string;\n};\n\nexport type PiSelectChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** The value of the newly selected option. */\n value: string;\n};\n\nexport type PiSelectOpenedEvent = Record<string, never>;\n\nexport type PiSelectClosedEvent = Record<string, never>;\n\nexport type PiSelectEvents = {\n onChanged: PiSelectChangedEvent;\n onOpened: PiSelectOpenedEvent;\n onClosed: PiSelectClosedEvent;\n};\n"],"mappings":";;AAMA,IAAa,IAAiB,aAEjB,IAAS,EACpB,CACF,GAEa,IAAmB,EAAgB,GAAgB;CAC9D;CACA;CACA;AACF,CAAC,GAEY,IAAkB,EAC7B,EAAiB,OACnB,GAGa,IAAoB,GAEpB,IAAiB,EAC5B,EAAiB,MACnB,GAGa,IAAmB,GAEnB,IAAiB,EAC5B,EAAiB,MACnB,GAGa,IAAmB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './slider.types';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SliderComponent as e } from "./slider.component.js";
|
|
2
|
+
import { PI_SLIDER_ACTION as t, PI_SLIDER_CARD as n, Slider as r, onPiSliderChanged as i, onPiSliderCommitted as a, onSliderChanged as o, onSliderCommitted as s } from "./slider.types.js";
|
|
3
|
+
import { actionTypesToEvents as c, registerCardComponent as l } from "@pihanga2/core";
|
|
4
|
+
//#region src/cards/slider/index.ts
|
|
5
|
+
l({
|
|
6
|
+
name: n,
|
|
7
|
+
component: e,
|
|
8
|
+
events: c(t)
|
|
9
|
+
});
|
|
10
|
+
//#endregion
|
|
11
|
+
export { t as PI_SLIDER_ACTION, n as PI_SLIDER_CARD, r as Slider, i as onPiSliderChanged, a as onPiSliderCommitted, o as onSliderChanged, s as onSliderCommitted };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/slider/index.ts"],"sourcesContent":["import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {SliderComponent} from \"./slider.component\";\nimport {PI_SLIDER_ACTION, PI_SLIDER_CARD} from \"./slider.types\";\n\nexport * from \"./slider.types\";\n\nregisterCardComponent({\n name: PI_SLIDER_CARD,\n component: SliderComponent,\n events: actionTypesToEvents(PI_SLIDER_ACTION),\n});\n"],"mappings":";;;;AAOA,EAAsB;CACpB,MAAM;CACN,WAAW;CACX,QAAQ,EAAoB,CAAgB;AAC9C,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useFormContext as e } from "../form/form.context.js";
|
|
2
|
+
import { Slider as t } from "../../components/ui/slider.js";
|
|
3
|
+
import "@pihanga2/core";
|
|
4
|
+
import { useEffect as n, useRef as r, useState as i } from "react";
|
|
5
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
//#region src/cards/slider/slider.component.tsx
|
|
7
|
+
var s = (s) => {
|
|
8
|
+
let { name: c, value: l, defaultValue: u, selfManaged: d = !1, min: f = 0, max: p = 100, step: m = 1, disabled: h, debounceMs: g = 0, suppressChangedEvents: _ = !1, label: v, className: y, cardName: b, onChanged: x, onCommitted: S } = s, C = e(), w = C.isInForm && !!c, [T, E] = i(u ?? l ?? f), D = w ? C.formData[c] ?? f : d ? T : l ?? f, O = r(D);
|
|
9
|
+
O.current = D;
|
|
10
|
+
let k = r(!1), [A, j] = i(D), M = k.current ? A : D, N = r(null), P = r(D);
|
|
11
|
+
n(() => () => {
|
|
12
|
+
N.current != null && clearTimeout(N.current);
|
|
13
|
+
}, []);
|
|
14
|
+
function F(e) {
|
|
15
|
+
_ || (w ? C.handleChange(c, e) : x({
|
|
16
|
+
name: c,
|
|
17
|
+
value: e
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
function I() {
|
|
21
|
+
k.current = !0, j(O.current);
|
|
22
|
+
}
|
|
23
|
+
function L(e) {
|
|
24
|
+
let t = e[0] ?? f;
|
|
25
|
+
k.current && (j(t), d && !w && E(t), g > 0 ? (P.current = t, N.current != null && clearTimeout(N.current), N.current = setTimeout(() => {
|
|
26
|
+
N.current = null, F(P.current);
|
|
27
|
+
}, g)) : F(t));
|
|
28
|
+
}
|
|
29
|
+
function R(e) {
|
|
30
|
+
let t = e[0] ?? f;
|
|
31
|
+
k.current = !1, j(O.current), N.current != null && (clearTimeout(N.current), N.current = null), g > 0 && !_ && F(t), S({
|
|
32
|
+
name: c,
|
|
33
|
+
value: t
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return /* @__PURE__ */ o("div", {
|
|
37
|
+
"data-pihanga": b,
|
|
38
|
+
className: `flex flex-col gap-2 ${y ?? ""}`,
|
|
39
|
+
children: [v && /* @__PURE__ */ o("div", {
|
|
40
|
+
className: "flex items-center justify-between",
|
|
41
|
+
children: [/* @__PURE__ */ a("span", {
|
|
42
|
+
className: "text-sm font-medium leading-none",
|
|
43
|
+
children: v
|
|
44
|
+
}), /* @__PURE__ */ a("span", {
|
|
45
|
+
className: "text-sm text-muted-foreground tabular-nums",
|
|
46
|
+
children: M
|
|
47
|
+
})]
|
|
48
|
+
}), /* @__PURE__ */ a(t, {
|
|
49
|
+
value: [M],
|
|
50
|
+
min: f,
|
|
51
|
+
max: p,
|
|
52
|
+
step: m,
|
|
53
|
+
disabled: h,
|
|
54
|
+
onPointerDown: I,
|
|
55
|
+
onValueChange: L,
|
|
56
|
+
onValueCommit: R,
|
|
57
|
+
className: "w-full"
|
|
58
|
+
})]
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { s as SliderComponent };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=slider.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slider.component.js","names":[],"sources":["../../../src/cards/slider/slider.component.tsx"],"sourcesContent":["import React, {useEffect, useRef, useState} from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {Slider as SliderUI} from \"@/components/ui/slider\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {PiSliderEvents, PiSliderProps} from \"./slider.types\";\n\nexport const SliderComponent = (\n props: PiCardProps<PiSliderProps, PiSliderEvents>,\n): React.ReactNode => {\n const {\n name,\n value: propValue,\n defaultValue,\n selfManaged = false,\n min = 0,\n max = 100,\n step = 1,\n disabled,\n debounceMs = 0,\n suppressChangedEvents = false,\n label,\n className,\n cardName,\n onChanged,\n onCommitted,\n } = props;\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // Internal state used only when selfManaged=true and not bound to a form.\n const [managedValue, setManagedValue] = useState<number>(\n defaultValue ?? propValue ?? min,\n );\n\n // The \"canonical\" value from props / form / self-managed state.\n const canonicalValue = useFormData\n ? ((form.formData[name!] as number | undefined) ?? min)\n : selfManaged\n ? managedValue\n : (propValue ?? min);\n\n // Keep a ref to the latest canonical value so event handlers always see\n // the most up-to-date external value even if it changed during a drag.\n const canonicalValueRef = useRef(canonicalValue);\n canonicalValueRef.current = canonicalValue;\n\n // \"Interactive\" drag state — tracks knob position independently while the\n // user is actively dragging so we don't need an external value round-trip.\n const isDragging = useRef(false);\n const [interactiveValue, setInteractiveValue] =\n useState<number>(canonicalValue);\n\n // While dragging the knob follows pointer; otherwise it reflects the canonical value.\n const displayValue = isDragging.current ? interactiveValue : canonicalValue;\n\n // Refs for debounce: timer handle + latest pending value.\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingValue = useRef<number>(canonicalValue);\n\n // Clean up any pending debounce timer on unmount.\n useEffect(() => {\n return () => {\n if (debounceTimer.current != null) clearTimeout(debounceTimer.current);\n };\n }, []);\n\n function dispatchChanged(newValue: number) {\n // Silently skip if the caller has opted out of continuous change events.\n if (suppressChangedEvents) return;\n\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n onChanged({name, value: newValue});\n }\n }\n\n // Called on pointer/touch down — enter interactive mode so the knob can\n // move freely without waiting for an external state update.\n function handlePointerDown() {\n isDragging.current = true;\n setInteractiveValue(canonicalValueRef.current);\n }\n\n function handleValueChange(newValues: number[]) {\n const newValue = newValues[0] ?? min;\n\n // Guard: only update interactive display when actively dragging.\n if (!isDragging.current) return;\n\n setInteractiveValue(newValue);\n\n // Update internal state immediately so the thumb moves without lag.\n if (selfManaged && !useFormData) {\n setManagedValue(newValue);\n }\n\n if (debounceMs > 0) {\n // Record the latest value and (re)start the quiet-window timer.\n pendingValue.current = newValue;\n if (debounceTimer.current != null) clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n debounceTimer.current = null;\n dispatchChanged(pendingValue.current);\n }, debounceMs);\n } else {\n dispatchChanged(newValue);\n }\n }\n\n function handleValueCommit(newValues: number[]) {\n const newValue = newValues[0] ?? min;\n\n // Leave interactive mode — on next render displayValue reverts to canonicalValue,\n // snapping the knob back to whatever the external state currently says.\n isDragging.current = false;\n setInteractiveValue(canonicalValueRef.current);\n\n // Cancel any pending debounced onChanged — the committed event supersedes it.\n if (debounceTimer.current != null) {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = null;\n }\n\n // When debounce is active (and not suppressed), flush the final value via\n // onChanged before the committed event so listeners always see both.\n if (debounceMs > 0 && !suppressChangedEvents) {\n dispatchChanged(newValue);\n }\n\n onCommitted({name, value: newValue});\n }\n\n return (\n <div\n data-pihanga={cardName}\n className={`flex flex-col gap-2 ${className ?? \"\"}`}\n >\n {label && (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium leading-none\">{label}</span>\n <span className=\"text-sm text-muted-foreground tabular-nums\">\n {displayValue}\n </span>\n </div>\n )}\n <SliderUI\n value={[displayValue]}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n onPointerDown={handlePointerDown}\n onValueChange={handleValueChange}\n onValueCommit={handleValueCommit}\n className=\"w-full\"\n />\n </div>\n );\n};\n"],"mappings":";;;;;;AAMA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,SACA,OAAO,GACP,iBACA,iBAAc,IACd,SAAM,GACN,SAAM,KACN,UAAO,GACP,aACA,gBAAa,GACb,2BAAwB,IACxB,UACA,cACA,aACA,cACA,mBACE,GAGE,IAAO,EAAe,GACtB,IAAc,EAAK,YAAY,EAAQ,GAGvC,CAAC,GAAc,KAAmB,EACtC,KAAgB,KAAa,CAC/B,GAGM,IAAiB,IACjB,EAAK,SAAS,MAAiC,IACjD,IACE,IACC,KAAa,GAId,IAAoB,EAAO,CAAc;CAC/C,EAAkB,UAAU;CAI5B,IAAM,IAAa,EAAO,EAAK,GACzB,CAAC,GAAkB,KACvB,EAAiB,CAAc,GAG3B,IAAe,EAAW,UAAU,IAAmB,GAGvD,IAAgB,EAA6C,IAAI,GACjE,IAAe,EAAe,CAAc;CAGlD,cACe;EACX,AAAI,EAAc,WAAW,QAAM,aAAa,EAAc,OAAO;CACvE,GACC,CAAC,CAAC;CAEL,SAAS,EAAgB,GAAkB;EAErC,MAEA,IACF,EAAK,aAAa,GAAO,CAAQ,IAEjC,EAAU;GAAC;GAAM,OAAO;EAAQ,CAAC;CAErC;CAIA,SAAS,IAAoB;EAE3B,AADA,EAAW,UAAU,IACrB,EAAoB,EAAkB,OAAO;CAC/C;CAEA,SAAS,EAAkB,GAAqB;EAC9C,IAAM,IAAW,EAAU,MAAM;EAG5B,EAAW,YAEhB,EAAoB,CAAQ,GAGxB,KAAe,CAAC,KAClB,EAAgB,CAAQ,GAGtB,IAAa,KAEf,EAAa,UAAU,GACnB,EAAc,WAAW,QAAM,aAAa,EAAc,OAAO,GACrE,EAAc,UAAU,iBAAiB;GAEvC,AADA,EAAc,UAAU,MACxB,EAAgB,EAAa,OAAO;EACtC,GAAG,CAAU,KAEb,EAAgB,CAAQ;CAE5B;CAEA,SAAS,EAAkB,GAAqB;EAC9C,IAAM,IAAW,EAAU,MAAM;EAmBjC,AAfA,EAAW,UAAU,IACrB,EAAoB,EAAkB,OAAO,GAGzC,EAAc,WAAW,SAC3B,aAAa,EAAc,OAAO,GAClC,EAAc,UAAU,OAKtB,IAAa,KAAK,CAAC,KACrB,EAAgB,CAAQ,GAG1B,EAAY;GAAC;GAAM,OAAO;EAAQ,CAAC;CACrC;CAEA,OACE,kBAAC,OAAD;EACE,gBAAc;EACd,WAAW,uBAAuB,KAAa;YAFjD,CAIG,KACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAoC;GAAY,CAAA,GAChE,kBAAC,QAAD;IAAM,WAAU;cACb;GACG,CAAA,CACH;MAEP,kBAAC,GAAD;GACE,OAAO,CAAC,CAAY;GACf;GACA;GACC;GACI;GACV,eAAe;GACf,eAAe;GACf,eAAe;GACf,WAAU;EACX,CAAA,CACE;;AAET"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export declare const PI_SLIDER_CARD = "shad/slider";
|
|
2
|
+
export declare const Slider: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<PiSliderProps, S, PiSliderEvents>) => import('@pihanga2/core').PiCardDef;
|
|
3
|
+
export declare const PI_SLIDER_ACTION: {
|
|
4
|
+
CHANGED: string;
|
|
5
|
+
COMMITTED: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const onSliderChanged: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
8
|
+
cardID: string;
|
|
9
|
+
} & PiSliderChangedEvent>) => void;
|
|
10
|
+
/** @deprecated Use `onSliderChanged` instead. */
|
|
11
|
+
export declare const onPiSliderChanged: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
12
|
+
cardID: string;
|
|
13
|
+
} & PiSliderChangedEvent>) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Fired once when the user **finishes** dragging (mouse-up / touch-end).
|
|
16
|
+
*
|
|
17
|
+
* Use this instead of `onSliderChanged` when you only care about the final
|
|
18
|
+
* settled value and do not want to process every intermediate drag position.
|
|
19
|
+
*/
|
|
20
|
+
export declare const onSliderCommitted: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
21
|
+
cardID: string;
|
|
22
|
+
} & PiSliderCommittedEvent>) => void;
|
|
23
|
+
/** @deprecated Use `onSliderCommitted` instead. */
|
|
24
|
+
export declare const onPiSliderCommitted: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
|
|
25
|
+
cardID: string;
|
|
26
|
+
} & PiSliderCommittedEvent>) => void;
|
|
27
|
+
export type PiSliderProps = {
|
|
28
|
+
/**
|
|
29
|
+
* Field name used to bind to FormContext when inside a pi/form card.
|
|
30
|
+
* When provided the component reads its value from form data and writes
|
|
31
|
+
* back via form.handleChange.
|
|
32
|
+
*/
|
|
33
|
+
name?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Controlled numeric value (single thumb).
|
|
36
|
+
* Ignored when `name` is set and the component is inside a pi/form,
|
|
37
|
+
* and also ignored when `selfManaged` is true (use `defaultValue` instead).
|
|
38
|
+
*/
|
|
39
|
+
value?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Initial value used when `selfManaged` is true.
|
|
42
|
+
* Falls back to `value` → `min` → 0 if not provided.
|
|
43
|
+
*/
|
|
44
|
+
defaultValue?: number;
|
|
45
|
+
/**
|
|
46
|
+
* When true the slider keeps its own internal value state.
|
|
47
|
+
* Dragging updates the displayed value immediately without needing an
|
|
48
|
+
* external state update. `onChanged` is still fired on every change so
|
|
49
|
+
* the app can react (or log) without being responsible for feeding the
|
|
50
|
+
* value back as a prop.
|
|
51
|
+
*
|
|
52
|
+
* Ignored when inside a pi/form (form state takes precedence).
|
|
53
|
+
*/
|
|
54
|
+
selfManaged?: boolean;
|
|
55
|
+
/** Minimum value of the slider range. Defaults to 0. */
|
|
56
|
+
min?: number;
|
|
57
|
+
/** Maximum value of the slider range. Defaults to 100. */
|
|
58
|
+
max?: number;
|
|
59
|
+
/** Stepping interval between selectable values. Defaults to 1. */
|
|
60
|
+
step?: number;
|
|
61
|
+
/** When true, the slider is disabled and non-interactive. */
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Debounce delay in milliseconds applied to `onChanged` during continuous
|
|
65
|
+
* drag. Only the last value within each quiet window is dispatched.
|
|
66
|
+
*
|
|
67
|
+
* Useful when the `onChanged` handler is expensive (e.g. triggers an API
|
|
68
|
+
* call) — set e.g. `debounceMs: 200` to cap the event rate.
|
|
69
|
+
*
|
|
70
|
+
* Has no effect on `onCommitted`, which always fires exactly once at
|
|
71
|
+
* drag-end regardless of this setting.
|
|
72
|
+
*
|
|
73
|
+
* Omit (or set to 0) to dispatch on every drag position (default behaviour).
|
|
74
|
+
*/
|
|
75
|
+
debounceMs?: number;
|
|
76
|
+
/**
|
|
77
|
+
* When true, `onSliderChanged` is **never** dispatched during drag.
|
|
78
|
+
*
|
|
79
|
+
* Use this together with `onSliderCommitted` when you only care about
|
|
80
|
+
* the final settled value and want to eliminate all intermediate Redux
|
|
81
|
+
* actions entirely (stricter than `debounceMs`).
|
|
82
|
+
*
|
|
83
|
+
* `onSliderCommitted` is unaffected and always fires on release.
|
|
84
|
+
* `selfManaged` visual updates are also unaffected.
|
|
85
|
+
*/
|
|
86
|
+
suppressChangedEvents?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Optional label text rendered above the slider.
|
|
89
|
+
* When provided, the current numeric value is shown to the right of the label.
|
|
90
|
+
*/
|
|
91
|
+
label?: string;
|
|
92
|
+
/** Extra Tailwind / CSS classes forwarded to the root wrapper element. */
|
|
93
|
+
className?: string;
|
|
94
|
+
};
|
|
95
|
+
export type PiSliderChangedEvent = {
|
|
96
|
+
/** Field name, mirrors the `name` prop if provided. */
|
|
97
|
+
name?: string;
|
|
98
|
+
/** New numeric value after the change. */
|
|
99
|
+
value: number;
|
|
100
|
+
};
|
|
101
|
+
export type PiSliderCommittedEvent = {
|
|
102
|
+
/** Field name, mirrors the `name` prop if provided. */
|
|
103
|
+
name?: string;
|
|
104
|
+
/** Final settled value when the user released the thumb. */
|
|
105
|
+
value: number;
|
|
106
|
+
};
|
|
107
|
+
export type PiSliderEvents = {
|
|
108
|
+
onChanged: PiSliderChangedEvent;
|
|
109
|
+
onCommitted: PiSliderCommittedEvent;
|
|
110
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createCardDeclaration as e, createOnAction as t, registerActions as n } from "@pihanga2/core";
|
|
2
|
+
//#region src/cards/slider/slider.types.ts
|
|
3
|
+
var r = "shad/slider", i = e(r), a = n(r, ["changed", "committed"]), o = t(a.CHANGED), s = o, c = t(a.COMMITTED), l = c;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { a as PI_SLIDER_ACTION, r as PI_SLIDER_CARD, i as Slider, s as onPiSliderChanged, l as onPiSliderCommitted, o as onSliderChanged, c as onSliderCommitted };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=slider.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slider.types.js","names":[],"sources":["../../../src/cards/slider/slider.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_SLIDER_CARD = \"shad/slider\";\n\nexport const Slider = createCardDeclaration<PiSliderProps, PiSliderEvents>(\n PI_SLIDER_CARD,\n);\n\nexport const PI_SLIDER_ACTION = registerActions(PI_SLIDER_CARD, [\n \"changed\",\n \"committed\",\n]);\n\nexport const onSliderChanged = createOnAction<PiSliderChangedEvent>(\n PI_SLIDER_ACTION.CHANGED,\n);\n\n/** @deprecated Use `onSliderChanged` instead. */\nexport const onPiSliderChanged = onSliderChanged;\n\n/**\n * Fired once when the user **finishes** dragging (mouse-up / touch-end).\n *\n * Use this instead of `onSliderChanged` when you only care about the final\n * settled value and do not want to process every intermediate drag position.\n */\nexport const onSliderCommitted = createOnAction<PiSliderCommittedEvent>(\n PI_SLIDER_ACTION.COMMITTED,\n);\n\n/** @deprecated Use `onSliderCommitted` instead. */\nexport const onPiSliderCommitted = onSliderCommitted;\n\n// ---------------------------------------------------------------------------\n// Props & Events\n// ---------------------------------------------------------------------------\n\nexport type PiSliderProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its value from form data and writes\n * back via form.handleChange.\n */\n name?: string;\n\n /**\n * Controlled numeric value (single thumb).\n * Ignored when `name` is set and the component is inside a pi/form,\n * and also ignored when `selfManaged` is true (use `defaultValue` instead).\n */\n value?: number;\n\n /**\n * Initial value used when `selfManaged` is true.\n * Falls back to `value` → `min` → 0 if not provided.\n */\n defaultValue?: number;\n\n /**\n * When true the slider keeps its own internal value state.\n * Dragging updates the displayed value immediately without needing an\n * external state update. `onChanged` is still fired on every change so\n * the app can react (or log) without being responsible for feeding the\n * value back as a prop.\n *\n * Ignored when inside a pi/form (form state takes precedence).\n */\n selfManaged?: boolean;\n\n /** Minimum value of the slider range. Defaults to 0. */\n min?: number;\n\n /** Maximum value of the slider range. Defaults to 100. */\n max?: number;\n\n /** Stepping interval between selectable values. Defaults to 1. */\n step?: number;\n\n /** When true, the slider is disabled and non-interactive. */\n disabled?: boolean;\n\n /**\n * Debounce delay in milliseconds applied to `onChanged` during continuous\n * drag. Only the last value within each quiet window is dispatched.\n *\n * Useful when the `onChanged` handler is expensive (e.g. triggers an API\n * call) — set e.g. `debounceMs: 200` to cap the event rate.\n *\n * Has no effect on `onCommitted`, which always fires exactly once at\n * drag-end regardless of this setting.\n *\n * Omit (or set to 0) to dispatch on every drag position (default behaviour).\n */\n debounceMs?: number;\n\n /**\n * When true, `onSliderChanged` is **never** dispatched during drag.\n *\n * Use this together with `onSliderCommitted` when you only care about\n * the final settled value and want to eliminate all intermediate Redux\n * actions entirely (stricter than `debounceMs`).\n *\n * `onSliderCommitted` is unaffected and always fires on release.\n * `selfManaged` visual updates are also unaffected.\n */\n suppressChangedEvents?: boolean;\n\n /**\n * Optional label text rendered above the slider.\n * When provided, the current numeric value is shown to the right of the label.\n */\n label?: string;\n\n /** Extra Tailwind / CSS classes forwarded to the root wrapper element. */\n className?: string;\n};\n\nexport type PiSliderChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** New numeric value after the change. */\n value: number;\n};\n\nexport type PiSliderCommittedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** Final settled value when the user released the thumb. */\n value: number;\n};\n\nexport type PiSliderEvents = {\n onChanged: PiSliderChangedEvent;\n onCommitted: PiSliderCommittedEvent;\n};\n"],"mappings":";;AAMA,IAAa,IAAiB,eAEjB,IAAS,EACpB,CACF,GAEa,IAAmB,EAAgB,GAAgB,CAC9D,WACA,WACF,CAAC,GAEY,IAAkB,EAC7B,EAAiB,OACnB,GAGa,IAAoB,GAQpB,IAAoB,EAC/B,EAAiB,SACnB,GAGa,IAAsB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sliderValue.types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SliderValueComponent as e } from "./sliderValue.component.js";
|
|
2
|
+
import { PI_SLIDER_VALUE_CARD as t, SliderValue as n } from "./sliderValue.types.js";
|
|
3
|
+
import { registerCardComponent as r } from "@pihanga2/core";
|
|
4
|
+
//#region src/cards/sliderValue/index.ts
|
|
5
|
+
r({
|
|
6
|
+
name: t,
|
|
7
|
+
component: e
|
|
8
|
+
});
|
|
9
|
+
//#endregion
|
|
10
|
+
export { t as PI_SLIDER_VALUE_CARD, n as SliderValue };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/sliderValue/index.ts"],"sourcesContent":["import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {SliderValueComponent} from \"./sliderValue.component\";\nimport {PI_SLIDER_VALUE_CARD} from \"./sliderValue.types\";\n\nexport * from \"./sliderValue.types\";\n\nregisterCardComponent({\n name: PI_SLIDER_VALUE_CARD,\n component: SliderValueComponent,\n});\n"],"mappings":";;;;AAOA,EAAsB;CACpB,MAAM;CACN,WAAW;AACb,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { PiCardProps } from '@pihanga2/core';
|
|
3
|
+
import { PiSliderValueProps } from './sliderValue.types';
|
|
4
|
+
/**
|
|
5
|
+
* Display-only companion to `shad/slider`.
|
|
6
|
+
*
|
|
7
|
+
* Renders the same track + filled-range visual as the interactive slider but
|
|
8
|
+
* with no thumb and no user interaction. Use this card to show a read-only
|
|
9
|
+
* numeric value in contexts where editing is not permitted (e.g. a summary
|
|
10
|
+
* panel, a table cell, or a loading state).
|
|
11
|
+
*
|
|
12
|
+
* The component uses the same Tailwind classes as `src/components/ui/slider.tsx`
|
|
13
|
+
* so the two cards look identical.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SliderValueComponent: (props: PiCardProps<PiSliderValueProps>) => React.ReactNode;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn as e } from "../../components/lib/utils.js";
|
|
2
|
+
import "@pihanga2/core";
|
|
3
|
+
import "react";
|
|
4
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
5
|
+
//#region src/cards/sliderValue/sliderValue.component.tsx
|
|
6
|
+
var r = (r) => {
|
|
7
|
+
let { value: i, min: a = 0, max: o = 100, label: s, className: c, cardName: l } = r, u = Math.min(Math.max(i, a), o), d = o - a, f = d > 0 ? (u - a) / d * 100 : 0;
|
|
8
|
+
return /* @__PURE__ */ n("div", {
|
|
9
|
+
"data-pihanga": l,
|
|
10
|
+
className: e("flex flex-col gap-2", c),
|
|
11
|
+
children: [s && /* @__PURE__ */ n("div", {
|
|
12
|
+
className: "flex items-center justify-between",
|
|
13
|
+
children: [/* @__PURE__ */ t("span", {
|
|
14
|
+
className: "text-sm font-medium leading-none",
|
|
15
|
+
children: s
|
|
16
|
+
}), /* @__PURE__ */ t("span", {
|
|
17
|
+
className: "text-sm text-muted-foreground tabular-nums",
|
|
18
|
+
children: u
|
|
19
|
+
})]
|
|
20
|
+
}), /* @__PURE__ */ t("div", {
|
|
21
|
+
"aria-hidden": "true",
|
|
22
|
+
className: e("bg-muted relative h-1.5 w-full grow overflow-hidden rounded-full"),
|
|
23
|
+
children: /* @__PURE__ */ t("div", {
|
|
24
|
+
className: "bg-primary absolute h-full transition-[width]",
|
|
25
|
+
style: { width: `${f}%` }
|
|
26
|
+
})
|
|
27
|
+
})]
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { r as SliderValueComponent };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=sliderValue.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliderValue.component.js","names":[],"sources":["../../../src/cards/sliderValue/sliderValue.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {cn} from \"@/components/lib/utils\";\nimport type {PiSliderValueProps} from \"./sliderValue.types\";\n\n/**\n * Display-only companion to `shad/slider`.\n *\n * Renders the same track + filled-range visual as the interactive slider but\n * with no thumb and no user interaction. Use this card to show a read-only\n * numeric value in contexts where editing is not permitted (e.g. a summary\n * panel, a table cell, or a loading state).\n *\n * The component uses the same Tailwind classes as `src/components/ui/slider.tsx`\n * so the two cards look identical.\n */\nexport const SliderValueComponent = (\n props: PiCardProps<PiSliderValueProps>,\n): React.ReactNode => {\n const {value, min = 0, max = 100, label, className, cardName} = props;\n\n // Clamp value inside [min, max] and compute fill percentage.\n const clamped = Math.min(Math.max(value, min), max);\n const range = max - min;\n const pct = range > 0 ? ((clamped - min) / range) * 100 : 0;\n\n return (\n <div\n data-pihanga={cardName}\n className={cn(\"flex flex-col gap-2\", className)}\n >\n {label && (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium leading-none\">{label}</span>\n <span className=\"text-sm text-muted-foreground tabular-nums\">\n {clamped}\n </span>\n </div>\n )}\n {/* Track — mirrors the SliderPrimitive.Track CSS from slider.tsx */}\n <div\n aria-hidden=\"true\"\n className={cn(\n \"bg-muted relative h-1.5 w-full grow overflow-hidden rounded-full\",\n )}\n >\n {/* Range fill — mirrors SliderPrimitive.Range */}\n <div\n className=\"bg-primary absolute h-full transition-[width]\"\n style={{width: `${pct}%`}}\n />\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;AAgBA,IAAa,KACX,MACoB;CACpB,IAAM,EAAC,UAAO,SAAM,GAAG,SAAM,KAAK,UAAO,cAAW,gBAAY,GAG1D,IAAU,KAAK,IAAI,KAAK,IAAI,GAAO,CAAG,GAAG,CAAG,GAC5C,IAAQ,IAAM,GACd,IAAM,IAAQ,KAAM,IAAU,KAAO,IAAS,MAAM;CAE1D,OACE,kBAAC,OAAD;EACE,gBAAc;EACd,WAAW,EAAG,uBAAuB,CAAS;YAFhD,CAIG,KACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAoC;GAAY,CAAA,GAChE,kBAAC,QAAD;IAAM,WAAU;cACb;GACG,CAAA,CACH;MAGP,kBAAC,OAAD;GACE,eAAY;GACZ,WAAW,EACT,kEACF;aAGA,kBAAC,OAAD;IACE,WAAU;IACV,OAAO,EAAC,OAAO,GAAG,EAAI,GAAE;GACzB,CAAA;EACE,CAAA,CACF;;AAET"}
|