@milaboratories/uikit 2.2.13 → 2.2.14
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/CHANGELOG.md +6 -0
- package/dist/pl-uikit.js +2043 -1943
- package/dist/pl-uikit.umd.cjs +6 -6
- package/dist/src/components/PlDropdownLine/PlDropdownLine.vue.d.ts +1 -1
- package/dist/src/components/PlDropdownRef/PlDropdownRef.vue.d.ts +1 -1
- package/dist/src/components/PlEditableTitle/PlEditableTitle.vue.d.ts +37 -0
- package/dist/src/components/PlEditableTitle/index.d.ts +1 -0
- package/dist/src/composition/useTransformedModel.d.ts +40 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/types.d.ts +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/components/PlEditableTitle/PlEditableTitle.vue +88 -0
- package/src/components/PlEditableTitle/index.ts +1 -0
- package/src/components/PlEditableTitle/pl-editable-title.module.scss +77 -0
- package/src/composition/useTransformedModel.ts +80 -0
- package/src/index.ts +1 -0
- package/src/layout/PlBlockPage/PlBlockPage.vue +1 -1
- package/src/layout/PlBlockPage/pl-block-page.scss +5 -1
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/uikit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/pl-uikit.umd.js",
|
|
6
6
|
"module": "dist/pl-uikit.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"yarpm": "^1.2.0",
|
|
34
34
|
"svgo": "^3.3.2",
|
|
35
35
|
"@milaboratories/helpers": "^1.6.7",
|
|
36
|
-
"@platforma-sdk/model": "^1.13.
|
|
36
|
+
"@platforma-sdk/model": "^1.13.5"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"dev": "vite",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { useTransformedModel } from '@/composition/useTransformedModel';
|
|
3
|
+
import style from './pl-editable-title.module.scss';
|
|
4
|
+
import { computed, ref } from 'vue';
|
|
5
|
+
|
|
6
|
+
const model = defineModel<string>();
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
/**
|
|
11
|
+
* Standard input placeholder
|
|
12
|
+
*/
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Any css `width` value (px, %), default is 80%
|
|
16
|
+
*/
|
|
17
|
+
maxWidth?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Fixed non-editable prefix
|
|
20
|
+
*/
|
|
21
|
+
prefix?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Max title length (default is 1000)
|
|
24
|
+
*/
|
|
25
|
+
maxLength?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Min title length
|
|
28
|
+
*/
|
|
29
|
+
minLength?: number;
|
|
30
|
+
}>(),
|
|
31
|
+
{
|
|
32
|
+
placeholder: 'Title',
|
|
33
|
+
maxWidth: '80%',
|
|
34
|
+
prefix: undefined,
|
|
35
|
+
maxLength: 1000,
|
|
36
|
+
minLength: undefined,
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const local = useTransformedModel(model, {
|
|
41
|
+
update() {
|
|
42
|
+
return false;
|
|
43
|
+
},
|
|
44
|
+
parse: (v): string => {
|
|
45
|
+
if (typeof v !== 'string') {
|
|
46
|
+
throw Error('value should be a string');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (props.maxLength && v.length > props.maxLength) {
|
|
50
|
+
throw Error(`Max title length is ${props.maxLength} characters`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (props.minLength && v.length < props.minLength) {
|
|
54
|
+
throw Error(`Min title length is ${props.minLength} characters`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return v.trim();
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const computedStyle = computed(() => ({
|
|
62
|
+
maxWidth: props.maxWidth ?? '80%',
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
const save = () => {
|
|
66
|
+
model.value = local.value && !local.error ? local.value : model.value;
|
|
67
|
+
local.reset();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const inputRef = ref<HTMLInputElement>();
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div class="pl-editable-title" :class="style.component" :style="computedStyle">
|
|
75
|
+
<div :class="style.container" @click="() => inputRef?.focus()">
|
|
76
|
+
<span v-if="prefix">{{ prefix.trim() }} </span>
|
|
77
|
+
<input
|
|
78
|
+
ref="inputRef"
|
|
79
|
+
v-model="local.value"
|
|
80
|
+
:placeholder="placeholder"
|
|
81
|
+
@focusout="save"
|
|
82
|
+
@keydown.escape="local.reset"
|
|
83
|
+
@keydown.enter="(ev) => (ev.target as HTMLInputElement)?.blur()"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
<div v-if="local.error" :class="style.error">{{ local.error }}</div>
|
|
87
|
+
</div>
|
|
88
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PlEditableTitle } from './PlEditableTitle.vue';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
@import "@/assets/mixins.scss";
|
|
2
|
+
|
|
3
|
+
.component {
|
|
4
|
+
position: relative;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
gap: 0;
|
|
8
|
+
|
|
9
|
+
--mask-icon-bg-color: transparent;
|
|
10
|
+
--mask-size: 24px;
|
|
11
|
+
|
|
12
|
+
&:hover {
|
|
13
|
+
--mask-icon-bg-color: var(--ic-02);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&:focus-within:not(&:hover) {
|
|
17
|
+
--mask-icon-bg-color: transparent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.container {
|
|
21
|
+
position: relative;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: row;
|
|
24
|
+
gap: 0;
|
|
25
|
+
align-items: center;
|
|
26
|
+
margin-right: calc(var(--mask-size));
|
|
27
|
+
|
|
28
|
+
span {
|
|
29
|
+
font-size: 28px;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
line-height: 32px;
|
|
32
|
+
letter-spacing: -0.56px;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
input {
|
|
37
|
+
outline: none;
|
|
38
|
+
border: none;
|
|
39
|
+
text-overflow: ellipsis;
|
|
40
|
+
cursor: text;
|
|
41
|
+
field-sizing: content;
|
|
42
|
+
font-size: 28px;
|
|
43
|
+
font-weight: 500;
|
|
44
|
+
line-height: 32px;
|
|
45
|
+
letter-spacing: -0.56px;
|
|
46
|
+
padding-top: 4px;
|
|
47
|
+
padding-bottom: 4px;
|
|
48
|
+
padding-right: 4px;
|
|
49
|
+
margin: 0;
|
|
50
|
+
font-family: var(--font-family-base);
|
|
51
|
+
white-space: nowrap;
|
|
52
|
+
text-overflow: ellipsis;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
&::placeholder {
|
|
55
|
+
color: var(--txt-mask);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&::before {
|
|
60
|
+
content: '';
|
|
61
|
+
@include mask-var(url(@icons/icon-assets-min/24_edit.svg));
|
|
62
|
+
position: absolute;
|
|
63
|
+
right: calc((var(--mask-size)) * -1);
|
|
64
|
+
bottom: 6px;
|
|
65
|
+
background-color: var(--mask-icon-bg-color);
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.error {
|
|
71
|
+
position: absolute;
|
|
72
|
+
bottom: -4px;
|
|
73
|
+
transform: translateY(100%);
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
@include field-error();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Ref, UnwrapNestedRefs } from 'vue';
|
|
2
|
+
import { reactive, computed, ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a reactive local model with optional transformation and validation logic.
|
|
6
|
+
*
|
|
7
|
+
* @template T The type of the model's value.
|
|
8
|
+
*
|
|
9
|
+
* @param model - A `Ref` representing the underlying value.
|
|
10
|
+
* @param options - Optional configuration for validation and parsing.
|
|
11
|
+
* @param options.update - A function that takes the transformed value and returns `true` if it should be applied to the model, or `false` to keep it in a cached state.
|
|
12
|
+
* @param options.parse - A function that takes the input value and returns a transformed value of type `T`. If omitted, the value is used as-is.
|
|
13
|
+
*
|
|
14
|
+
* @returns A reactive object with the following properties:
|
|
15
|
+
* - `value`: A computed property for getting and setting the model value.
|
|
16
|
+
* - `error`: A `Ref<string | undefined>` containing the last error message, if any.
|
|
17
|
+
* - `reset`: A method to clear the cached value and error state.
|
|
18
|
+
*
|
|
19
|
+
* ### Example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { ref } from 'vue';
|
|
22
|
+
* import { useTransformedModel } from './useTransformedModel';
|
|
23
|
+
*
|
|
24
|
+
* const model = ref<number>(42);
|
|
25
|
+
*
|
|
26
|
+
* const transformedModel = useTransformedModel(model, {
|
|
27
|
+
* parse: (value) => {
|
|
28
|
+
* const parsed = Number(value);
|
|
29
|
+
* if (!Number.isFinite(parsed)) throw new Error('Invalid number');
|
|
30
|
+
* return parsed;
|
|
31
|
+
* },
|
|
32
|
+
* update: (value) => value >= 0, // Only allow non-negative numbers
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export function useTransformedModel<T>(model: Ref<T>, options: { update?: (v: T) => boolean; parse?: (v: unknown) => T }) {
|
|
37
|
+
const cached = ref<T | undefined>();
|
|
38
|
+
const error = ref<string>();
|
|
39
|
+
|
|
40
|
+
const { parse, update } = options;
|
|
41
|
+
|
|
42
|
+
const reset = () => {
|
|
43
|
+
cached.value = undefined;
|
|
44
|
+
error.value = undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const value = computed<T>({
|
|
48
|
+
get() {
|
|
49
|
+
if (cached.value !== undefined) {
|
|
50
|
+
return cached.value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return model.value;
|
|
54
|
+
},
|
|
55
|
+
set(value) {
|
|
56
|
+
reset();
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const newValue = parse ? parse(value) : value;
|
|
60
|
+
|
|
61
|
+
const shouldUpdate = update ? update(newValue) : true;
|
|
62
|
+
|
|
63
|
+
if (shouldUpdate) {
|
|
64
|
+
model.value = newValue;
|
|
65
|
+
} else {
|
|
66
|
+
cached.value = newValue;
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
cached.value = value;
|
|
70
|
+
error.value = err instanceof Error ? err.message : String(err);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return reactive({
|
|
76
|
+
value,
|
|
77
|
+
error,
|
|
78
|
+
reset,
|
|
79
|
+
}) as UnwrapNestedRefs<{ value: T; error?: string; reset: () => void }>;
|
|
80
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export * from './components/PlBtnSecondary';
|
|
|
26
26
|
export * from './components/PlBtnGhost';
|
|
27
27
|
export * from './components/PlBtnLink';
|
|
28
28
|
export * from './components/PlBtnGroup';
|
|
29
|
+
export * from './components/PlEditableTitle';
|
|
29
30
|
export * from './components/PlTextField';
|
|
30
31
|
export * from './components/PlTextArea';
|
|
31
32
|
export * from './components/PlDropdown';
|
|
@@ -19,7 +19,7 @@ defineProps<{
|
|
|
19
19
|
<template>
|
|
20
20
|
<div class="pl-layout-component pl-block-page" :class="{ noBodyGutters }">
|
|
21
21
|
<div v-if="slots.title" class="pl-block-page__title">
|
|
22
|
-
<
|
|
22
|
+
<div class="pl-block-page__title__default"><slot name="title" /></div>
|
|
23
23
|
<div class="pl-block-page__title__append">
|
|
24
24
|
<slot name="append" />
|
|
25
25
|
</div>
|
|
@@ -21,13 +21,17 @@
|
|
|
21
21
|
gap: 12px;
|
|
22
22
|
padding: 20px 24px;
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
&__default {
|
|
25
25
|
margin: 0;
|
|
26
26
|
color: var(--txt-01);
|
|
27
27
|
font-size: 28px;
|
|
28
28
|
font-weight: 500;
|
|
29
29
|
line-height: 32px;
|
|
30
30
|
letter-spacing: -0.56px;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: row;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 12px;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
&__append {
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImportFileHandle, Platforma, StorageHandle,
|
|
1
|
+
import type { ImportFileHandle, Platforma, StorageHandle, PlRef as ModelRef } from '@platforma-sdk/model';
|
|
2
2
|
import type { Ref, ComputedRef } from 'vue';
|
|
3
3
|
import { maskIcons16 } from './generated/icons-16';
|
|
4
4
|
import { maskIcons24 } from './generated/icons-24';
|