@webx-ui/module-catalog-properties 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 webx-ui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @webx-ui/module-catalog-properties
2
+
3
+ Properties of products for the catalogue of the [WebX UI](https://github.com/webx-ui/webx-ui) admin
4
+ panel: reference books (flat or a tree, with colours and pictures), numbers with units, texts and
5
+ yes/no; sets inherited down the categories; the specifications of a product.
6
+
7
+ The other half is the Composer package `webx-ui/module-catalog-properties`, which owns the
8
+ properties, their values and intervals, the sets, the facets of the filter, the index document and
9
+ the storefront's specifications. The section appears in the panel when both halves are installed,
10
+ beside `@webx-ui/module-catalog`.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @webx-ui/module-catalog-properties
16
+ ```
17
+
18
+ ```ts
19
+ import { createAdmin } from '@webx-ui/module-admin'
20
+ import { catalog } from '@webx-ui/module-catalog'
21
+ import { catalogProperties } from '@webx-ui/module-catalog-properties'
22
+ import { media } from '@webx-ui/module-media'
23
+ import '@webx-ui/module-catalog/style.css'
24
+ import '@webx-ui/module-catalog-properties/style.css'
25
+
26
+ createAdmin({
27
+ basePath: '/cms',
28
+ modules: [...catalog(), ...catalogProperties(), media()],
29
+ }).mount()
30
+ ```
31
+
32
+ `catalogProperties()` is one section, «Catalog» → «Dictionaries» → «Properties»:
33
+
34
+ - the list, in the order of the filter's blocks, with a filter by type and group, the number of
35
+ products on each line and «Deleted» with a restore;
36
+ - the page of one property — «Main» (only the fields its type has, and a number printed as the
37
+ site will print it), «Values» (a lazy tree with a search, colours and pictures, merging) and
38
+ «Intervals» (the steps a number is filtered by);
39
+ - the groups of the card, behind the «Groups» button.
40
+
41
+ It also draws the two tabs the server patches onto the catalogue's screens: «Properties» of a
42
+ category (its set, inherited from the categories above) and «Specifications» of a product (the set
43
+ of its main category, read again as soon as the category changes). The pictures of values come from
44
+ the library of `@webx-ui/module-media`.
45
+
46
+ The catalogue's `path` moves this section with it: `catalogProperties({ path: '/shop' })`.
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import { PropertyValue } from './types';
2
+ type __VLS_Props = {
3
+ property: number;
4
+ value: PropertyValue;
5
+ };
6
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
7
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { HeldValue } from './types';
2
+ type Values = Record<string, HeldValue>;
3
+ type __VLS_PublicProps = {
4
+ modelValue?: Values | null;
5
+ };
6
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
7
+ "update:modelValue": (value: Values | null) => any;
8
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
9
+ "onUpdate:modelValue"?: ((value: Values | null) => any) | undefined;
10
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
11
+ export default _default;
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ base?: string;
3
+ };
4
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
5
+ base: string;
6
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
7
+ export default _default;
@@ -0,0 +1,4 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ field: HTMLDivElement;
3
+ }, any>;
4
+ export default _default;
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ base?: string;
3
+ };
4
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
5
+ base: string;
6
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
7
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import { SelectOption } from '@webx-ui/core';
2
+ type __VLS_Props = {
3
+ options?: SelectOption[];
4
+ };
5
+ type __VLS_PublicProps = {
6
+ modelValue?: string | null;
7
+ } & __VLS_Props;
8
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
9
+ "update:modelValue": (value: string | null) => any;
10
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
11
+ "onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
12
+ }>, {
13
+ options: SelectOption[];
14
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
15
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import { PropertyValue } from './types';
2
+ type __VLS_Props = {
3
+ property: number;
4
+ value?: PropertyValue | null;
5
+ parent?: number | null;
6
+ title: string;
7
+ color?: boolean;
8
+ image?: boolean;
9
+ };
10
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
11
+ color: boolean;
12
+ image: boolean;
13
+ value: PropertyValue | null;
14
+ parent: number | null;
15
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
16
+ field: HTMLDivElement;
17
+ }, any>;
18
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import { ValueNode } from './types';
2
+ type __VLS_Props = {
3
+ node: ValueNode;
4
+ locale: string;
5
+ };
6
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLSpanElement>;
7
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import { PropertyRow } from './types';
2
+ type __VLS_Props = {
3
+ property: PropertyRow;
4
+ disabled?: boolean;
5
+ ariaLabel?: string;
6
+ };
7
+ type __VLS_PublicProps = {
8
+ modelValue?: number | number[] | null;
9
+ } & __VLS_Props;
10
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
11
+ "update:modelValue": (value: number | number[] | null) => any;
12
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
+ "onUpdate:modelValue"?: ((value: number | number[] | null) => any) | undefined;
14
+ }>, {
15
+ disabled: boolean;
16
+ ariaLabel: string;
17
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
18
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import { PropertyRow } from './types';
2
+ type __VLS_Props = {
3
+ property: PropertyRow;
4
+ disabled?: boolean;
5
+ ariaLabel?: string;
6
+ };
7
+ type __VLS_PublicProps = {
8
+ modelValue?: number | number[] | null;
9
+ } & __VLS_Props;
10
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
11
+ "update:modelValue": (value: number | number[] | null) => any;
12
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
+ "onUpdate:modelValue"?: ((value: number | number[] | null) => any) | undefined;
14
+ }>, {
15
+ disabled: boolean;
16
+ ariaLabel: string;
17
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
18
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
package/dist/api.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { AdminContext } from '@webx-ui/module-admin';
2
+ import { ScreenModel } from '@webx-ui/schema';
3
+ import { CategorySet, EffectiveSet, PropertiesPage, PropertyDetail, PropertyInterval, PropertyQuery, PropertyValue, Words } from './types';
4
+ /** Where the properties answer under the panel's API — `CatalogPropertiesServiceProvider::SOURCE`. */
5
+ export declare const PROPERTIES_API = "catalog/properties";
6
+ /** Where their groups do: the panel's shared category API. */
7
+ export declare const GROUPS_API = "catalog/property-groups";
8
+ /** Where the set in force for a product form is — `catalog/property-sets/{category}`. */
9
+ export declare const SETS_API = "catalog/property-sets";
10
+ /** What a value is written with; a field left out is not touched. */
11
+ export interface ValueInput {
12
+ title?: Words | string;
13
+ slug?: Words | string;
14
+ parent_id?: number | null;
15
+ color?: string | null;
16
+ image_id?: number | null;
17
+ }
18
+ export interface CatalogPropertiesApi {
19
+ list(query?: PropertyQuery): Promise<PropertiesPage>;
20
+ get(id: number): Promise<PropertyDetail>;
21
+ /** The values of `catalog.property-form`; a refused field comes back as a 422 under its name. */
22
+ create(values: ScreenModel): Promise<PropertyDetail>;
23
+ save(id: number, values: ScreenModel): Promise<PropertyDetail>;
24
+ reorder(ids: number[]): Promise<void>;
25
+ remove(id: number): Promise<void>;
26
+ restore(id: number): Promise<PropertyDetail>;
27
+ /** All the intervals at once, in order: one without an id is new, one left out is gone. */
28
+ saveIntervals(id: number, intervals: PropertyInterval[]): Promise<PropertyInterval[]>;
29
+ /** The children of `parent` (null — the top) in the book's order. */
30
+ values(id: number, parent?: number | null): Promise<PropertyValue[]>;
31
+ /** Whatever matches, at any depth — one page. */
32
+ searchValues(id: number, q: string, perPage?: number): Promise<PropertyValue[]>;
33
+ /** These values, each with the path down to it. */
34
+ valuesById(id: number, ids: number[]): Promise<PropertyValue[]>;
35
+ createValue(id: number, input: ValueInput): Promise<PropertyValue>;
36
+ saveValue(id: number, value: number, input: ValueInput): Promise<PropertyValue>;
37
+ /** Under `parent` (null — the top), before `before` (null — last). */
38
+ moveValue(id: number, value: number, parent: number | null, before: number | null): Promise<PropertyValue>;
39
+ /** `A → B`: how many products moved, or would move with `dryRun`. */
40
+ mergeValue(id: number, value: number, into: number, dryRun?: boolean): Promise<number>;
41
+ /** A 422 with `meta.products` while products hold it. */
42
+ removeValue(id: number, value: number): Promise<void>;
43
+ categorySet(category: number): Promise<CategorySet>;
44
+ saveCategorySet(category: number, ids: number[]): Promise<CategorySet>;
45
+ effectiveSet(category: number): Promise<EffectiveSet>;
46
+ }
47
+ export declare function propertySearch(query: PropertyQuery): URLSearchParams;
48
+ export declare function createPropertiesApi(admin: AdminContext): CatalogPropertiesApi;
@@ -0,0 +1,22 @@
1
+ import { InjectionKey, Ref } from 'vue';
2
+ import { ScreenModel } from '@webx-ui/schema';
3
+ import { PropertyInterval, PropertyRow } from './types';
4
+ /**
5
+ * What the property's editor knows and the nodes of `catalog.property-form` do not: which property
6
+ * it is — the values and the intervals are records of their own, saved by requests of their own —
7
+ * and what the form says right now, which the type field and the live example read.
8
+ */
9
+ export interface PropertyEditorContext {
10
+ /** The property as the server last answered it, or `null` while the first request is out. */
11
+ property: Ref<PropertyRow | null>;
12
+ /** The values of the screen as they are right now, edits included. */
13
+ values: Ref<ScreenModel>;
14
+ /** The intervals as the server last answered them; the node keeps its own edits. */
15
+ intervals: Ref<PropertyInterval[]>;
16
+ /** Closed for writing: no permission, or a save in flight. */
17
+ locked: Ref<boolean>;
18
+ }
19
+ export declare const propertyEditorKey: InjectionKey<PropertyEditorContext>;
20
+ export declare function providePropertyEditor(editor: PropertyEditorContext): void;
21
+ /** The editor above this node, or `null` outside one — a demo, a test, a stray screen. */
22
+ export declare function usePropertyEditor(): PropertyEditorContext | null;
@@ -0,0 +1,27 @@
1
+ import { PropertyRow, Words } from './types';
2
+ /**
3
+ * A map of languages in one of them, the first written one where it is empty. The server sends an
4
+ * empty map as `[]` — PHP's empty array — which reads as no words at all.
5
+ */
6
+ export declare function wordsIn(words: Words | string | undefined, locale: string, fallback?: string): string;
7
+ /** What a property is called in the panel: its name, its code, its number — in that order. */
8
+ export declare function propertyName(property: Pick<PropertyRow, 'id' | 'title' | 'code'>, locale: string): string;
9
+ export interface NumberShape {
10
+ precision?: number | null;
11
+ prefix?: string | null;
12
+ suffix?: string | null;
13
+ }
14
+ /**
15
+ * A number as the site shows it (§3.1): the property's precision, the language's decimal mark,
16
+ * the prefix and the suffix glued on as written — `M8`, `⌀12 мм`, `1,35 кг`. The same rule as
17
+ * `Property::formatNumber()` on the server, so the example in the form is what the card will say.
18
+ */
19
+ export declare function formatNumber(value: number, shape: NumberShape, locale: string): string;
20
+ /** The number a live example shows: twelve, with as many digits as the property keeps. */
21
+ export declare function exampleNumber(precision: number | null | undefined): number;
22
+ /**
23
+ * The decimals a field of a product keeps: the property's precision, or more when the product
24
+ * holds more — a number is stored as it is, and a field that rounded `0.85` to `1` would save
25
+ * the `1`.
26
+ */
27
+ export declare function fieldDecimals(precision: number, held: number | null): number;
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** The server's dictionary for this package: `webx-catalog-properties::`. */
2
+ export declare const NAMESPACE = "webx-catalog-properties";
3
+ /** Puts this package's English under the panel's dictionary, once per panel. */
4
+ export declare function useCatalogPropertiesMessages(): void;
@@ -0,0 +1,12 @@
1
+ export { catalogProperties, propertyGroupsOptions, type CatalogPropertiesOptions } from './module';
2
+ export { catalogPropertiesMessages } from './messages';
3
+ export { createPropertiesApi, propertySearch, GROUPS_API, PROPERTIES_API, SETS_API, type CatalogPropertiesApi, type ValueInput, } from './api';
4
+ export { formatNumber, propertyName, wordsIn, type NumberShape } from './format';
5
+ export { propertyEditorKey, providePropertyEditor, usePropertyEditor, type PropertyEditorContext, } from './editor';
6
+ export { default as WxCatalogPropertiesPage } from './PropertiesPage';
7
+ export { default as WxCatalogPropertyEditor } from './PropertyEditorPage';
8
+ export { default as WxCatalogPropertyValues } from './ValuesField';
9
+ export { default as WxCatalogPropertyIntervals } from './IntervalsField';
10
+ export { default as WxCatalogCategoryProperties } from './CategoryPropertiesField';
11
+ export { default as WxCatalogProductProperties } from './ProductPropertiesField';
12
+ export type { CategorySet, EffectiveSet, FilterMode, HeldValue, PropertiesPage, PropertyDetail, PropertyInterval, PropertyQuery, PropertyRow, PropertyType, PropertyValue, ValueNode, ValueOrder, Words, } from './types';