@nexxtmove/ui 1.38.0 → 1.40.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/components/editor/molecules/EditorAlignItems/EditorAlignItems.vue.d.ts +18 -3
- package/dist/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.vue.d.ts +15 -3
- package/dist/components/editor/molecules/EditorBorder/EditorBorder.vue.d.ts +11 -6
- package/dist/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.vue.d.ts +13 -3
- package/dist/components/editor/molecules/EditorGap/EditorGap.vue.d.ts +55 -6
- package/dist/components/editor/molecules/EditorHeadingType/EditorHeadingType.vue.d.ts +16 -12
- package/dist/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.vue.d.ts +18 -3
- package/dist/components/editor/molecules/EditorPadding/EditorPadding.vue.d.ts +106 -0
- package/dist/components/editor/molecules/EditorTextAlign/EditorTextAlign.vue.d.ts +18 -3
- package/dist/components/organisms/ComboboxPanel/_ComboboxPanelOptions.vue.d.ts +24 -0
- package/dist/components/organisms/ComboboxPanel/combobox-types.d.ts +17 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +5079 -4894
- package/package.json +1 -1
- package/src/components/editor/molecules/EditorAlignItems/EditorAlignItems.vue +35 -11
- package/src/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.vue +33 -6
- package/src/components/editor/molecules/EditorBorder/EditorBorder.vue +19 -12
- package/src/components/editor/molecules/EditorFlexDirection/EditorFlexDirection.vue +40 -5
- package/src/components/editor/molecules/EditorGap/EditorGap.vue +30 -15
- package/src/components/editor/molecules/EditorHeadingType/EditorHeadingType.vue +19 -15
- package/src/components/editor/molecules/EditorJustifyContent/EditorJustifyContent.vue +35 -15
- package/src/components/editor/molecules/EditorPadding/EditorPadding.vue +61 -19
- package/src/components/editor/molecules/EditorTextAlign/EditorTextAlign.vue +35 -7
- package/src/components/organisms/ComboboxPanel/ComboboxPanel.vue +58 -46
- package/src/components/organisms/ComboboxPanel/_ComboboxPanelOptions.vue +96 -0
- package/src/components/organisms/ComboboxPanel/combobox-types.ts +19 -0
- package/src/index.ts +14 -1
package/package.json
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The options the editor offers, in the order they are shown. `token` is the
|
|
4
|
+
* class without its fixed `items-` prefix, so the class is `items-` plus the
|
|
5
|
+
* token. The labels are not in here; they arrive through `messages`, keyed by
|
|
6
|
+
* token.
|
|
7
|
+
*/
|
|
8
|
+
export const EDITOR_ALIGN_ITEMS_OPTIONS = [
|
|
9
|
+
{ token: 'start' },
|
|
10
|
+
{ token: 'center' },
|
|
11
|
+
{ token: 'end' },
|
|
12
|
+
{ token: 'stretch' },
|
|
13
|
+
] as const
|
|
14
|
+
|
|
2
15
|
/**
|
|
3
16
|
* The cross-axis alignment as the editor needs it: the Tailwind class itself,
|
|
4
17
|
* so the consumer can put the model value straight on the element it styles.
|
|
5
18
|
*/
|
|
6
|
-
export type EditorAlignItemsValue =
|
|
19
|
+
export type EditorAlignItemsValue = `items-${(typeof EDITOR_ALIGN_ITEMS_OPTIONS)[number]['token']}`
|
|
7
20
|
|
|
8
21
|
/** Overridable copy for the four options. Every key is optional. */
|
|
9
22
|
export interface EditorAlignItemsMessages {
|
|
@@ -64,16 +77,27 @@ const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
|
64
77
|
// the spacing icons depict gaps between separate objects, which is the
|
|
65
78
|
// justify-between metaphor, not this one. The copy travels as `ariaLabel`, so
|
|
66
79
|
// the buttons still have a name for assistive technology.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
/**
|
|
81
|
+
* The icon of each option. Keyed on the token, so a token added to the list
|
|
82
|
+
* without an icon is a type error.
|
|
83
|
+
*/
|
|
84
|
+
const ICONS: Record<
|
|
85
|
+
(typeof EDITOR_ALIGN_ITEMS_OPTIONS)[number]['token'],
|
|
86
|
+
NonNullable<NexxtToggleGroupOption['icon']>
|
|
87
|
+
> = {
|
|
88
|
+
start: 'objects-align-top',
|
|
89
|
+
center: 'objects-align-center-vertical',
|
|
90
|
+
end: 'objects-align-bottom',
|
|
91
|
+
stretch: 'arrows-up-down',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const options = computed<NexxtToggleGroupOption[]>(() =>
|
|
95
|
+
EDITOR_ALIGN_ITEMS_OPTIONS.map(({ token }) => ({
|
|
96
|
+
value: `items-${token}`,
|
|
97
|
+
icon: ICONS[token],
|
|
98
|
+
ariaLabel: copy.value[token],
|
|
99
|
+
})),
|
|
100
|
+
)
|
|
77
101
|
|
|
78
102
|
// ToggleGroup types its model as the open `string` of its options, so writing
|
|
79
103
|
// straight back into the narrower model is a type error. The setter is the one
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The options the editor offers, in the order they are shown. `token` is the
|
|
4
|
+
* class without its fixed `bg-` prefix, so the class is `bg-` plus the token.
|
|
5
|
+
* The labels are not in here; they arrive through `messages`, keyed by token.
|
|
6
|
+
*/
|
|
7
|
+
export const EDITOR_BACKGROUND_SIZE_OPTIONS = [
|
|
8
|
+
{ token: 'cover' },
|
|
9
|
+
{ token: 'contain' },
|
|
10
|
+
{ token: 'auto' },
|
|
11
|
+
] as const
|
|
12
|
+
|
|
2
13
|
/**
|
|
3
14
|
* The background size as the editor needs it: the Tailwind class itself, so the
|
|
4
15
|
* consumer can put the model value straight on the element it styles.
|
|
5
16
|
*/
|
|
6
|
-
export type EditorBackgroundSizeValue =
|
|
17
|
+
export type EditorBackgroundSizeValue =
|
|
18
|
+
`bg-${(typeof EDITOR_BACKGROUND_SIZE_OPTIONS)[number]['token']}`
|
|
7
19
|
|
|
8
20
|
/** Overridable copy for the three options. Every key is optional. */
|
|
9
21
|
export interface EditorBackgroundSizeMessages {
|
|
@@ -58,11 +70,26 @@ const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
|
58
70
|
// options do not fit the width the editor sidebar gives this block. The copy
|
|
59
71
|
// travels as `ariaLabel`, so the buttons still have a name for assistive
|
|
60
72
|
// technology.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
/**
|
|
74
|
+
* The icon of each option. Keyed on the token, so a token added to the list
|
|
75
|
+
* without an icon is a type error.
|
|
76
|
+
*/
|
|
77
|
+
const ICONS: Record<
|
|
78
|
+
(typeof EDITOR_BACKGROUND_SIZE_OPTIONS)[number]['token'],
|
|
79
|
+
NonNullable<NexxtToggleGroupOption['icon']>
|
|
80
|
+
> = {
|
|
81
|
+
cover: 'arrows-maximize',
|
|
82
|
+
contain: 'arrows-minimize',
|
|
83
|
+
auto: 'image',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const options = computed<NexxtToggleGroupOption[]>(() =>
|
|
87
|
+
EDITOR_BACKGROUND_SIZE_OPTIONS.map(({ token }) => ({
|
|
88
|
+
value: `bg-${token}`,
|
|
89
|
+
icon: ICONS[token],
|
|
90
|
+
ariaLabel: copy.value[token],
|
|
91
|
+
})),
|
|
92
|
+
)
|
|
66
93
|
|
|
67
94
|
// ToggleGroup types its model as the open `string` of its options, so writing
|
|
68
95
|
// straight back into the narrower model is a type error. The setter is the one
|
|
@@ -5,16 +5,21 @@
|
|
|
5
5
|
* rather than theme tokens -- the theme defines no border-width scale, so
|
|
6
6
|
* stepping over arbitrary integers would produce classes with no CSS behind
|
|
7
7
|
* them.
|
|
8
|
+
*
|
|
9
|
+
* `token` is the full width class, not a suffix: the 1px step is the bare
|
|
10
|
+
* `border`, which has none. The model value pairs it with an optional colour
|
|
11
|
+
* class; a value without a width class on the list puts the slider on the first
|
|
12
|
+
* step.
|
|
8
13
|
*/
|
|
9
|
-
const
|
|
10
|
-
{
|
|
11
|
-
{
|
|
12
|
-
{
|
|
13
|
-
{
|
|
14
|
+
export const EDITOR_BORDER_STEPS = [
|
|
15
|
+
{ token: 'border', px: 1 },
|
|
16
|
+
{ token: 'border-2', px: 2 },
|
|
17
|
+
{ token: 'border-4', px: 4 },
|
|
18
|
+
{ token: 'border-8', px: 8 },
|
|
14
19
|
] as const
|
|
15
20
|
|
|
16
21
|
/** The width part of the model value, as the Tailwind class itself. */
|
|
17
|
-
export type EditorBorderWidth = (typeof
|
|
22
|
+
export type EditorBorderWidth = (typeof EDITOR_BORDER_STEPS)[number]['token']
|
|
18
23
|
|
|
19
24
|
/** Overridable copy of the readout and the picker. Every key is optional. */
|
|
20
25
|
export interface EditorBorderMessages {
|
|
@@ -94,7 +99,7 @@ const enabled = defineModel<boolean>('enabled', { default: true })
|
|
|
94
99
|
|
|
95
100
|
const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
96
101
|
|
|
97
|
-
const maxIndex =
|
|
102
|
+
const maxIndex = EDITOR_BORDER_STEPS.length - 1
|
|
98
103
|
|
|
99
104
|
/** The model read as a token list; whitespace of any shape separates them. */
|
|
100
105
|
const tokens = computed(() => model.value.split(/\s+/).filter(Boolean))
|
|
@@ -107,11 +112,13 @@ const tokens = computed(() => model.value.split(/\s+/).filter(Boolean))
|
|
|
107
112
|
* first change writes a valid class back.
|
|
108
113
|
*/
|
|
109
114
|
const stepIndex = computed(() => {
|
|
110
|
-
const widthToken = tokens.value.find((token) =>
|
|
115
|
+
const widthToken = tokens.value.find((token) =>
|
|
116
|
+
EDITOR_BORDER_STEPS.some((step) => step.token === token),
|
|
117
|
+
)
|
|
111
118
|
|
|
112
119
|
return Math.max(
|
|
113
120
|
0,
|
|
114
|
-
|
|
121
|
+
EDITOR_BORDER_STEPS.findIndex((step) => step.token === widthToken),
|
|
115
122
|
)
|
|
116
123
|
})
|
|
117
124
|
|
|
@@ -143,7 +150,7 @@ const write = (width: EditorBorderWidth, color: string) => {
|
|
|
143
150
|
const sliderValue = computed({
|
|
144
151
|
get: () => [stepIndex.value],
|
|
145
152
|
set: ([index]) => {
|
|
146
|
-
write(
|
|
153
|
+
write(EDITOR_BORDER_STEPS[index].token, hex.value)
|
|
147
154
|
},
|
|
148
155
|
})
|
|
149
156
|
|
|
@@ -153,11 +160,11 @@ const picked = computed({
|
|
|
153
160
|
// would still wipe a perfectly good colour, so it is dropped rather than
|
|
154
161
|
// written through. The picker keeps rendering the model's colour.
|
|
155
162
|
set: (value: string) => {
|
|
156
|
-
if (isHex(value)) write(
|
|
163
|
+
if (isHex(value)) write(EDITOR_BORDER_STEPS[stepIndex.value].token, value)
|
|
157
164
|
},
|
|
158
165
|
})
|
|
159
166
|
|
|
160
|
-
const displayValue = computed(() => `${
|
|
167
|
+
const displayValue = computed(() => `${EDITOR_BORDER_STEPS[stepIndex.value].px} ${copy.value.unit}`)
|
|
161
168
|
</script>
|
|
162
169
|
|
|
163
170
|
<template>
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The options the editor offers, in the order they are shown. `token` is the
|
|
4
|
+
* class without its fixed `flex-` prefix, so the class is `flex-` plus the
|
|
5
|
+
* token. The labels are not in here; they arrive through `messages`.
|
|
6
|
+
*/
|
|
7
|
+
export const EDITOR_FLEX_DIRECTION_OPTIONS = [{ token: 'row' }, { token: 'col' }] as const
|
|
8
|
+
|
|
2
9
|
/**
|
|
3
10
|
* The flex direction as the editor needs it: the Tailwind class itself, so the
|
|
4
11
|
* consumer can put the model value straight on the element it styles.
|
|
5
12
|
*/
|
|
6
|
-
export type EditorFlexDirectionValue =
|
|
13
|
+
export type EditorFlexDirectionValue =
|
|
14
|
+
`flex-${(typeof EDITOR_FLEX_DIRECTION_OPTIONS)[number]['token']}`
|
|
7
15
|
|
|
8
16
|
/** Overridable copy for the two options. Every key is optional. */
|
|
9
17
|
export interface EditorFlexDirectionMessages {
|
|
@@ -49,10 +57,37 @@ const model = defineModel<EditorFlexDirectionValue>({ required: true })
|
|
|
49
57
|
|
|
50
58
|
const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The icon of each option. Keyed on the token, so a token added to the list
|
|
62
|
+
* without an icon is a type error.
|
|
63
|
+
*/
|
|
64
|
+
const ICONS: Record<
|
|
65
|
+
(typeof EDITOR_FLEX_DIRECTION_OPTIONS)[number]['token'],
|
|
66
|
+
NonNullable<NexxtToggleGroupOption['icon']>
|
|
67
|
+
> = {
|
|
68
|
+
row: 'arrow-right',
|
|
69
|
+
col: 'arrow-down',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The messages key of each option: the copy names the direction, not the
|
|
74
|
+
* class.
|
|
75
|
+
*/
|
|
76
|
+
const MESSAGE_KEYS: Record<
|
|
77
|
+
(typeof EDITOR_FLEX_DIRECTION_OPTIONS)[number]['token'],
|
|
78
|
+
keyof EditorFlexDirectionMessages
|
|
79
|
+
> = {
|
|
80
|
+
row: 'horizontal',
|
|
81
|
+
col: 'vertical',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const options = computed<NexxtToggleGroupOption[]>(() =>
|
|
85
|
+
EDITOR_FLEX_DIRECTION_OPTIONS.map(({ token }) => ({
|
|
86
|
+
value: `flex-${token}`,
|
|
87
|
+
icon: ICONS[token],
|
|
88
|
+
label: copy.value[MESSAGE_KEYS[token]],
|
|
89
|
+
})),
|
|
90
|
+
)
|
|
56
91
|
|
|
57
92
|
// ToggleGroup types its model as the open `string` of its options, so writing
|
|
58
93
|
// straight back into the narrower model is a type error. The setter is the one
|
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* The steps the slider can land on,
|
|
4
|
-
* 13 and 15 are missing on purpose:
|
|
5
|
-
* `--spacing-12` and then only 14 and
|
|
6
|
-
* of over this list would offer gaps
|
|
3
|
+
* The steps the slider can land on, with the px they render (the spacing scale
|
|
4
|
+
* is built on 0.25rem, so a step is 4 px). 13 and 15 are missing on purpose:
|
|
5
|
+
* the theme defines `--spacing-0` through `--spacing-12` and then only 14 and
|
|
6
|
+
* 16, so stepping over the px value instead of over this list would offer gaps
|
|
7
|
+
* that have no token behind them.
|
|
8
|
+
*
|
|
9
|
+
* `token` is the numeric part of the class; the class is `gap-` plus the token.
|
|
10
|
+
* A stored value that is not on the list puts the slider on the first step.
|
|
7
11
|
*/
|
|
8
|
-
const
|
|
12
|
+
export const EDITOR_GAP_STEPS = [
|
|
13
|
+
{ token: 0, px: 0 },
|
|
14
|
+
{ token: 1, px: 4 },
|
|
15
|
+
{ token: 2, px: 8 },
|
|
16
|
+
{ token: 3, px: 12 },
|
|
17
|
+
{ token: 4, px: 16 },
|
|
18
|
+
{ token: 5, px: 20 },
|
|
19
|
+
{ token: 6, px: 24 },
|
|
20
|
+
{ token: 7, px: 28 },
|
|
21
|
+
{ token: 8, px: 32 },
|
|
22
|
+
{ token: 9, px: 36 },
|
|
23
|
+
{ token: 10, px: 40 },
|
|
24
|
+
{ token: 11, px: 44 },
|
|
25
|
+
{ token: 12, px: 48 },
|
|
26
|
+
{ token: 14, px: 56 },
|
|
27
|
+
{ token: 16, px: 64 },
|
|
28
|
+
] as const
|
|
9
29
|
|
|
10
30
|
/**
|
|
11
31
|
* The gap as the editor needs it: the Tailwind class itself, so the consumer
|
|
12
32
|
* can put the model value straight on the element it styles.
|
|
13
33
|
*/
|
|
14
|
-
export type EditorGapValue = `gap-${(typeof
|
|
34
|
+
export type EditorGapValue = `gap-${(typeof EDITOR_GAP_STEPS)[number]['token']}`
|
|
15
35
|
|
|
16
36
|
/** Overridable copy of the value readout. Every key is optional. */
|
|
17
37
|
export interface EditorGapMessages {
|
|
@@ -36,9 +56,6 @@ export interface NexxtEditorGapProps {
|
|
|
36
56
|
const DEFAULT_MESSAGES = {
|
|
37
57
|
unit: 'px',
|
|
38
58
|
} satisfies Required<EditorGapMessages>
|
|
39
|
-
|
|
40
|
-
/** The spacing scale is built on 0.25rem, so `gap-4` is 4 × 4 = 16 px. */
|
|
41
|
-
const PX_PER_STEP = 4
|
|
42
59
|
</script>
|
|
43
60
|
|
|
44
61
|
<script lang="ts" setup>
|
|
@@ -55,7 +72,7 @@ const model = defineModel<EditorGapValue>({ required: true })
|
|
|
55
72
|
|
|
56
73
|
const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
57
74
|
|
|
58
|
-
const maxIndex =
|
|
75
|
+
const maxIndex = EDITOR_GAP_STEPS.length - 1
|
|
59
76
|
|
|
60
77
|
/**
|
|
61
78
|
* Position of the current value on the step list. A value that is not on the
|
|
@@ -66,7 +83,7 @@ const maxIndex = GAP_STEPS.length - 1
|
|
|
66
83
|
const stepIndex = computed(() =>
|
|
67
84
|
Math.max(
|
|
68
85
|
0,
|
|
69
|
-
|
|
86
|
+
EDITOR_GAP_STEPS.findIndex(({ token }) => `gap-${token}` === model.value),
|
|
70
87
|
),
|
|
71
88
|
)
|
|
72
89
|
|
|
@@ -75,13 +92,11 @@ const stepIndex = computed(() =>
|
|
|
75
92
|
const sliderValue = computed({
|
|
76
93
|
get: () => [stepIndex.value],
|
|
77
94
|
set: ([index]) => {
|
|
78
|
-
model.value = `gap-${
|
|
95
|
+
model.value = `gap-${EDITOR_GAP_STEPS[index].token}`
|
|
79
96
|
},
|
|
80
97
|
})
|
|
81
98
|
|
|
82
|
-
const displayValue = computed(
|
|
83
|
-
() => `${GAP_STEPS[stepIndex.value] * PX_PER_STEP} ${copy.value.unit}`,
|
|
84
|
-
)
|
|
99
|
+
const displayValue = computed(() => `${EDITOR_GAP_STEPS[stepIndex.value].px} ${copy.value.unit}`)
|
|
85
100
|
</script>
|
|
86
101
|
|
|
87
102
|
<template>
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* The heading levels the editor offers, in the order they are listed. The level
|
|
4
|
-
* is what the option shows -- its label and its `h1`..`h6` icon -- and the
|
|
5
|
-
* is what the model carries. The sizes are Tailwind's default scale on
|
|
6
|
-
* not the theme's `heading-*` utilities: this value is data the editor
|
|
7
|
-
* and puts back on an element, so it has to be a class every consumer
|
|
4
|
+
* is what the option shows -- its label and its `h1`..`h6` icon -- and the
|
|
5
|
+
* token is what the model carries. The sizes are Tailwind's default scale on
|
|
6
|
+
* purpose, not the theme's `heading-*` utilities: this value is data the editor
|
|
7
|
+
* stores and puts back on an element, so it has to be a class every consumer
|
|
8
|
+
* resolves.
|
|
9
|
+
*
|
|
10
|
+
* `token` is the full font-size class, not a suffix: it is stored as is. The
|
|
11
|
+
* labels are not in here; they arrive through `messages`, keyed by level.
|
|
8
12
|
*/
|
|
9
|
-
const
|
|
10
|
-
{
|
|
11
|
-
{
|
|
12
|
-
{
|
|
13
|
-
{
|
|
14
|
-
{
|
|
15
|
-
{
|
|
13
|
+
export const EDITOR_HEADING_TYPE_OPTIONS = [
|
|
14
|
+
{ token: 'text-4xl', level: 1 },
|
|
15
|
+
{ token: 'text-3xl', level: 2 },
|
|
16
|
+
{ token: 'text-2xl', level: 3 },
|
|
17
|
+
{ token: 'text-xl', level: 4 },
|
|
18
|
+
{ token: 'text-lg', level: 5 },
|
|
19
|
+
{ token: 'text-base', level: 6 },
|
|
16
20
|
] as const
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -20,7 +24,7 @@ const HEADING_LEVELS = [
|
|
|
20
24
|
* the consumer can put the model value straight on the element it styles.
|
|
21
25
|
* Weight is deliberately not part of it -- that becomes a setting of its own.
|
|
22
26
|
*/
|
|
23
|
-
export type EditorHeadingTypeValue = (typeof
|
|
27
|
+
export type EditorHeadingTypeValue = (typeof EDITOR_HEADING_TYPE_OPTIONS)[number]['token']
|
|
24
28
|
|
|
25
29
|
/** Overridable copy for the six options. Every key is optional. */
|
|
26
30
|
export interface EditorHeadingTypeMessages {
|
|
@@ -82,13 +86,13 @@ const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
|
82
86
|
// The level doubles as the messages key and the Font Awesome icon name --
|
|
83
87
|
// `h1` through `h6` all exist in the kit.
|
|
84
88
|
const options = computed<SelectOption[]>(() =>
|
|
85
|
-
|
|
89
|
+
EDITOR_HEADING_TYPE_OPTIONS.map(({ token, level }) => {
|
|
86
90
|
const key = `h${level}` as const
|
|
87
|
-
return { value:
|
|
91
|
+
return { value: token, label: copy.value[key], icon: key }
|
|
88
92
|
}),
|
|
89
93
|
)
|
|
90
94
|
|
|
91
|
-
const values =
|
|
95
|
+
const values = EDITOR_HEADING_TYPE_OPTIONS.map(({ token }) => token)
|
|
92
96
|
|
|
93
97
|
// Select types its model as the open `string | number | null` of its options,
|
|
94
98
|
// so writing straight back into the narrower model is a type error. The setter
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The options the editor offers, in the order they are shown. `token` is the
|
|
4
|
+
* class without its fixed `justify-` prefix, so the class is `justify-` plus
|
|
5
|
+
* the token. The labels are not in here; they arrive through `messages`, keyed
|
|
6
|
+
* by token.
|
|
7
|
+
*/
|
|
8
|
+
export const EDITOR_JUSTIFY_CONTENT_OPTIONS = [
|
|
9
|
+
{ token: 'start' },
|
|
10
|
+
{ token: 'center' },
|
|
11
|
+
{ token: 'end' },
|
|
12
|
+
{ token: 'between' },
|
|
13
|
+
] as const
|
|
14
|
+
|
|
2
15
|
/**
|
|
3
16
|
* The justification as the editor needs it: the Tailwind class itself, so the
|
|
4
17
|
* consumer can put the model value straight on the element it styles.
|
|
5
18
|
*/
|
|
6
19
|
export type EditorJustifyContentValue =
|
|
7
|
-
|
|
20
|
+
`justify-${(typeof EDITOR_JUSTIFY_CONTENT_OPTIONS)[number]['token']}`
|
|
8
21
|
|
|
9
22
|
/** Overridable copy for the four options. Every key is optional. */
|
|
10
23
|
export interface EditorJustifyContentMessages {
|
|
@@ -61,20 +74,27 @@ const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
|
61
74
|
// side at the width the editor sidebar gives this block, and the icons are
|
|
62
75
|
// conventional enough to stand on their own. The copy travels as `ariaLabel`,
|
|
63
76
|
// so the buttons still have a name for assistive technology.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The icon of each option. Keyed on the token, so a token added to the list
|
|
79
|
+
* without an icon is a type error.
|
|
80
|
+
*/
|
|
81
|
+
const ICONS: Record<
|
|
82
|
+
(typeof EDITOR_JUSTIFY_CONTENT_OPTIONS)[number]['token'],
|
|
83
|
+
NonNullable<NexxtToggleGroupOption['icon']>
|
|
84
|
+
> = {
|
|
85
|
+
start: 'objects-align-left',
|
|
86
|
+
center: 'objects-align-center-horizontal',
|
|
87
|
+
end: 'objects-align-right',
|
|
88
|
+
between: 'distribute-spacing-horizontal',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const options = computed<NexxtToggleGroupOption[]>(() =>
|
|
92
|
+
EDITOR_JUSTIFY_CONTENT_OPTIONS.map(({ token }) => ({
|
|
93
|
+
value: `justify-${token}`,
|
|
94
|
+
icon: ICONS[token],
|
|
95
|
+
ariaLabel: copy.value[token],
|
|
96
|
+
})),
|
|
97
|
+
)
|
|
78
98
|
|
|
79
99
|
// ToggleGroup types its model as the open `string` of its options, so writing
|
|
80
100
|
// straight back into the narrower model is a type error. The setter is the one
|
|
@@ -2,22 +2,56 @@
|
|
|
2
2
|
import type { PaddingIconSide } from './_PaddingIcon.vue'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The steps a field can land on
|
|
6
|
-
*
|
|
7
|
-
* climbs -- 13 and 15 are absent, and
|
|
8
|
-
*
|
|
9
|
-
* field off classes that have no token
|
|
5
|
+
* The steps a field can land on: every integer `--spacing-*` token the theme
|
|
6
|
+
* defines, with the px it renders (the spacing scale is built on 0.25rem, so a
|
|
7
|
+
* step is 4 px). The list thins out as it climbs -- 13 and 15 are absent, and
|
|
8
|
+
* past 16 it moves in fours and then eights -- so snapping over this list
|
|
9
|
+
* rather than over the px value keeps the field off classes that have no token
|
|
10
|
+
* behind them.
|
|
11
|
+
*
|
|
12
|
+
* `token` is the numeric part of the class, without a prefix: one padding value
|
|
13
|
+
* is written with several of them (`p-`, `px-`, `py-`, `pt-`, `pr-`, `pb-`,
|
|
14
|
+
* `pl-`), so the full class depends on which sides it covers. A value between
|
|
15
|
+
* two steps snaps to the nearest one, the lower on a tie.
|
|
10
16
|
*/
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
export const EDITOR_PADDING_STEPS = [
|
|
18
|
+
{ token: 0, px: 0 },
|
|
19
|
+
{ token: 1, px: 4 },
|
|
20
|
+
{ token: 2, px: 8 },
|
|
21
|
+
{ token: 3, px: 12 },
|
|
22
|
+
{ token: 4, px: 16 },
|
|
23
|
+
{ token: 5, px: 20 },
|
|
24
|
+
{ token: 6, px: 24 },
|
|
25
|
+
{ token: 7, px: 28 },
|
|
26
|
+
{ token: 8, px: 32 },
|
|
27
|
+
{ token: 9, px: 36 },
|
|
28
|
+
{ token: 10, px: 40 },
|
|
29
|
+
{ token: 11, px: 44 },
|
|
30
|
+
{ token: 12, px: 48 },
|
|
31
|
+
{ token: 14, px: 56 },
|
|
32
|
+
{ token: 16, px: 64 },
|
|
33
|
+
{ token: 20, px: 80 },
|
|
34
|
+
{ token: 24, px: 96 },
|
|
35
|
+
{ token: 28, px: 112 },
|
|
36
|
+
{ token: 32, px: 128 },
|
|
37
|
+
{ token: 36, px: 144 },
|
|
38
|
+
{ token: 40, px: 160 },
|
|
39
|
+
{ token: 44, px: 176 },
|
|
40
|
+
{ token: 48, px: 192 },
|
|
41
|
+
{ token: 52, px: 208 },
|
|
42
|
+
{ token: 56, px: 224 },
|
|
43
|
+
{ token: 60, px: 240 },
|
|
44
|
+
{ token: 64, px: 256 },
|
|
45
|
+
{ token: 72, px: 288 },
|
|
46
|
+
{ token: 80, px: 320 },
|
|
47
|
+
{ token: 96, px: 384 },
|
|
14
48
|
] as const
|
|
15
49
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
50
|
+
/** A padding step as the numeric part of the Tailwind class, as in `p-4`. */
|
|
51
|
+
export type EditorPaddingStep = (typeof EDITOR_PADDING_STEPS)[number]['token']
|
|
18
52
|
|
|
19
53
|
/** Highest value a field takes, in px: the top step is `p-96`, which is 384 px. */
|
|
20
|
-
const MAX_PX =
|
|
54
|
+
const MAX_PX = EDITOR_PADDING_STEPS[EDITOR_PADDING_STEPS.length - 1].px
|
|
21
55
|
|
|
22
56
|
const ALL_SIDES: PaddingIconSide[] = ['top', 'right', 'bottom', 'left']
|
|
23
57
|
|
|
@@ -103,7 +137,7 @@ const parse = (value: string): PaddingSides => {
|
|
|
103
137
|
if (!match) continue
|
|
104
138
|
|
|
105
139
|
const step = Number(match[2])
|
|
106
|
-
if (!
|
|
140
|
+
if (!EDITOR_PADDING_STEPS.some(({ token }) => token === step)) continue
|
|
107
141
|
|
|
108
142
|
for (const side of PREFIX_SIDES[match[1]]) sides[side] = step
|
|
109
143
|
}
|
|
@@ -126,10 +160,18 @@ const serialize = ({ top, right, bottom, left }: PaddingSides): string => {
|
|
|
126
160
|
const snap = (px: number): number => {
|
|
127
161
|
const clamped = Math.min(Math.max(px, 0), MAX_PX)
|
|
128
162
|
|
|
129
|
-
return
|
|
130
|
-
Math.abs(step
|
|
131
|
-
)
|
|
163
|
+
return EDITOR_PADDING_STEPS.reduce((best, step) =>
|
|
164
|
+
Math.abs(step.px - clamped) < Math.abs(best.px - clamped) ? step : best,
|
|
165
|
+
).token
|
|
132
166
|
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The px a step renders. Every side only ever holds a token from the list --
|
|
170
|
+
* `parse` skips the rest and every write goes through `snap` or the list -- so
|
|
171
|
+
* the lookup always hits; the 0 is there for the type, not for a real case.
|
|
172
|
+
*/
|
|
173
|
+
const pxOf = (step: number): number =>
|
|
174
|
+
EDITOR_PADDING_STEPS.find(({ token }) => token === step)?.px ?? 0
|
|
133
175
|
</script>
|
|
134
176
|
|
|
135
177
|
<script lang="ts" setup>
|
|
@@ -215,7 +257,7 @@ const secondaryInert = computed(() => (expanded.value ? undefined : true))
|
|
|
215
257
|
// The fields speak px, not steps: `p-6` reads as 24. A 0 is shown as an empty
|
|
216
258
|
// field so the placeholder carries it, and an empty field commits back as a 0.
|
|
217
259
|
const display = (side: PaddingIconSide) =>
|
|
218
|
-
sides.value[side] === 0 ? '' : String(sides.value[side]
|
|
260
|
+
sides.value[side] === 0 ? '' : String(pxOf(sides.value[side]))
|
|
219
261
|
|
|
220
262
|
const apply = (next: PaddingSides) => {
|
|
221
263
|
// A change that changes nothing -- committing 30 where the value is already
|
|
@@ -239,11 +281,11 @@ const onCommit = (field: PaddingField, px: number) => write(field, snap(px))
|
|
|
239
281
|
|
|
240
282
|
/** One step along the list, so the arrow keys never stop on a missing token. */
|
|
241
283
|
const onStep = (field: PaddingField, direction: 1 | -1) => {
|
|
242
|
-
const index =
|
|
284
|
+
const index = EDITOR_PADDING_STEPS.findIndex(({ token }) => token === sides.value[field.source])
|
|
243
285
|
// No wrapping: the ends of the list are the ends of the range.
|
|
244
|
-
const next = Math.min(Math.max(index + direction, 0),
|
|
286
|
+
const next = Math.min(Math.max(index + direction, 0), EDITOR_PADDING_STEPS.length - 1)
|
|
245
287
|
|
|
246
|
-
write(field,
|
|
288
|
+
write(field, EDITOR_PADDING_STEPS[next].token)
|
|
247
289
|
}
|
|
248
290
|
|
|
249
291
|
/**
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The options the editor offers, in the order they are shown. `token` is the
|
|
4
|
+
* class without its fixed `text-` prefix, so the class is `text-` plus the
|
|
5
|
+
* token. The labels are not in here; they arrive through `messages`, keyed by
|
|
6
|
+
* token.
|
|
7
|
+
*/
|
|
8
|
+
export const EDITOR_TEXT_ALIGN_OPTIONS = [
|
|
9
|
+
{ token: 'left' },
|
|
10
|
+
{ token: 'center' },
|
|
11
|
+
{ token: 'right' },
|
|
12
|
+
{ token: 'justify' },
|
|
13
|
+
] as const
|
|
14
|
+
|
|
2
15
|
/**
|
|
3
16
|
* The text alignment as the editor needs it: the Tailwind class itself, so the
|
|
4
17
|
* consumer can put the model value straight on the element it styles.
|
|
5
18
|
*/
|
|
6
|
-
export type EditorTextAlignValue =
|
|
19
|
+
export type EditorTextAlignValue = `text-${(typeof EDITOR_TEXT_ALIGN_OPTIONS)[number]['token']}`
|
|
7
20
|
|
|
8
21
|
/** Overridable copy for the four options. Every key is optional. */
|
|
9
22
|
export interface EditorTextAlignMessages {
|
|
@@ -60,12 +73,27 @@ const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
|
60
73
|
// side at the width the editor sidebar gives this block, and the icons are
|
|
61
74
|
// conventional enough to stand on their own. The copy travels as `ariaLabel`,
|
|
62
75
|
// so the buttons still have a name for assistive technology.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
]
|
|
76
|
+
/**
|
|
77
|
+
* The icon of each option. Keyed on the token, so a token added to the list
|
|
78
|
+
* without an icon is a type error.
|
|
79
|
+
*/
|
|
80
|
+
const ICONS: Record<
|
|
81
|
+
(typeof EDITOR_TEXT_ALIGN_OPTIONS)[number]['token'],
|
|
82
|
+
NonNullable<NexxtToggleGroupOption['icon']>
|
|
83
|
+
> = {
|
|
84
|
+
left: 'align-left',
|
|
85
|
+
center: 'align-center',
|
|
86
|
+
right: 'align-right',
|
|
87
|
+
justify: 'align-justify',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const options = computed<NexxtToggleGroupOption[]>(() =>
|
|
91
|
+
EDITOR_TEXT_ALIGN_OPTIONS.map(({ token }) => ({
|
|
92
|
+
value: `text-${token}`,
|
|
93
|
+
icon: ICONS[token],
|
|
94
|
+
ariaLabel: copy.value[token],
|
|
95
|
+
})),
|
|
96
|
+
)
|
|
69
97
|
|
|
70
98
|
// ToggleGroup types its model as the open `string` of its options, so writing
|
|
71
99
|
// straight back into the narrower model is a type error. The setter is the one
|