@aprovan/patchwork 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +22 -0
- package/.github/workflows/publish.yml +41 -0
- package/.prettierignore +17 -0
- package/LICENSE +373 -0
- package/README.md +15 -0
- package/apps/chat/.utcp_config.json +14 -0
- package/apps/chat/.working/widgets/27060b91-a2a5-4272-b243-6eb904bd4070/main.tsx +107 -0
- package/apps/chat/index.html +17 -0
- package/apps/chat/node_modules/.bin/autoprefixer +17 -0
- package/apps/chat/node_modules/.bin/browserslist +17 -0
- package/apps/chat/node_modules/.bin/conc +17 -0
- package/apps/chat/node_modules/.bin/concurrently +17 -0
- package/apps/chat/node_modules/.bin/copilot-proxy +17 -0
- package/apps/chat/node_modules/.bin/jiti +17 -0
- package/apps/chat/node_modules/.bin/tailwind +17 -0
- package/apps/chat/node_modules/.bin/tailwindcss +17 -0
- package/apps/chat/node_modules/.bin/tsc +17 -0
- package/apps/chat/node_modules/.bin/tsserver +17 -0
- package/apps/chat/node_modules/.bin/tsx +17 -0
- package/apps/chat/node_modules/.bin/vite +17 -0
- package/apps/chat/package.json +55 -0
- package/apps/chat/postcss.config.js +6 -0
- package/apps/chat/src/App.tsx +7 -0
- package/apps/chat/src/components/ui/avatar.tsx +48 -0
- package/apps/chat/src/components/ui/badge.tsx +36 -0
- package/apps/chat/src/components/ui/button.tsx +56 -0
- package/apps/chat/src/components/ui/card.tsx +86 -0
- package/apps/chat/src/components/ui/collapsible.tsx +9 -0
- package/apps/chat/src/components/ui/dialog.tsx +60 -0
- package/apps/chat/src/components/ui/input.tsx +25 -0
- package/apps/chat/src/components/ui/scroll-area.tsx +46 -0
- package/apps/chat/src/index.css +190 -0
- package/apps/chat/src/lib/utils.ts +6 -0
- package/apps/chat/src/main.tsx +10 -0
- package/apps/chat/src/pages/ChatPage.tsx +460 -0
- package/apps/chat/tailwind.config.js +71 -0
- package/apps/chat/tsconfig.json +25 -0
- package/apps/chat/vite.config.ts +26 -0
- package/package.json +35 -0
- package/packages/bobbin/node_modules/.bin/esbuild +14 -0
- package/packages/bobbin/node_modules/.bin/jiti +17 -0
- package/packages/bobbin/node_modules/.bin/tsc +17 -0
- package/packages/bobbin/node_modules/.bin/tsserver +17 -0
- package/packages/bobbin/node_modules/.bin/tsup +17 -0
- package/packages/bobbin/node_modules/.bin/tsup-node +17 -0
- package/packages/bobbin/node_modules/.bin/tsx +17 -0
- package/packages/bobbin/package.json +30 -0
- package/packages/bobbin/src/Bobbin.tsx +89 -0
- package/packages/bobbin/src/components/EditPanel/EditPanel.tsx +376 -0
- package/packages/bobbin/src/components/EditPanel/controls/ColorPicker.tsx +138 -0
- package/packages/bobbin/src/components/EditPanel/controls/QuickSelectDropdown.tsx +142 -0
- package/packages/bobbin/src/components/EditPanel/controls/SliderInput.tsx +94 -0
- package/packages/bobbin/src/components/EditPanel/controls/SpacingControl.tsx +285 -0
- package/packages/bobbin/src/components/EditPanel/controls/ToggleGroup.tsx +37 -0
- package/packages/bobbin/src/components/EditPanel/controls/TokenDropdown.tsx +33 -0
- package/packages/bobbin/src/components/EditPanel/sections/AnnotationSection.tsx +136 -0
- package/packages/bobbin/src/components/EditPanel/sections/BackgroundSection.tsx +79 -0
- package/packages/bobbin/src/components/EditPanel/sections/EffectsSection.tsx +85 -0
- package/packages/bobbin/src/components/EditPanel/sections/LayoutSection.tsx +224 -0
- package/packages/bobbin/src/components/EditPanel/sections/SectionWrapper.tsx +57 -0
- package/packages/bobbin/src/components/EditPanel/sections/SizeSection.tsx +166 -0
- package/packages/bobbin/src/components/EditPanel/sections/SpacingSection.tsx +69 -0
- package/packages/bobbin/src/components/EditPanel/sections/TypographySection.tsx +148 -0
- package/packages/bobbin/src/components/Inspector/Inspector.tsx +221 -0
- package/packages/bobbin/src/components/Overlay/ControlHandles.tsx +572 -0
- package/packages/bobbin/src/components/Overlay/MarginPaddingOverlay.tsx +229 -0
- package/packages/bobbin/src/components/Overlay/SelectionOverlay.tsx +73 -0
- package/packages/bobbin/src/components/Pill/Pill.tsx +155 -0
- package/packages/bobbin/src/components/ThemeToggle/ThemeToggle.tsx +72 -0
- package/packages/bobbin/src/core/changeSerializer.ts +139 -0
- package/packages/bobbin/src/core/useBobbin.ts +399 -0
- package/packages/bobbin/src/core/useChangeTracker.ts +186 -0
- package/packages/bobbin/src/core/useClipboard.ts +21 -0
- package/packages/bobbin/src/core/useElementSelection.ts +146 -0
- package/packages/bobbin/src/index.ts +46 -0
- package/packages/bobbin/src/tokens/borders.ts +19 -0
- package/packages/bobbin/src/tokens/colors.ts +150 -0
- package/packages/bobbin/src/tokens/index.ts +37 -0
- package/packages/bobbin/src/tokens/shadows.ts +10 -0
- package/packages/bobbin/src/tokens/spacing.ts +37 -0
- package/packages/bobbin/src/tokens/typography.ts +51 -0
- package/packages/bobbin/src/types.ts +157 -0
- package/packages/bobbin/src/utils/animation.ts +40 -0
- package/packages/bobbin/src/utils/dom.ts +36 -0
- package/packages/bobbin/src/utils/selectors.ts +76 -0
- package/packages/bobbin/tsconfig.json +10 -0
- package/packages/bobbin/tsup.config.ts +10 -0
- package/packages/compiler/node_modules/.bin/esbuild +17 -0
- package/packages/compiler/node_modules/.bin/jiti +17 -0
- package/packages/compiler/node_modules/.bin/tsc +17 -0
- package/packages/compiler/node_modules/.bin/tsserver +17 -0
- package/packages/compiler/node_modules/.bin/tsup +17 -0
- package/packages/compiler/node_modules/.bin/tsup-node +17 -0
- package/packages/compiler/node_modules/.bin/tsx +17 -0
- package/packages/compiler/package.json +38 -0
- package/packages/compiler/src/compiler.ts +258 -0
- package/packages/compiler/src/images/index.ts +13 -0
- package/packages/compiler/src/images/loader.ts +234 -0
- package/packages/compiler/src/images/registry.ts +112 -0
- package/packages/compiler/src/index.ts +141 -0
- package/packages/compiler/src/mount/bridge.ts +399 -0
- package/packages/compiler/src/mount/embedded.ts +306 -0
- package/packages/compiler/src/mount/iframe.ts +433 -0
- package/packages/compiler/src/mount/index.ts +18 -0
- package/packages/compiler/src/schemas.ts +169 -0
- package/packages/compiler/src/transforms/cdn.ts +411 -0
- package/packages/compiler/src/transforms/index.ts +4 -0
- package/packages/compiler/src/transforms/vfs.ts +138 -0
- package/packages/compiler/src/types.ts +233 -0
- package/packages/compiler/src/vfs/backends/indexeddb.ts +66 -0
- package/packages/compiler/src/vfs/backends/local-fs.ts +41 -0
- package/packages/compiler/src/vfs/backends/s3.ts +60 -0
- package/packages/compiler/src/vfs/index.ts +11 -0
- package/packages/compiler/src/vfs/project.ts +56 -0
- package/packages/compiler/src/vfs/store.ts +53 -0
- package/packages/compiler/src/vfs/types.ts +20 -0
- package/packages/compiler/tsconfig.json +8 -0
- package/packages/compiler/tsup.config.ts +14 -0
- package/packages/editor/node_modules/.bin/jiti +17 -0
- package/packages/editor/node_modules/.bin/tsc +17 -0
- package/packages/editor/node_modules/.bin/tsserver +17 -0
- package/packages/editor/node_modules/.bin/tsup +17 -0
- package/packages/editor/node_modules/.bin/tsup-node +17 -0
- package/packages/editor/node_modules/.bin/tsx +17 -0
- package/packages/editor/package.json +45 -0
- package/packages/editor/src/components/CodeBlockExtension.tsx +190 -0
- package/packages/editor/src/components/CodePreview.tsx +344 -0
- package/packages/editor/src/components/MarkdownEditor.tsx +270 -0
- package/packages/editor/src/components/ServicesInspector.tsx +118 -0
- package/packages/editor/src/components/edit/EditHistory.tsx +89 -0
- package/packages/editor/src/components/edit/EditModal.tsx +236 -0
- package/packages/editor/src/components/edit/FileTree.tsx +144 -0
- package/packages/editor/src/components/edit/api.ts +100 -0
- package/packages/editor/src/components/edit/index.ts +6 -0
- package/packages/editor/src/components/edit/types.ts +53 -0
- package/packages/editor/src/components/edit/useEditSession.ts +164 -0
- package/packages/editor/src/components/index.ts +5 -0
- package/packages/editor/src/index.ts +72 -0
- package/packages/editor/src/lib/code-extractor.ts +210 -0
- package/packages/editor/src/lib/diff.ts +308 -0
- package/packages/editor/src/lib/index.ts +4 -0
- package/packages/editor/src/lib/utils.ts +6 -0
- package/packages/editor/src/lib/vfs.ts +106 -0
- package/packages/editor/tsconfig.json +10 -0
- package/packages/editor/tsup.config.ts +10 -0
- package/packages/images/ink/node_modules/.bin/jiti +17 -0
- package/packages/images/ink/node_modules/.bin/tsc +17 -0
- package/packages/images/ink/node_modules/.bin/tsserver +17 -0
- package/packages/images/ink/node_modules/.bin/tsup +17 -0
- package/packages/images/ink/node_modules/.bin/tsup-node +17 -0
- package/packages/images/ink/node_modules/.bin/tsx +17 -0
- package/packages/images/ink/package.json +53 -0
- package/packages/images/ink/src/index.ts +48 -0
- package/packages/images/ink/src/runner.ts +331 -0
- package/packages/images/ink/src/setup.ts +123 -0
- package/packages/images/ink/tsconfig.json +10 -0
- package/packages/images/ink/tsup.config.ts +11 -0
- package/packages/images/shadcn/node_modules/.bin/jiti +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsc +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsserver +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsup +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsup-node +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsx +17 -0
- package/packages/images/shadcn/package.json +82 -0
- package/packages/images/shadcn/src/html.ts +341 -0
- package/packages/images/shadcn/src/index.ts +37 -0
- package/packages/images/shadcn/src/setup.ts +287 -0
- package/packages/images/shadcn/tsconfig.json +9 -0
- package/packages/images/shadcn/tsup.config.ts +13 -0
- package/packages/images/vanilla/node_modules/.bin/jiti +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsc +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsserver +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsup +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsup-node +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsx +17 -0
- package/packages/images/vanilla/package.json +35 -0
- package/packages/images/vanilla/src/index.ts +7 -0
- package/packages/images/vanilla/src/setup.ts +6 -0
- package/packages/images/vanilla/tsconfig.json +9 -0
- package/packages/images/vanilla/tsup.config.ts +10 -0
- package/packages/patchwork/node_modules/.bin/jiti +17 -0
- package/packages/patchwork/node_modules/.bin/tsc +17 -0
- package/packages/patchwork/node_modules/.bin/tsserver +17 -0
- package/packages/patchwork/node_modules/.bin/tsup +17 -0
- package/packages/patchwork/node_modules/.bin/tsup-node +17 -0
- package/packages/patchwork/node_modules/.bin/tsx +17 -0
- package/packages/patchwork/package.json +27 -0
- package/packages/patchwork/src/index.ts +15 -0
- package/packages/patchwork/src/services/index.ts +11 -0
- package/packages/patchwork/src/services/proxy.ts +213 -0
- package/packages/patchwork/src/services/types.ts +28 -0
- package/packages/patchwork/src/types.ts +116 -0
- package/packages/patchwork/tsconfig.json +8 -0
- package/packages/patchwork/tsup.config.ts +14 -0
- package/packages/stitchery/node_modules/.bin/jiti +17 -0
- package/packages/stitchery/node_modules/.bin/tsc +17 -0
- package/packages/stitchery/node_modules/.bin/tsserver +17 -0
- package/packages/stitchery/node_modules/.bin/tsup +17 -0
- package/packages/stitchery/node_modules/.bin/tsup-node +17 -0
- package/packages/stitchery/node_modules/.bin/tsx +17 -0
- package/packages/stitchery/package.json +40 -0
- package/packages/stitchery/src/cli.ts +116 -0
- package/packages/stitchery/src/index.ts +16 -0
- package/packages/stitchery/src/prompts.ts +326 -0
- package/packages/stitchery/src/server/index.ts +365 -0
- package/packages/stitchery/src/server/local-packages.ts +91 -0
- package/packages/stitchery/src/server/routes.ts +122 -0
- package/packages/stitchery/src/server/services.ts +382 -0
- package/packages/stitchery/src/server/vfs-routes.ts +142 -0
- package/packages/stitchery/src/types.ts +59 -0
- package/packages/stitchery/tsconfig.json +13 -0
- package/packages/stitchery/tsup.config.ts +15 -0
- package/packages/utcp/node_modules/.bin/jiti +17 -0
- package/packages/utcp/node_modules/.bin/tsc +17 -0
- package/packages/utcp/node_modules/.bin/tsserver +17 -0
- package/packages/utcp/node_modules/.bin/tsup +17 -0
- package/packages/utcp/node_modules/.bin/tsup-node +17 -0
- package/packages/utcp/node_modules/.bin/tsx +17 -0
- package/packages/utcp/package.json +38 -0
- package/packages/utcp/src/index.ts +153 -0
- package/packages/utcp/tsconfig.json +8 -0
- package/packages/utcp/tsup.config.ts +12 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.json +18 -0
- package/turbo.json +23 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import type { SelectedElement } from '../types';
|
|
3
|
+
import { getElementPath, getElementXPath } from '../utils/selectors';
|
|
4
|
+
|
|
5
|
+
export interface UseElementSelectionOptions {
|
|
6
|
+
container?: HTMLElement | null;
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useElementSelection(options: UseElementSelectionOptions) {
|
|
12
|
+
const { container, exclude = [], enabled = true } = options;
|
|
13
|
+
|
|
14
|
+
const [hoveredElement, setHoveredElement] = useState<SelectedElement | null>(
|
|
15
|
+
null,
|
|
16
|
+
);
|
|
17
|
+
const [selectedElement, setSelectedElement] =
|
|
18
|
+
useState<SelectedElement | null>(null);
|
|
19
|
+
const lastRectRef = useRef<DOMRect | null>(null);
|
|
20
|
+
|
|
21
|
+
const isExcluded = useCallback(
|
|
22
|
+
(el: HTMLElement): boolean => {
|
|
23
|
+
// Exclude bobbin elements themselves
|
|
24
|
+
if (el.closest('[data-bobbin]')) return true;
|
|
25
|
+
// Exclude user-specified selectors
|
|
26
|
+
return exclude.some(
|
|
27
|
+
(selector) => el.matches(selector) || el.closest(selector),
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
[exclude],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const createSelectedElement = useCallback(
|
|
34
|
+
(el: HTMLElement): SelectedElement => {
|
|
35
|
+
return {
|
|
36
|
+
element: el,
|
|
37
|
+
rect: el.getBoundingClientRect(),
|
|
38
|
+
path: getElementPath(el),
|
|
39
|
+
xpath: getElementXPath(el),
|
|
40
|
+
tagName: el.tagName.toLowerCase(),
|
|
41
|
+
id: el.id || undefined,
|
|
42
|
+
classList: Array.from(el.classList),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
[],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const handleMouseMove = useCallback(
|
|
49
|
+
(e: MouseEvent) => {
|
|
50
|
+
if (!enabled) return;
|
|
51
|
+
|
|
52
|
+
const target = document.elementFromPoint(
|
|
53
|
+
e.clientX,
|
|
54
|
+
e.clientY,
|
|
55
|
+
) as HTMLElement | null;
|
|
56
|
+
if (!target || isExcluded(target)) {
|
|
57
|
+
setHoveredElement(null);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if within container bounds
|
|
62
|
+
if (container && !container.contains(target)) {
|
|
63
|
+
setHoveredElement(null);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setHoveredElement(createSelectedElement(target));
|
|
68
|
+
},
|
|
69
|
+
[enabled, container, isExcluded, createSelectedElement],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleClick = useCallback(
|
|
73
|
+
(e: MouseEvent) => {
|
|
74
|
+
if (!enabled) return;
|
|
75
|
+
|
|
76
|
+
// Don't intercept clicks on bobbin UI elements
|
|
77
|
+
const target = e.target as HTMLElement;
|
|
78
|
+
if (target.closest('[data-bobbin]')) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!hoveredElement) return;
|
|
83
|
+
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
|
|
87
|
+
setSelectedElement(hoveredElement);
|
|
88
|
+
lastRectRef.current = hoveredElement.rect;
|
|
89
|
+
},
|
|
90
|
+
[enabled, hoveredElement],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const clearSelection = useCallback(() => {
|
|
94
|
+
setSelectedElement(null);
|
|
95
|
+
setHoveredElement(null);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
const selectElement = useCallback(
|
|
99
|
+
(el: HTMLElement | null) => {
|
|
100
|
+
if (!el) {
|
|
101
|
+
clearSelection();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setSelectedElement(createSelectedElement(el));
|
|
105
|
+
},
|
|
106
|
+
[createSelectedElement, clearSelection],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!enabled) return;
|
|
111
|
+
|
|
112
|
+
document.addEventListener('mousemove', handleMouseMove, { passive: true });
|
|
113
|
+
document.addEventListener('click', handleClick, { capture: true });
|
|
114
|
+
|
|
115
|
+
return () => {
|
|
116
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
117
|
+
document.removeEventListener('click', handleClick, { capture: true });
|
|
118
|
+
};
|
|
119
|
+
}, [enabled, handleMouseMove, handleClick]);
|
|
120
|
+
|
|
121
|
+
// Update rect on scroll/resize
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!selectedElement) return;
|
|
124
|
+
|
|
125
|
+
const updateRect = () => {
|
|
126
|
+
const newRect = selectedElement.element.getBoundingClientRect();
|
|
127
|
+
setSelectedElement((prev) => (prev ? { ...prev, rect: newRect } : null));
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
window.addEventListener('scroll', updateRect, { passive: true });
|
|
131
|
+
window.addEventListener('resize', updateRect, { passive: true });
|
|
132
|
+
|
|
133
|
+
return () => {
|
|
134
|
+
window.removeEventListener('scroll', updateRect);
|
|
135
|
+
window.removeEventListener('resize', updateRect);
|
|
136
|
+
};
|
|
137
|
+
}, [selectedElement?.element]);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
hoveredElement,
|
|
141
|
+
selectedElement,
|
|
142
|
+
selectElement,
|
|
143
|
+
clearSelection,
|
|
144
|
+
lastRect: lastRectRef.current,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Main component
|
|
2
|
+
export { Bobbin } from './Bobbin';
|
|
3
|
+
export type { BobbinComponentProps } from './Bobbin';
|
|
4
|
+
|
|
5
|
+
// Hooks
|
|
6
|
+
export { useBobbin } from './core/useBobbin';
|
|
7
|
+
export { useElementSelection } from './core/useElementSelection';
|
|
8
|
+
export { useChangeTracker } from './core/useChangeTracker';
|
|
9
|
+
export { useClipboard } from './core/useClipboard';
|
|
10
|
+
|
|
11
|
+
// Utilities
|
|
12
|
+
export {
|
|
13
|
+
serializeChangesToYAML,
|
|
14
|
+
parseYAMLChangeset,
|
|
15
|
+
} from './core/changeSerializer';
|
|
16
|
+
export { getElementPath, getElementXPath, generateId } from './utils/selectors';
|
|
17
|
+
|
|
18
|
+
// Types
|
|
19
|
+
export type {
|
|
20
|
+
BobbinProps,
|
|
21
|
+
BobbinState,
|
|
22
|
+
BobbinActions,
|
|
23
|
+
SelectedElement,
|
|
24
|
+
Change,
|
|
25
|
+
ChangeType,
|
|
26
|
+
StyleChange,
|
|
27
|
+
TextChange,
|
|
28
|
+
MoveChange,
|
|
29
|
+
Annotation,
|
|
30
|
+
DesignTokens,
|
|
31
|
+
BobbinChangeset,
|
|
32
|
+
} from './types';
|
|
33
|
+
|
|
34
|
+
// Tokens
|
|
35
|
+
export { defaultTokens } from './tokens';
|
|
36
|
+
export { colors } from './tokens/colors';
|
|
37
|
+
export { spacing } from './tokens/spacing';
|
|
38
|
+
export {
|
|
39
|
+
fontSize,
|
|
40
|
+
fontWeight,
|
|
41
|
+
fontFamily,
|
|
42
|
+
lineHeight,
|
|
43
|
+
letterSpacing,
|
|
44
|
+
} from './tokens/typography';
|
|
45
|
+
export { borderRadius, borderWidth } from './tokens/borders';
|
|
46
|
+
export { boxShadow } from './tokens/shadows';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const borderRadius: Record<string, string> = {
|
|
2
|
+
none: '0px',
|
|
3
|
+
sm: '0.125rem',
|
|
4
|
+
DEFAULT: '0.25rem',
|
|
5
|
+
md: '0.375rem',
|
|
6
|
+
lg: '0.5rem',
|
|
7
|
+
xl: '0.75rem',
|
|
8
|
+
'2xl': '1rem',
|
|
9
|
+
'3xl': '1.5rem',
|
|
10
|
+
full: '9999px',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const borderWidth: Record<string, string> = {
|
|
14
|
+
'0': '0px',
|
|
15
|
+
DEFAULT: '1px',
|
|
16
|
+
'2': '2px',
|
|
17
|
+
'4': '4px',
|
|
18
|
+
'8': '8px',
|
|
19
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export const colors = {
|
|
2
|
+
slate: {
|
|
3
|
+
50: '#f8fafc',
|
|
4
|
+
100: '#f1f5f9',
|
|
5
|
+
200: '#e2e8f0',
|
|
6
|
+
300: '#cbd5e1',
|
|
7
|
+
400: '#94a3b8',
|
|
8
|
+
500: '#64748b',
|
|
9
|
+
600: '#475569',
|
|
10
|
+
700: '#334155',
|
|
11
|
+
800: '#1e293b',
|
|
12
|
+
900: '#0f172a',
|
|
13
|
+
950: '#020617',
|
|
14
|
+
},
|
|
15
|
+
gray: {
|
|
16
|
+
50: '#f9fafb',
|
|
17
|
+
100: '#f3f4f6',
|
|
18
|
+
200: '#e5e7eb',
|
|
19
|
+
300: '#d1d5db',
|
|
20
|
+
400: '#9ca3af',
|
|
21
|
+
500: '#6b7280',
|
|
22
|
+
600: '#4b5563',
|
|
23
|
+
700: '#374151',
|
|
24
|
+
800: '#1f2937',
|
|
25
|
+
900: '#111827',
|
|
26
|
+
950: '#030712',
|
|
27
|
+
},
|
|
28
|
+
zinc: {
|
|
29
|
+
50: '#fafafa',
|
|
30
|
+
100: '#f4f4f5',
|
|
31
|
+
200: '#e4e4e7',
|
|
32
|
+
300: '#d4d4d8',
|
|
33
|
+
400: '#a1a1aa',
|
|
34
|
+
500: '#71717a',
|
|
35
|
+
600: '#52525b',
|
|
36
|
+
700: '#3f3f46',
|
|
37
|
+
800: '#27272a',
|
|
38
|
+
900: '#18181b',
|
|
39
|
+
950: '#09090b',
|
|
40
|
+
},
|
|
41
|
+
red: {
|
|
42
|
+
50: '#fef2f2',
|
|
43
|
+
100: '#fee2e2',
|
|
44
|
+
200: '#fecaca',
|
|
45
|
+
300: '#fca5a5',
|
|
46
|
+
400: '#f87171',
|
|
47
|
+
500: '#ef4444',
|
|
48
|
+
600: '#dc2626',
|
|
49
|
+
700: '#b91c1c',
|
|
50
|
+
800: '#991b1b',
|
|
51
|
+
900: '#7f1d1d',
|
|
52
|
+
950: '#450a0a',
|
|
53
|
+
},
|
|
54
|
+
orange: {
|
|
55
|
+
50: '#fff7ed',
|
|
56
|
+
100: '#ffedd5',
|
|
57
|
+
200: '#fed7aa',
|
|
58
|
+
300: '#fdba74',
|
|
59
|
+
400: '#fb923c',
|
|
60
|
+
500: '#f97316',
|
|
61
|
+
600: '#ea580c',
|
|
62
|
+
700: '#c2410c',
|
|
63
|
+
800: '#9a3412',
|
|
64
|
+
900: '#7c2d12',
|
|
65
|
+
950: '#431407',
|
|
66
|
+
},
|
|
67
|
+
yellow: {
|
|
68
|
+
50: '#fefce8',
|
|
69
|
+
100: '#fef9c3',
|
|
70
|
+
200: '#fef08a',
|
|
71
|
+
300: '#fde047',
|
|
72
|
+
400: '#facc15',
|
|
73
|
+
500: '#eab308',
|
|
74
|
+
600: '#ca8a04',
|
|
75
|
+
700: '#a16207',
|
|
76
|
+
800: '#854d0e',
|
|
77
|
+
900: '#713f12',
|
|
78
|
+
950: '#422006',
|
|
79
|
+
},
|
|
80
|
+
green: {
|
|
81
|
+
50: '#f0fdf4',
|
|
82
|
+
100: '#dcfce7',
|
|
83
|
+
200: '#bbf7d0',
|
|
84
|
+
300: '#86efac',
|
|
85
|
+
400: '#4ade80',
|
|
86
|
+
500: '#22c55e',
|
|
87
|
+
600: '#16a34a',
|
|
88
|
+
700: '#15803d',
|
|
89
|
+
800: '#166534',
|
|
90
|
+
900: '#14532d',
|
|
91
|
+
950: '#052e16',
|
|
92
|
+
},
|
|
93
|
+
blue: {
|
|
94
|
+
50: '#eff6ff',
|
|
95
|
+
100: '#dbeafe',
|
|
96
|
+
200: '#bfdbfe',
|
|
97
|
+
300: '#93c5fd',
|
|
98
|
+
400: '#60a5fa',
|
|
99
|
+
500: '#3b82f6',
|
|
100
|
+
600: '#2563eb',
|
|
101
|
+
700: '#1d4ed8',
|
|
102
|
+
800: '#1e40af',
|
|
103
|
+
900: '#1e3a8a',
|
|
104
|
+
950: '#172554',
|
|
105
|
+
},
|
|
106
|
+
indigo: {
|
|
107
|
+
50: '#eef2ff',
|
|
108
|
+
100: '#e0e7ff',
|
|
109
|
+
200: '#c7d2fe',
|
|
110
|
+
300: '#a5b4fc',
|
|
111
|
+
400: '#818cf8',
|
|
112
|
+
500: '#6366f1',
|
|
113
|
+
600: '#4f46e5',
|
|
114
|
+
700: '#4338ca',
|
|
115
|
+
800: '#3730a3',
|
|
116
|
+
900: '#312e81',
|
|
117
|
+
950: '#1e1b4b',
|
|
118
|
+
},
|
|
119
|
+
purple: {
|
|
120
|
+
50: '#faf5ff',
|
|
121
|
+
100: '#f3e8ff',
|
|
122
|
+
200: '#e9d5ff',
|
|
123
|
+
300: '#d8b4fe',
|
|
124
|
+
400: '#c084fc',
|
|
125
|
+
500: '#a855f7',
|
|
126
|
+
600: '#9333ea',
|
|
127
|
+
700: '#7e22ce',
|
|
128
|
+
800: '#6b21a8',
|
|
129
|
+
900: '#581c87',
|
|
130
|
+
950: '#3b0764',
|
|
131
|
+
},
|
|
132
|
+
pink: {
|
|
133
|
+
50: '#fdf2f8',
|
|
134
|
+
100: '#fce7f3',
|
|
135
|
+
200: '#fbcfe8',
|
|
136
|
+
300: '#f9a8d4',
|
|
137
|
+
400: '#f472b6',
|
|
138
|
+
500: '#ec4899',
|
|
139
|
+
600: '#db2777',
|
|
140
|
+
700: '#be185d',
|
|
141
|
+
800: '#9d174d',
|
|
142
|
+
900: '#831843',
|
|
143
|
+
950: '#500724',
|
|
144
|
+
},
|
|
145
|
+
// Semantic
|
|
146
|
+
white: { DEFAULT: '#ffffff' },
|
|
147
|
+
black: { DEFAULT: '#000000' },
|
|
148
|
+
transparent: { DEFAULT: 'transparent' },
|
|
149
|
+
current: { DEFAULT: 'currentColor' },
|
|
150
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { DesignTokens } from '../types';
|
|
2
|
+
import { colors } from './colors';
|
|
3
|
+
import { spacing } from './spacing';
|
|
4
|
+
import {
|
|
5
|
+
fontSize,
|
|
6
|
+
fontWeight,
|
|
7
|
+
fontFamily,
|
|
8
|
+
lineHeight,
|
|
9
|
+
letterSpacing,
|
|
10
|
+
} from './typography';
|
|
11
|
+
import { borderRadius, borderWidth } from './borders';
|
|
12
|
+
import { boxShadow } from './shadows';
|
|
13
|
+
|
|
14
|
+
export const defaultTokens: DesignTokens = {
|
|
15
|
+
colors,
|
|
16
|
+
spacing,
|
|
17
|
+
fontSize,
|
|
18
|
+
fontWeight,
|
|
19
|
+
fontFamily,
|
|
20
|
+
borderRadius,
|
|
21
|
+
borderWidth,
|
|
22
|
+
boxShadow,
|
|
23
|
+
lineHeight,
|
|
24
|
+
letterSpacing,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { colors } from './colors';
|
|
28
|
+
export { spacing } from './spacing';
|
|
29
|
+
export {
|
|
30
|
+
fontSize,
|
|
31
|
+
fontWeight,
|
|
32
|
+
fontFamily,
|
|
33
|
+
lineHeight,
|
|
34
|
+
letterSpacing,
|
|
35
|
+
} from './typography';
|
|
36
|
+
export { borderRadius, borderWidth } from './borders';
|
|
37
|
+
export { boxShadow } from './shadows';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const boxShadow: Record<string, string> = {
|
|
2
|
+
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
|
3
|
+
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
|
4
|
+
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
|
5
|
+
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
|
|
6
|
+
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
|
|
7
|
+
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
|
|
8
|
+
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
|
|
9
|
+
none: 'none',
|
|
10
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const spacing: Record<string, string> = {
|
|
2
|
+
px: '1px',
|
|
3
|
+
'0': '0px',
|
|
4
|
+
'0.5': '0.125rem',
|
|
5
|
+
'1': '0.25rem',
|
|
6
|
+
'1.5': '0.375rem',
|
|
7
|
+
'2': '0.5rem',
|
|
8
|
+
'2.5': '0.625rem',
|
|
9
|
+
'3': '0.75rem',
|
|
10
|
+
'3.5': '0.875rem',
|
|
11
|
+
'4': '1rem',
|
|
12
|
+
'5': '1.25rem',
|
|
13
|
+
'6': '1.5rem',
|
|
14
|
+
'7': '1.75rem',
|
|
15
|
+
'8': '2rem',
|
|
16
|
+
'9': '2.25rem',
|
|
17
|
+
'10': '2.5rem',
|
|
18
|
+
'11': '2.75rem',
|
|
19
|
+
'12': '3rem',
|
|
20
|
+
'14': '3.5rem',
|
|
21
|
+
'16': '4rem',
|
|
22
|
+
'20': '5rem',
|
|
23
|
+
'24': '6rem',
|
|
24
|
+
'28': '7rem',
|
|
25
|
+
'32': '8rem',
|
|
26
|
+
'36': '9rem',
|
|
27
|
+
'40': '10rem',
|
|
28
|
+
'44': '11rem',
|
|
29
|
+
'48': '12rem',
|
|
30
|
+
'52': '13rem',
|
|
31
|
+
'56': '14rem',
|
|
32
|
+
'60': '15rem',
|
|
33
|
+
'64': '16rem',
|
|
34
|
+
'72': '18rem',
|
|
35
|
+
'80': '20rem',
|
|
36
|
+
'96': '24rem',
|
|
37
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const fontSize: Record<string, string> = {
|
|
2
|
+
xs: '0.75rem',
|
|
3
|
+
sm: '0.875rem',
|
|
4
|
+
base: '1rem',
|
|
5
|
+
lg: '1.125rem',
|
|
6
|
+
xl: '1.25rem',
|
|
7
|
+
'2xl': '1.5rem',
|
|
8
|
+
'3xl': '1.875rem',
|
|
9
|
+
'4xl': '2.25rem',
|
|
10
|
+
'5xl': '3rem',
|
|
11
|
+
'6xl': '3.75rem',
|
|
12
|
+
'7xl': '4.5rem',
|
|
13
|
+
'8xl': '6rem',
|
|
14
|
+
'9xl': '8rem',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const fontWeight: Record<string, string> = {
|
|
18
|
+
thin: '100',
|
|
19
|
+
extralight: '200',
|
|
20
|
+
light: '300',
|
|
21
|
+
normal: '400',
|
|
22
|
+
medium: '500',
|
|
23
|
+
semibold: '600',
|
|
24
|
+
bold: '700',
|
|
25
|
+
extrabold: '800',
|
|
26
|
+
black: '900',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const fontFamily: Record<string, string> = {
|
|
30
|
+
sans: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
|
|
31
|
+
serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
|
32
|
+
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const lineHeight: Record<string, string> = {
|
|
36
|
+
none: '1',
|
|
37
|
+
tight: '1.25',
|
|
38
|
+
snug: '1.375',
|
|
39
|
+
normal: '1.5',
|
|
40
|
+
relaxed: '1.625',
|
|
41
|
+
loose: '2',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const letterSpacing: Record<string, string> = {
|
|
45
|
+
tighter: '-0.05em',
|
|
46
|
+
tight: '-0.025em',
|
|
47
|
+
normal: '0em',
|
|
48
|
+
wide: '0.025em',
|
|
49
|
+
wider: '0.05em',
|
|
50
|
+
widest: '0.1em',
|
|
51
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// === Element Selection ===
|
|
2
|
+
export interface SelectedElement {
|
|
3
|
+
element: HTMLElement;
|
|
4
|
+
rect: DOMRect;
|
|
5
|
+
path: string; // CSS selector path
|
|
6
|
+
xpath: string; // XPath selector
|
|
7
|
+
tagName: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
classList: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// === Change Tracking ===
|
|
13
|
+
export type ChangeType =
|
|
14
|
+
| 'style' // CSS property change
|
|
15
|
+
| 'text' // Text content change
|
|
16
|
+
| 'delete' // Element removed
|
|
17
|
+
| 'move' // Element repositioned
|
|
18
|
+
| 'duplicate' // Element duplicated
|
|
19
|
+
| 'insert' // New element inserted
|
|
20
|
+
| 'attribute'; // Attribute modified
|
|
21
|
+
|
|
22
|
+
export interface Change {
|
|
23
|
+
id: string;
|
|
24
|
+
type: ChangeType;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
target: {
|
|
27
|
+
path: string; // CSS selector path to element
|
|
28
|
+
xpath: string; // XPath selector to element
|
|
29
|
+
tagName: string;
|
|
30
|
+
};
|
|
31
|
+
before: unknown;
|
|
32
|
+
after: unknown;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface StyleChange extends Change {
|
|
37
|
+
type: 'style';
|
|
38
|
+
before: { property: string; value: string };
|
|
39
|
+
after: { property: string; value: string };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TextChange extends Change {
|
|
43
|
+
type: 'text';
|
|
44
|
+
before: string;
|
|
45
|
+
after: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MoveChange extends Change {
|
|
49
|
+
type: 'move';
|
|
50
|
+
before: { parent: string; index: number };
|
|
51
|
+
after: { parent: string; index: number };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// === Annotations ===
|
|
55
|
+
export interface Annotation {
|
|
56
|
+
id: string;
|
|
57
|
+
elementPath: string; // CSS selector
|
|
58
|
+
elementXpath: string; // XPath selector
|
|
59
|
+
content: string;
|
|
60
|
+
createdAt: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// === Design Tokens ===
|
|
64
|
+
export interface DesignTokens {
|
|
65
|
+
colors: Record<string, Record<string, string>>;
|
|
66
|
+
spacing: Record<string, string>;
|
|
67
|
+
fontSize: Record<string, string>;
|
|
68
|
+
fontWeight: Record<string, string>;
|
|
69
|
+
fontFamily: Record<string, string>;
|
|
70
|
+
borderRadius: Record<string, string>;
|
|
71
|
+
borderWidth: Record<string, string>;
|
|
72
|
+
boxShadow: Record<string, string>;
|
|
73
|
+
lineHeight: Record<string, string>;
|
|
74
|
+
letterSpacing: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// === Bobbin State ===
|
|
78
|
+
export interface BobbinState {
|
|
79
|
+
isActive: boolean;
|
|
80
|
+
isPillExpanded: boolean;
|
|
81
|
+
hoveredElement: SelectedElement | null;
|
|
82
|
+
selectedElement: SelectedElement | null;
|
|
83
|
+
changes: Change[];
|
|
84
|
+
annotations: Annotation[];
|
|
85
|
+
clipboard: SelectedElement | null;
|
|
86
|
+
showMarginPadding: boolean;
|
|
87
|
+
activePanel: 'style' | 'inspector' | null;
|
|
88
|
+
theme: 'light' | 'dark' | 'system';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface BobbinActions {
|
|
92
|
+
activate: () => void;
|
|
93
|
+
deactivate: () => void;
|
|
94
|
+
selectElement: (el: HTMLElement | null) => void;
|
|
95
|
+
clearSelection: () => void;
|
|
96
|
+
applyStyle: (property: string, value: string) => void;
|
|
97
|
+
deleteElement: () => void;
|
|
98
|
+
moveElement: (targetParent: HTMLElement, index: number) => void;
|
|
99
|
+
duplicateElement: () => void;
|
|
100
|
+
insertElement: (
|
|
101
|
+
direction: 'before' | 'after' | 'child',
|
|
102
|
+
content?: string,
|
|
103
|
+
) => void;
|
|
104
|
+
copyElement: () => void;
|
|
105
|
+
pasteElement: (direction: 'before' | 'after' | 'child') => void;
|
|
106
|
+
annotate: (content: string) => void;
|
|
107
|
+
toggleMarginPadding: () => void;
|
|
108
|
+
toggleTheme: () => void;
|
|
109
|
+
undo: () => void;
|
|
110
|
+
exportChanges: () => string; // Returns YAML
|
|
111
|
+
getChanges: () => Change[];
|
|
112
|
+
resetChanges: () => void; // Reset all style changes
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// === YAML Export Format ===
|
|
116
|
+
export interface BobbinChangeset {
|
|
117
|
+
version: '1.0';
|
|
118
|
+
timestamp: string;
|
|
119
|
+
changeCount: number;
|
|
120
|
+
changes: Array<{
|
|
121
|
+
type: ChangeType;
|
|
122
|
+
target: string; // CSS selector
|
|
123
|
+
xpath: string; // XPath selector
|
|
124
|
+
property?: string;
|
|
125
|
+
before?: string;
|
|
126
|
+
after?: string;
|
|
127
|
+
note?: string;
|
|
128
|
+
}>;
|
|
129
|
+
annotations: Array<{
|
|
130
|
+
type: 'annotation';
|
|
131
|
+
target: string;
|
|
132
|
+
xpath: string;
|
|
133
|
+
note: string;
|
|
134
|
+
}>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// === Component Props ===
|
|
138
|
+
export interface BobbinProps {
|
|
139
|
+
/** Custom design tokens to merge with defaults */
|
|
140
|
+
tokens?: Partial<DesignTokens>;
|
|
141
|
+
/** Container to scope element selection (default: document.body) */
|
|
142
|
+
container?: HTMLElement | null;
|
|
143
|
+
/** Container for pill positioning (if different from container) */
|
|
144
|
+
pillContainer?: HTMLElement | null;
|
|
145
|
+
/** Initial active state */
|
|
146
|
+
defaultActive?: boolean;
|
|
147
|
+
/** Callback when changes occur */
|
|
148
|
+
onChanges?: (changes: Change[]) => void;
|
|
149
|
+
/** Callback when selection changes */
|
|
150
|
+
onSelect?: (element: SelectedElement | null) => void;
|
|
151
|
+
/** Custom pill position offset from bottom-right of container */
|
|
152
|
+
position?: { bottom: number; right: number };
|
|
153
|
+
/** Z-index for overlay elements */
|
|
154
|
+
zIndex?: number;
|
|
155
|
+
/** Elements to exclude from selection (CSS selectors) */
|
|
156
|
+
exclude?: string[];
|
|
157
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface FLIPState {
|
|
2
|
+
rect: DOMRect;
|
|
3
|
+
opacity: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function measureElement(el: HTMLElement): FLIPState {
|
|
7
|
+
return {
|
|
8
|
+
rect: el.getBoundingClientRect(),
|
|
9
|
+
opacity: parseFloat(getComputedStyle(el).opacity),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function animateFLIP(
|
|
14
|
+
el: HTMLElement,
|
|
15
|
+
from: FLIPState,
|
|
16
|
+
to: FLIPState,
|
|
17
|
+
duration = 150,
|
|
18
|
+
): void {
|
|
19
|
+
const deltaX = from.rect.left - to.rect.left;
|
|
20
|
+
const deltaY = from.rect.top - to.rect.top;
|
|
21
|
+
const deltaW = from.rect.width / to.rect.width;
|
|
22
|
+
const deltaH = from.rect.height / to.rect.height;
|
|
23
|
+
|
|
24
|
+
el.animate(
|
|
25
|
+
[
|
|
26
|
+
{
|
|
27
|
+
transform: `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`,
|
|
28
|
+
opacity: from.opacity,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
transform: 'translate(0, 0) scale(1, 1)',
|
|
32
|
+
opacity: to.opacity,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
{
|
|
36
|
+
duration,
|
|
37
|
+
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
}
|