@dynamic-framework/ui-react 2.8.0 → 2.9.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/dist/css/dynamic-ui.css +5 -22
- package/dist/css/dynamic-ui.min.css +2 -2
- package/dist/index.esm.js +178 -35
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +179 -34
- package/dist/index.js.map +1 -1
- package/dist/types/components/DInputCheck/DInputCheck.d.ts +1 -0
- package/dist/types/components/DInputSwitch/DInputSwitch.d.ts +2 -1
- package/dist/types/components/DModal/components/DModalHeader.d.ts +5 -1
- package/dist/types/components/DOffcanvas/components/DOffcanvasHeader.d.ts +5 -1
- package/dist/types/components/DTooltip/DTooltip.d.ts +38 -3
- package/dist/types/utils/buildUrl.d.ts +32 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/sanitizeHref.d.ts +37 -0
- package/dist/types/utils/tests/buildUrl.spec.d.ts +1 -0
- package/dist/types/utils/tests/sanitizeHref.spec.d.ts +1 -0
- package/package.json +9 -6
- package/src/style/_shame.scss +1 -5
- package/src/style/abstracts/variables/_+import.scss +1 -0
- package/src/style/abstracts/variables/_box.scss +7 -0
- package/src/style/abstracts/variables/_forms.scss +1 -3
- package/src/style/abstracts/variables/_shadow.scss +12 -1
- package/src/style/base/_+import.scss +0 -1
- package/src/style/base/_form-check.scss +4 -2
- package/src/style/components/_d-box.scss +5 -5
- package/src/style/base/_form-text.scss +0 -12
|
@@ -12,6 +12,7 @@ type Props = ComponentPropsWithoutRef<'input'> & BaseProps & {
|
|
|
12
12
|
invalid?: boolean;
|
|
13
13
|
valid?: boolean;
|
|
14
14
|
hint?: string;
|
|
15
|
+
/** Only applies when `type` is `checkbox`; ignored for `radio`. */
|
|
15
16
|
indeterminate?: boolean;
|
|
16
17
|
value?: string;
|
|
17
18
|
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
@@ -10,8 +10,9 @@ type Props = BaseProps & {
|
|
|
10
10
|
inputClassName?: string;
|
|
11
11
|
invalid?: boolean;
|
|
12
12
|
valid?: boolean;
|
|
13
|
+
hint?: string;
|
|
13
14
|
readonly?: boolean;
|
|
14
15
|
onChange?: (isChecked: boolean) => void;
|
|
15
16
|
};
|
|
16
|
-
export default function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, invalid, valid, readonly, className, style, dataAttributes, inputClassName, onChange, }: Props): import("react").JSX.Element;
|
|
17
|
+
export default function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, invalid, valid, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }: Props): import("react").JSX.Element;
|
|
17
18
|
export {};
|
|
@@ -3,8 +3,12 @@ import type { BaseProps, FamilyIconProps } from '../../interface';
|
|
|
3
3
|
type Props = BaseProps & FamilyIconProps & PropsWithChildren<{
|
|
4
4
|
showCloseButton?: boolean;
|
|
5
5
|
icon?: string;
|
|
6
|
+
iconMaterialStyle?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Use `iconMaterialStyle` instead. It will be removed in a future major version.
|
|
9
|
+
*/
|
|
6
10
|
materialStyle?: boolean;
|
|
7
11
|
onClose?: () => void;
|
|
8
12
|
}>;
|
|
9
|
-
export default function DModalHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle, }: Props): import("react").JSX.Element;
|
|
13
|
+
export default function DModalHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, iconMaterialStyle, materialStyle: materialStyleProp, }: Props): import("react").JSX.Element;
|
|
10
14
|
export {};
|
|
@@ -3,8 +3,12 @@ import type { BaseProps, FamilyIconProps } from '../../interface';
|
|
|
3
3
|
type Props = BaseProps & FamilyIconProps & PropsWithChildren<{
|
|
4
4
|
showCloseButton?: boolean;
|
|
5
5
|
icon?: string;
|
|
6
|
+
iconMaterialStyle?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Use `iconMaterialStyle` instead. It will be removed in a future major version.
|
|
9
|
+
*/
|
|
6
10
|
materialStyle?: boolean;
|
|
7
11
|
onClose?: () => void;
|
|
8
12
|
}>;
|
|
9
|
-
export default function DOffcanvasHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle, }: Props): import("react").JSX.Element;
|
|
13
|
+
export default function DOffcanvasHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, iconMaterialStyle, materialStyle: materialStyleProp, }: Props): import("react").JSX.Element;
|
|
10
14
|
export {};
|
|
@@ -1,16 +1,51 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
2
|
import type { Placement } from '@floating-ui/react';
|
|
3
3
|
import type { BaseProps, ComponentSize } from '../interface';
|
|
4
|
-
export type Props = BaseProps &
|
|
4
|
+
export type Props = BaseProps & {
|
|
5
|
+
/** CSS class applied to the wrapper element that surrounds `Component` (the trigger). */
|
|
5
6
|
childrenClassName?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Distance in pixels between the trigger and the floating tooltip.
|
|
9
|
+
* Defaults to arrow height + gap.
|
|
10
|
+
*/
|
|
6
11
|
offSet?: number;
|
|
12
|
+
/** Virtual padding, in pixels, used to keep the tooltip within the viewport boundaries. */
|
|
7
13
|
padding?: number;
|
|
14
|
+
/** When true, the tooltip opens when the pointer hovers over the trigger element. */
|
|
8
15
|
withHover?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* When true, the tooltip opens when the trigger element receives keyboard focus
|
|
18
|
+
* (recommended for accessibility).
|
|
19
|
+
*/
|
|
9
20
|
withFocus?: boolean;
|
|
21
|
+
/** When true, the tooltip toggles open/closed when the trigger element is clicked. */
|
|
10
22
|
withClick?: boolean;
|
|
23
|
+
/** Controls the initial open state of the tooltip. */
|
|
11
24
|
open?: boolean;
|
|
25
|
+
/** Size of the tooltip content. Omit for the default size. */
|
|
12
26
|
size?: ComponentSize;
|
|
27
|
+
/**
|
|
28
|
+
* Preferred position of the tooltip relative to the trigger element.
|
|
29
|
+
* Flips automatically if there isn't enough space.
|
|
30
|
+
*/
|
|
13
31
|
placement?: Placement;
|
|
32
|
+
/**
|
|
33
|
+
* Element that triggers the tooltip. It is rendered inside a wrapper `<div>`
|
|
34
|
+
* that holds the Floating UI reference and the interaction handlers, so the
|
|
35
|
+
* trigger area is that wrapper rather than this node itself. Style it with
|
|
36
|
+
* `childrenClassName`.
|
|
37
|
+
*/
|
|
14
38
|
Component: ReactNode;
|
|
15
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Floating content rendered inside the tooltip body. Keep it short and
|
|
41
|
+
* non-critical: the tooltip is only visible while it is open.
|
|
42
|
+
*/
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Tooltip is an abstraction component that wraps any element (`Component`) and displays
|
|
47
|
+
* floating contextual content (`children`) positioned relative to it using Floating UI.
|
|
48
|
+
* Use it to add short, non-critical hints or descriptions triggered by hover, focus, or
|
|
49
|
+
* click, without altering the surrounding layout.
|
|
50
|
+
*/
|
|
16
51
|
export default function DTooltip({ className, childrenClassName, style, offSet, padding, withFocus, withClick, withHover, open, placement, size, Component, children, }: Props): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds an internal URL from a `path` and a set of query `params`, normalizing leading
|
|
3
|
+
* separators to prevent scheme-relative URLs (e.g. `//accounts` and `/\accounts` both become
|
|
4
|
+
* `/accounts`).
|
|
5
|
+
*
|
|
6
|
+
* The `path` is trimmed and every tab, line feed and carriage return is removed before
|
|
7
|
+
* building the result, because the URL parser ignores those characters too and they would
|
|
8
|
+
* otherwise hide a scheme-relative prefix (`/<TAB>/evil.com` resolves to `//evil.com`).
|
|
9
|
+
*
|
|
10
|
+
* Handles the special case where `path` is the literal string `'#'`, which happens when
|
|
11
|
+
* a Liquid template variable used as a link target is left unconfigured; in that case the
|
|
12
|
+
* params are ignored and `'#'` is returned as-is instead of building a broken URL.
|
|
13
|
+
*
|
|
14
|
+
* @param path - The path to build the URL from. Leading separators are stripped before
|
|
15
|
+
* building the result (`/accounts`, `//accounts` and `\accounts` are all treated as `accounts`).
|
|
16
|
+
* @param params - Key/value pairs serialized as the query string.
|
|
17
|
+
* @returns The resulting URL:
|
|
18
|
+
* - `'#'` when `path` normalizes to `'#'` (`'#'`, `'/#'`), ignoring the params.
|
|
19
|
+
* - `'/'` when `path` is empty and there are no params.
|
|
20
|
+
* - `'/?<query>'` when `path` is empty and there are params.
|
|
21
|
+
* - `'<path>'` or `'<path>?<query>'` when `path` starts with `#`, since a fragment must not be
|
|
22
|
+
* prefixed with `/` (it would navigate away from the current document).
|
|
23
|
+
* - `'/<path>'` or `'/<path>?<query>'` otherwise.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* buildUrl('accounts', { account_id: '42' }); // '/accounts?account_id=42'
|
|
27
|
+
* buildUrl('//accounts', {}); // '/accounts'
|
|
28
|
+
* buildUrl('#', { account_id: '42' }); // '#'
|
|
29
|
+
* buildUrl('#accounts', { account_id: '42' }); // '#accounts?account_id=42'
|
|
30
|
+
* buildUrl('', { tab: 'movimientos' }); // '/?tab=movimientos'
|
|
31
|
+
*/
|
|
32
|
+
export default function buildUrl(path: string, params: Record<string, string | number>): string;
|
|
@@ -4,4 +4,6 @@ export { default as getQueryString } from './getQueryString';
|
|
|
4
4
|
export { default as changeQueryString } from './changeQueryString';
|
|
5
5
|
export { default as getCssVariable } from './getCssVariable';
|
|
6
6
|
export { default as validatePhoneNumber } from './validatePhoneNumber';
|
|
7
|
+
export { default as sanitizeHref } from './sanitizeHref';
|
|
8
|
+
export { default as buildUrl } from './buildUrl';
|
|
7
9
|
export * from './mediaQuery';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a `href` value and returns it only if it is safe to render as a link,
|
|
3
|
+
* preventing XSS through unsafe URL schemes and scheme-relative URLs.
|
|
4
|
+
*
|
|
5
|
+
* Before validating, the value is trimmed and every tab, line feed and carriage return is
|
|
6
|
+
* removed, because the URL parser ignores those characters too and they would otherwise
|
|
7
|
+
* hide an unsafe value from the checks below.
|
|
8
|
+
*
|
|
9
|
+
* A `href` is considered safe when it is either:
|
|
10
|
+
* - An absolute path starting with a single separator (e.g. `/accounts/42`). A leading `\`
|
|
11
|
+
* is normalized to `/` in the returned value, since that is how the browser resolves it.
|
|
12
|
+
* - A URL using one of the allowed schemes: `http:`, `https:`, `mailto:`, `tel:`.
|
|
13
|
+
*
|
|
14
|
+
* A `href` is rejected (returns `undefined`) when it is:
|
|
15
|
+
* - Empty or made only of whitespace/ignored characters (`''`, `' '`).
|
|
16
|
+
* - Scheme-relative (starts with two separators, e.g. `//evil.com`, `/\evil.com`,
|
|
17
|
+
* `/<TAB>/evil.com`), since the browser would resolve it using the current protocol and it
|
|
18
|
+
* can be used to redirect to an attacker-controlled host.
|
|
19
|
+
* - Using a disallowed/dangerous scheme, such as `javascript:` or `data:`
|
|
20
|
+
* (e.g. `javascript:alert(1)`, `data:text/html,<script>...</script>`).
|
|
21
|
+
* - Not parseable as an absolute URL and not an absolute path, such as bare fragments
|
|
22
|
+
* (`#anchor`) or scheme-less relative paths (`relative/path`). These cannot be validated
|
|
23
|
+
* against `SAFE_SCHEMES`, so they are rejected rather than assumed safe.
|
|
24
|
+
*
|
|
25
|
+
* @param href - The raw URL or path to validate, typically coming from user input,
|
|
26
|
+
* a CMS, or an external API.
|
|
27
|
+
* @returns The sanitized `href` when safe, `undefined` otherwise.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* sanitizeHref('/accounts/42'); // '/accounts/42'
|
|
31
|
+
* sanitizeHref('https://example.com'); // 'https://example.com'
|
|
32
|
+
* sanitizeHref('mailto:test@example.com'); // 'mailto:test@example.com'
|
|
33
|
+
* sanitizeHref('//evil.com'); // undefined
|
|
34
|
+
* sanitizeHref('/\\evil.com'); // undefined
|
|
35
|
+
* sanitizeHref('javascript:alert(1)'); // undefined
|
|
36
|
+
*/
|
|
37
|
+
export default function sanitizeHref(href: string): string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"sideEffects": [
|
|
4
4
|
"*.css"
|
|
5
5
|
],
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.9.0",
|
|
7
7
|
"description": "React Dynamic Framework",
|
|
8
8
|
"license": "https://github.com/dynamic-framework/dynamic-ui/blob/master/LICENSE.md",
|
|
9
9
|
"repository": {
|
|
@@ -40,9 +40,10 @@
|
|
|
40
40
|
}
|
|
41
41
|
],
|
|
42
42
|
"scripts": {
|
|
43
|
-
"test": "jest",
|
|
44
|
-
"test:watch": "jest --watch",
|
|
45
|
-
"test:coverage": "jest --coverage",
|
|
43
|
+
"test": "jest --testPathPatterns=\\.spec\\.tsx?$",
|
|
44
|
+
"test:watch": "jest --testPathPatterns=\\.spec\\.tsx?$ --watch",
|
|
45
|
+
"test:coverage": "jest --testPathPatterns=\\.spec\\.tsx?$ --coverage",
|
|
46
|
+
"test:a11y": "jest --testPathPatterns=\\.a11y\\.test\\.tsx$",
|
|
46
47
|
"test:storybook": "test-storybook",
|
|
47
48
|
"build": "npm run clean && npm run build:react && npm run build:scss && npm run cp",
|
|
48
49
|
"build:react": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.mjs",
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
"prepare": "husky",
|
|
81
82
|
"commitlint": "commitlint --edit",
|
|
82
83
|
"verify": "node scripts/verify.js",
|
|
83
|
-
"axe": "npx tsx scripts/generate-a11y-report.ts"
|
|
84
|
+
"axe": "npx tsx scripts/generate-a11y-report.ts",
|
|
85
|
+
"axe:browser": "npx tsx scripts/generate-a11y-browser-warnings.ts"
|
|
84
86
|
},
|
|
85
87
|
"engines": {
|
|
86
88
|
"node": ">=22.0.0"
|
|
@@ -130,6 +132,7 @@
|
|
|
130
132
|
"@testing-library/user-event": "^14.6.1",
|
|
131
133
|
"@types/google-libphonenumber": "~7.4.30",
|
|
132
134
|
"@types/jest": "~29.5.12",
|
|
135
|
+
"@types/jest-axe": "^3.5.9",
|
|
133
136
|
"@types/node": "^22.0.0",
|
|
134
137
|
"@types/react": "^19.2.0",
|
|
135
138
|
"@types/react-dom": "^19.2.0",
|
|
@@ -157,7 +160,7 @@
|
|
|
157
160
|
"jest-axe": "^10.0.0",
|
|
158
161
|
"jest-environment-jsdom": "^30.3.0",
|
|
159
162
|
"lint-staged": "^15.2.10",
|
|
160
|
-
"playwright": "
|
|
163
|
+
"playwright": "1.61.1",
|
|
161
164
|
"postcss-cli": "~10.1.0",
|
|
162
165
|
"react": "~19.2.1",
|
|
163
166
|
"react-docgen-typescript": "^2.4.0",
|
package/src/style/_shame.scss
CHANGED
|
@@ -4,16 +4,12 @@
|
|
|
4
4
|
flex: 1;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// Deprecated by bootstrap 5.3, but still used in some components
|
|
7
8
|
.text-muted {
|
|
8
9
|
--#{$prefix}text-muted: var(--#{$prefix}gray-500);
|
|
9
10
|
color: var(--#{$prefix}text-muted) !important;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
.form-text {
|
|
13
|
-
--#{$prefix}form-text: var(--#{$prefix}gray-500);
|
|
14
|
-
color: var(--#{$prefix}form-text) !important;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
.spinner-border {
|
|
18
14
|
margin-top: 4px;
|
|
19
15
|
margin-bottom: 4px;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Box (DBox)
|
|
2
|
+
|
|
3
|
+
$box-component-bg: var(--#{$prefix}white) !default;
|
|
4
|
+
$box-component-border: 0 !default;
|
|
5
|
+
$box-component-border-radius: var(--#{$prefix}border-radius) !default;
|
|
6
|
+
$box-component-padding: var(--#{$prefix}ref-spacer-4) !default;
|
|
7
|
+
$box-component-shadow: var(--#{$prefix}box-shadow-sm) !default;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// scss-docs-start form-text-variables
|
|
2
2
|
$form-text-margin-top: var(--#{$prefix}ref-spacer-1) !default;
|
|
3
|
-
$form-text-color: var(--#{$prefix}gray-
|
|
3
|
+
$form-text-color: var(--#{$prefix}gray-500) !default;
|
|
4
4
|
// scss-docs-end form-text-variables
|
|
5
5
|
|
|
6
6
|
// scss-docs-start form-label-variables
|
|
@@ -81,8 +81,6 @@ $input-group-addon-border-color: transparent !default;
|
|
|
81
81
|
// custom
|
|
82
82
|
$input-group-sm-icon-size: $spacer-4 !default;
|
|
83
83
|
$input-group-lg-icon-size: $spacer-6 !default;
|
|
84
|
-
$form-text-padding-y: 0 !default;
|
|
85
|
-
$form-text-padding-x: 0 !default;
|
|
86
84
|
// end custom
|
|
87
85
|
|
|
88
86
|
// scss-docs-start form-select-variables
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
// Shadows
|
|
2
|
+
//
|
|
3
|
+
// Bootstrap's shadow scale, declared here explicitly so the elevation ramp is
|
|
4
|
+
// visible in one place and can be retuned quickly. Keep it monotonic:
|
|
5
|
+
// `sm` < default < `lg`, otherwise the `.shadow-*` / `.hover:shadow-*`
|
|
6
|
+
// utilities stop matching what Bootstrap's docs show.
|
|
7
|
+
//
|
|
8
|
+
// scss-docs-start box-shadow-variables
|
|
9
|
+
$box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !default;
|
|
10
|
+
$box-shadow-sm: 0 .125rem .25rem rgba(0, 0, 0, .075) !default;
|
|
11
|
+
$box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, .175) !default;
|
|
12
|
+
// scss-docs-end box-shadow-variables
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
.form-check {
|
|
11
|
+
// .form-check padding-start only accounts for the input width, the gap between
|
|
12
|
+
// the input and the label comes from .form-check-label's --bs-label-padding-x.
|
|
13
|
+
// The hint must repeat that offset to stay aligned with the label text.
|
|
11
14
|
.form-text {
|
|
12
|
-
--#{$prefix}form-text-padding: 0 var(--#{$prefix}ref-spacer-2);
|
|
13
|
-
|
|
14
15
|
display: flex;
|
|
16
|
+
padding-inline: var(--#{$prefix}ref-spacer-2);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
@each $state, $data in $form-validation-states {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
.d-box {
|
|
2
|
-
--#{$prefix}box-component-bg: #{
|
|
3
|
-
--#{$prefix}box-component-border:
|
|
4
|
-
--#{$prefix}box-component-border-radius: #{
|
|
5
|
-
--#{$prefix}box-component-padding: #{
|
|
6
|
-
--#{$prefix}box-component-shadow: #{
|
|
2
|
+
--#{$prefix}box-component-bg: #{$box-component-bg};
|
|
3
|
+
--#{$prefix}box-component-border: #{$box-component-border};
|
|
4
|
+
--#{$prefix}box-component-border-radius: #{$box-component-border-radius};
|
|
5
|
+
--#{$prefix}box-component-padding: #{$box-component-padding};
|
|
6
|
+
--#{$prefix}box-component-shadow: #{$box-component-shadow};
|
|
7
7
|
|
|
8
8
|
padding: var(--#{$prefix}box-component-padding);
|
|
9
9
|
background-color: var(--#{$prefix}box-component-bg);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
.form-text {
|
|
2
|
-
--#{$prefix}form-text-padding: #{$form-text-padding-y} #{$form-text-padding-x};
|
|
3
|
-
--#{$prefix}form-text-gap: var(--#{$prefix}ref-spacer-1);
|
|
4
|
-
--#{$prefix}form-text-color: #{$form-text-color};
|
|
5
|
-
|
|
6
|
-
display: inline-flex;
|
|
7
|
-
flex-direction: row;
|
|
8
|
-
gap: var(--#{$prefix}form-text-gap);
|
|
9
|
-
align-items: center;
|
|
10
|
-
padding: var(--#{$prefix}form-text-padding);
|
|
11
|
-
color: var(--#{$prefix}form-text-color);
|
|
12
|
-
}
|