@sit-onyx/headless 0.1.0-alpha.0 → 0.1.0-alpha.2

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/README.md CHANGED
@@ -1,57 +1,21 @@
1
- <p>
2
- <a href="https://gruppe.schwarz">
3
- <div align="center">
4
- <img src="../../.github/schwarz-group.svg" width="400px" />
5
- </div>
6
- </a>
7
- </p>
1
+ <div align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-light.svg">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-dark.svg">
5
+ <img alt="onyx logo" src="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-dark.svg" width="160px">
6
+ </picture>
7
+ </div>
8
+
9
+ <br>
8
10
 
9
11
  # onyx Headless
10
12
 
11
13
  A composable headless library for Vue created by [Schwarz IT](https://it.schwarz).
12
14
 
13
- Inspired by [Melt UI](https://melt-ui.com/).
14
-
15
15
  > **Work in progress**: This library is currently in early / active development.
16
16
 
17
17
  <br />
18
18
 
19
- ## Getting Started
20
-
21
- Install the npm package with your corresponding package manager:
22
-
23
- ### pnpm
24
-
25
- ```sh
26
- pnpm add @sit-onyx/headless
27
- ```
28
-
29
- ### npm
30
-
31
- ```sh
32
- npm install @sit-onyx/headless
33
- ```
34
-
35
- ### yarn
36
-
37
- ```sh
38
- yarn install @sit-onyx/headless
39
- ```
40
-
41
- Composables are now ready to be used, e.g.:
42
-
43
- ```vue
44
- <script lang="ts" setup>
45
- import { createCombobox } from "@sit-onyx/headless";
46
-
47
- const {
48
- elements: {
49
- ...
50
- }
51
- } = createCombobox();
52
- </script>
53
- ```
54
-
55
- <br />
19
+ ## Documentation
56
20
 
57
- ## [Contributing](../../CONTRIBUTING.md)
21
+ You can find our documentation [here](https://onyx.schwarz/development/packages/headless.html).
package/package.json CHANGED
@@ -1,20 +1,26 @@
1
1
  {
2
2
  "name": "@sit-onyx/headless",
3
3
  "description": "Headless composables for Vue",
4
- "version": "0.1.0-alpha.0",
4
+ "version": "0.1.0-alpha.2",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
7
7
  "license": "Apache-2.0",
8
8
  "files": [
9
9
  "src"
10
10
  ],
11
+ "types": "./src/index.ts",
11
12
  "exports": {
12
- ".": {
13
- "default": "./src/index.ts"
14
- },
15
- "./playwright": {
16
- "default": "./src/playwright.ts"
17
- }
13
+ ".": "./src/index.ts",
14
+ "./playwright": "./src/playwright.ts"
15
+ },
16
+ "homepage": "https://onyx.schwarz/development/packages/headless.html",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/SchwarzIT/onyx",
20
+ "directory": "packages/headless"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/SchwarzIT/onyx/issues"
18
24
  },
19
25
  "peerDependencies": {
20
26
  "typescript": ">= 5",
@@ -1,6 +1,6 @@
1
1
  import { computed, ref, type Ref } from "vue";
2
+ import { createBuilder } from "../../utils/builder";
2
3
  import { createId } from "../../utils/id";
3
- import { createBuilder, computeIterated } from "../../utils/builder";
4
4
 
5
5
  // TODO: https://w3c.github.io/aria/#aria-autocomplete
6
6
  // TODO: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
@@ -92,16 +92,16 @@ export const createComboBox = createBuilder(
92
92
  role: "listbox",
93
93
  id: controlsId,
94
94
  })),
95
- option: computeIterated<{ key: string; label: string; disabled: boolean }>(
96
- ({ key, label, disabled }) => ({
95
+ option: computed(() => {
96
+ return ({ key, label, disabled }: { key: string; label: string; disabled: boolean }) => ({
97
97
  role: "option",
98
98
  id: getOptionId(key),
99
99
  "aria-selected": activeKey.value === key,
100
100
  "aria-label": label,
101
101
  "aria-disabled": disabled,
102
102
  onClick: () => onSelect(key),
103
- }),
104
- ),
103
+ });
104
+ }),
105
105
  /**
106
106
  * An input that controls another element, that can dynamically pop-up to help the user set the value of the input.
107
107
  * The input MAY be either a single-line text field that supports editing and typing or an element that only displays the current value of the combobox.
@@ -0,0 +1,59 @@
1
+ import { computed, unref, type MaybeRef } from "vue";
2
+ import { createBuilder } from "../../utils/builder";
3
+
4
+ export type CreateListboxOptions = {
5
+ /**
6
+ * Aria label for the listbox.
7
+ */
8
+ label: MaybeRef<string>;
9
+ /**
10
+ * Whether the listbox is multiselect.
11
+ */
12
+ multiselect?: MaybeRef<boolean | undefined>;
13
+ /**
14
+ * Hook when an option is selected.
15
+ */
16
+ onSelect?: (id: ListboxValue) => void;
17
+ };
18
+
19
+ export type ListboxValue = string | number | boolean;
20
+
21
+ export const createListbox = createBuilder((options: CreateListboxOptions) => {
22
+ const isMultiselect = computed(() => unref(options.multiselect) ?? false);
23
+
24
+ return {
25
+ elements: {
26
+ listbox: computed(() => ({
27
+ role: "listbox",
28
+ "aria-multiselectable": isMultiselect.value,
29
+ "aria-label": unref(options.label),
30
+ tabindex: "0",
31
+ })),
32
+ group: computed(() => {
33
+ return (options: { label: string }) => ({
34
+ role: "group",
35
+ "aria-label": options.label,
36
+ });
37
+ }),
38
+ option: computed(() => {
39
+ return (data: {
40
+ label: string;
41
+ id: ListboxValue;
42
+ selected?: boolean;
43
+ disabled?: boolean;
44
+ }) => {
45
+ const isSelected = data.selected ?? false;
46
+ return {
47
+ role: "option",
48
+ "aria-label": data.label,
49
+ "aria-checked": isMultiselect.value ? isSelected : undefined,
50
+ "aria-selected": !isMultiselect.value ? isSelected : undefined,
51
+ "aria-disabled": data.disabled,
52
+ onClick: () => options.onSelect?.(data.id),
53
+ } as const;
54
+ };
55
+ }),
56
+ },
57
+ state: {},
58
+ };
59
+ });
package/src/index.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from "./composables/comboBox/createComboBox";
2
+ export * from "./composables/listbox/createListbox";
3
+ export { createId } from "./utils/id";
@@ -1,4 +1,4 @@
1
- import { computed, type ComputedRef, type HtmlHTMLAttributes, type Ref } from "vue";
1
+ import type { ComputedRef, HtmlHTMLAttributes, Ref } from "vue";
2
2
 
3
3
  export type IteratedHeadlessElementFunc<T extends Record<string, unknown>> = (
4
4
  opts: T,
@@ -25,19 +25,3 @@ export type HeadlessComposable<Elements extends HeadlessElements, State extends
25
25
  export const createBuilder = <P, Elements extends HeadlessElements, State extends HeadlessState>(
26
26
  builder: (props: P) => HeadlessComposable<Elements, State>,
27
27
  ) => builder;
28
-
29
- /**
30
- * Shorthand function for creating a typed IteratedHeadlessElementFunction
31
- * @example
32
- * ```ts
33
- * {
34
- * option: computeIterated<{ key: string; label: string; disabled: boolean }>(
35
- * ({ key, label, disabled }) => ({
36
- * // Do something with the typed props
37
- * }),
38
- * }
39
- * ```
40
- */
41
- export const computeIterated = <P extends Record<string, unknown>>(
42
- iteratedFunc: IteratedHeadlessElementFunc<P>,
43
- ) => computed(() => iteratedFunc);
package/src/utils/id.ts CHANGED
@@ -7,4 +7,8 @@ const nextId = (() => {
7
7
  return () => globalCounter++;
8
8
  })();
9
9
 
10
+ /**
11
+ * Creates a globally unique string using a counter.
12
+ * The given name is the prefix.
13
+ */
10
14
  export const createId = (name: string) => `${name}-${nextId()}`;