Merge branch 'Editor_component'

This commit is contained in:
davideraccagni 2022-04-22 03:51:17 +02:00
commit 525bf35b50
13 changed files with 630 additions and 284 deletions

View file

@ -5,6 +5,31 @@ components: ["AutoComplete", "AutoCompleteSkeleton"]
<script>
import { AutoComplete, AutoCompleteSkeleton, InlineNotification } from "carbon-components-svelte";
import Preview from "../../components/Preview.svelte";
let filteredItems = [];
function shouldFilterItem(value) {
if (!value) return [];
fetch('https://restcountries.com/v3.1/all?fields=name,ccn3')
.then(res => {
if (!res.ok) {
throw new Error("Failed!");
}
return res.json();
})
.then(data => {
let _items = [];
Object.values(data).forEach(country => {
if (country.name.common.startsWith(value)) _items.push({ id: country.ccn3, text: country.name.common});
});
filteredItems = _items;
})
.catch(err => {
console.log(err);
});
}
</script>
`AutoComplete` is keyed for performance reasons.
@ -15,10 +40,7 @@ components: ["AutoComplete", "AutoCompleteSkeleton"]
### Default
<AutoComplete titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "11", text: "Email1"},
{id: "12", text: "Email2"},
{id: "2", text: "Fax"}]}" />
<AutoComplete titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Custom slot
@ -28,73 +50,37 @@ Override the default slot to customize the display of each item. Access the item
### Hidden label
<AutoComplete hideLabel titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
### Format item display text
Use the `itemToString` prop to format the display of individual items.
<AutoComplete itemToString={item => {
return item.text + ' (' + item.id +')'
}} titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
### Multiple dropdowns
<FileSource src="/framed/AutoComplete/MultipleAutoComplete" />
<AutoComplete hideLabel titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Top direction
Set `direction` to `"top"` for the dropdown menu to appear above the input.
<AutoComplete direction="top" titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
<AutoComplete direction="top" titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Light variant
<AutoComplete light titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
### Inline variant
<AutoComplete type="inline" titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
<AutoComplete light titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Extra-large size
<AutoComplete size="xl" titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
<AutoComplete size="xl" titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Small size
<AutoComplete size="sm" titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
<AutoComplete size="sm" titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Invalid state
<AutoComplete invalid invalidText="Secondary contact method must be different from the primary contact" titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
<AutoComplete invalid invalidText="Secondary contact method must be different from the primary contact" titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Warning state
<AutoComplete warn warnText="This contact method is not associated with your account" titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
<AutoComplete warn warnText="This contact method is not associated with your account" titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Disabled state
<AutoComplete disabled titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}]}" />
<AutoComplete disabled titleText="Contact" selectedId="0" filteredItems="{filteredItems}" shouldFilterItem="{shouldFilterItem}" />
### Skeleton

View file

@ -18,6 +18,15 @@ items={[
{id: "2", text: "Fax"}
]} />
### `shouldFilterItem`
<ComboBox shouldFilterItem={(item, value) => item.text.startsWith(value)} titleText="Contact" placeholder="Select contact method"
items={[
{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}
]} />
### Custom slot
Override the default slot to customize the display of each item. Access the item and index through the `let:` directive.

View file

@ -0,0 +1,42 @@
---
components: ["Editor", "EditorSkeleton"]
---
<script>
import { Editor, EditorSkeleton, InlineNotification } from "carbon-components-svelte";
import Preview from "../../components/Preview.svelte";
</script>
`Editor` is keyed for performance reasons.
<InlineNotification svx-ignore lowContrast title="Note:" kind="info" hideCloseButton>
<div class="body-short-01"><code>html</code> attribute contains pure html.</div>
</InlineNotification>
### Default
<Editor titleText="Document" html="<p>Hello</p>" />
### Extra-large size
<Editor titleText="Document" html="<p>Hello</p>" />
### Small size
<Editor titleText="Document" html="<p>Hello</p>" />
### Invalid state
<Editor invalid invalidText="Secondary contact method must be different from the primary contact" titleText="Document" html="<p>Hello</p>" />
### Warning state
<Editor warn warnText="This contact method is not associated with your account" titleText="Document" html="<p>Hello</p>" />
### Disabled state
<Editor disabled titleText="Document" html="<p>Hello</p>" />
### Skeleton
<EditorSkeleton />

View file

@ -1,15 +1,38 @@
<script>
import { AutoComplete } from "carbon-components-svelte";
let filteredItems = [];
function shouldFilterItem(value) {
if (!value) return [];
fetch("https://restcountries.com/v3.1/all?fields=name,ccn3")
.then((res) => {
if (!res.ok) {
throw new Error("Failed!");
}
return res.json();
})
.then((data) => {
let _items = [];
Object.values(data).forEach((country) => {
if (country.name.common.startsWith(value))
_items.push({ id: country.ccn3, text: country.name.common });
});
filteredItems = _items;
})
.catch((err) => {
console.log(err);
});
}
</script>
<AutoComplete
titleText="Contact"
selectedId="0"
items="{[
{ id: '0', text: 'Slack' },
{ id: '1', text: 'Email' },
{ id: '2', text: 'Fax' },
]}"
filteredItems="{filteredItems}"
shouldFilterItem="{shouldFilterItem}"
let:item
let:index
>

View file

@ -1,42 +0,0 @@
<script>
import { AutoComplete } from "carbon-components-svelte";
const items = [
{ id: "0", text: "Slack" },
{ id: "1", text: "Email" },
{ id: "2", text: "Fax" },
];
let auto_complete1_selectedId = "0";
let auto_complete2_selectedId = "1";
const formatSelected = (id) =>
items.find((item) => item.id === id)?.text ?? "N/A";
$: primary = formatSelected(auto_complete1_selectedId);
$: secondary = formatSelected(auto_complete2_selectedId);
</script>
<AutoComplete
titleText="Primary contact"
bind:selectedId="{auto_complete1_selectedId}"
items="{items}"
/>
<div>Primary: {primary}</div>
<AutoComplete
invalid="{auto_complete1_selectedId === auto_complete2_selectedId}"
invalidText="Secondary contact method must be different from the primary contact"
titleText="Secondary contact"
bind:selectedId="{auto_complete2_selectedId}"
items="{items}"
/>
<div>Secondary: {secondary}</div>
<style>
div {
margin: var(--cds-layout-01) 0 var(--cds-layout-03);
}
</style>

View file

@ -7,12 +7,6 @@
* @slot {{ item: AutoCompleteItem; index: number; }}
*/
/**
* Set the full list of items
* @type {AutoCompleteItem[]}
*/
export let items = [];
/**
* Override the display of a dropdown item
* @type {(item: AutoCompleteItem) => string}
@ -32,10 +26,10 @@
export let selectedItem = undefined;
/**
* Specify the type of dropdown
* @type {"default" | "inline"}
* Determine if an item should be filtered given the current combobox value
* @type {(value: string) => {}}
*/
export let type = "default";
export let shouldFilterItem = () => {};
/**
* Specify the direction of the dropdown menu
@ -52,9 +46,6 @@
/** Set to `true` to open the dropdown */
export let open = false;
/** Set to `true` to use the inline variant */
export let inline = false;
/** Set to `true` to enable the light variant */
export let light = false;
@ -103,80 +94,107 @@
/** Specify the placeholder text */
export let placeholder = null;
import { createEventDispatcher } from "svelte";
/**
* Obtain a reference to the list HTML element
* @type {null | HTMLDivElement}
*/
export let listRef = null;
import { createEventDispatcher, afterUpdate, tick } from "svelte";
import Checkmark from "../icons/Checkmark.svelte";
import WarningFilled from "../icons/WarningFilled.svelte";
import WarningAltFilled from "../icons/WarningAltFilled.svelte";
import { ListBox, ListBoxMenu, ListBoxMenuItem } from "../ListBox";
import ListBox from "../ListBox/ListBox.svelte";
import ListBoxField from "../ListBox/ListBoxField.svelte";
import ListBoxMenu from "../ListBox/ListBoxMenu.svelte";
import ListBoxMenuIcon from "../ListBox/ListBoxMenuIcon.svelte";
import ListBoxMenuItem from "../ListBox/ListBoxMenuItem.svelte";
import ListBoxSelection from "../ListBox/ListBoxSelection.svelte";
const dispatch = createEventDispatcher();
let filteredItems = [];
export let filteredItems = [];
let inputValue = ""; //value;
let prevSelectedId = null;
let highlightedIndex = -1;
let innerValue = undefined;
function change(dir) {
let index = highlightedIndex + dir;
if (index < 0) {
index = filteredItems.length - 1;
} else if (index >= filteredItems.length) {
index = 0;
}
highlightedIndex = index;
}
function onKeydown(event) {
let key = event.key;
if (["Enter", "ArrowDown", "ArrowUp"].includes(key)) {
event.preventDefault();
}
if (key === "Enter") {
open = !open;
if (
highlightedIndex > -1 &&
filteredItems[highlightedIndex].id !== selectedId
) {
selectedItem = filteredItems[highlightedIndex];
selectedId = selectedItem.id;
innerValue = selectedItem.text;
open = false;
}
} else if (key === "Backspace") {
selectedItem = undefined;
selectedId = undefined;
open = innerValue.length > 0 && filteredItems.length > 0;
} else if (key === "Tab") {
open = false;
ref.blur();
} else if (key === "ArrowDown") {
change(1);
} else if (key === "ArrowUp") {
change(-1);
} else if (key === "Escape") {
innerValue = "";
dispatch("clear");
open = false;
} else {
if (!open) open = filteredItems.length > 0;
}
/**
* Clear the combo box programmatically
* @type {(options?: { focus?: boolean; }) => void}
*/
export function clear(options = {}) {
prevSelectedId = null;
highlightedIndex = -1;
highlightedId = undefined;
selectedId = undefined;
selectedItem = undefined;
open = false;
inputValue = "";
if (options?.focus !== false) ref?.focus();
}
afterUpdate(() => {
if (open) {
ref.focus();
//filteredItems = shouldFilterItem(value);
} else {
highlightedIndex = -1;
filteredItems = [];
if (!selectedItem) {
selectedId = undefined;
inputValue = "";
highlightedIndex = -1;
highlightedId = undefined;
} else {
// programmatically set inputValue
inputValue = selectedItem.text;
}
}
});
$: if (selectedId !== undefined) {
dispatch("select", { selectedId, selectedItem });
if (prevSelectedId !== selectedId) {
prevSelectedId = selectedId;
if (filteredItems?.length === 1 && open) {
selectedId = filteredItems[0].id;
selectedItem = filteredItems[0];
highlightedIndex = -1;
highlightedId = undefined;
} else {
selectedItem =
filteredItems?.length > 0
? filteredItems.find((item) => item.id === selectedId)
: null;
}
dispatch("select", { selectedId, selectedItem });
}
} else {
prevSelectedId = selectedId;
selectedItem = undefined;
}
$: filteredItems = items.filter(
(item) => innerValue?.length > 0 && item.text.startsWith(innerValue)
);
$: inline = type === "inline";
$: if (!open) {
highlightedIndex = -1;
$: ariaLabel = $$props["aria-label"] || "Choose an item";
$: menuId = `menu-${id}`;
$: comboId = `combo-${id}`;
$: highlightedId = filteredItems[highlightedIndex]
? filteredItems[highlightedIndex].id
: 0;
$: if (inputValue) {
shouldFilterItem(inputValue);
} else {
filteredItems = [];
}
//$: value = inputValue;
</script>
<svelte:window
@ -187,88 +205,169 @@
}}"
/>
<div {...$$restProps}>
{#if titleText}
<div class:bx--list-box__wrapper="{true}">
{#if titleText && !hideLabel}
<label
for="{id}"
class:bx--label="{true}"
class:bx--label--disabled="{disabled}"
class:bx--visually-hidden="{hideLabel}"
>
{titleText}
</label>
{/if}
<ListBox
type="{type}"
size="{size}"
id="{id}"
name="{name}"
aria-label="{$$props['aria-label']}"
class="bx--dropdown {direction === 'top' && 'bx--list-box--up'} {invalid &&
'bx--dropdown--invalid'} {!invalid &&
warn &&
'bx--dropdown--warning'} {open && 'bx--dropdown--open'}
{size === 'sm' && 'bx--dropdown--sm'}
{size === 'xl' && 'bx--dropdown--xl'}
{inline && 'bx--dropdown--inline'}
{disabled && 'bx--dropdown--disabled'}
{light && 'bx--dropdown--light'}"
on:click="{({ target }) => {
if (disabled) return;
open = ref.contains(target) ? !open : false;
}}"
class="bx--combo-box {direction === 'top' &&
'bx--list-box--up'} {!invalid && warn && 'bx--combo-box--warning'}"
id="{comboId}"
aria-label="{ariaLabel}"
disabled="{disabled}"
open="{open}"
invalid="{invalid}"
invalidText="{invalidText}"
open="{open}"
light="{light}"
size="{size}"
warn="{warn}"
warnText="{warnText}"
>
{#if invalid}
<WarningFilled class="bx--text-input__invalid-icon" />
{/if}
{#if !invalid && warn}
<WarningAltFilled
class="bx--text-input__invalid-icon
bx--text-input__invalid-icon--warning"
/>
{/if}
<input
bind:this="{ref}"
bind:value="{innerValue}"
type="text"
role="searchbox"
class="
auto-complete__input
{size === 'sm' && 'auto-complete__input--sm'}
{size === 'xl' && 'auto-complete__input--xl'}
"
autocomplete="false"
disabled="{disabled}"
<ListBoxField
role="button"
aria-expanded="{open}"
on:click="{async () => {
if (disabled) return;
open = true;
await tick();
ref.focus();
}}"
id="{id}"
name="{name}"
placeholder="{placeholder}"
disabled="{disabled}"
translateWithId="{translateWithId}"
{...$$restProps}
on:change
on:focus
on:blur
on:input
on:keydown="{onKeydown}"
/>
>
<input
bind:this="{ref}"
tabindex="0"
autocomplete="off"
aria-autocomplete="list"
aria-expanded="{open}"
aria-activedescendant="{highlightedId}"
aria-labelledby="{comboId}"
aria-disabled="{disabled}"
aria-controls="{open ? menuId : undefined}"
aria-owns="{open ? menuId : undefined}"
disabled="{disabled}"
placeholder="{placeholder}"
id="{id}"
value="{inputValue}"
name="{name}"
{...$$restProps}
class:bx--text-input="{true}"
class:bx--text-input--light="{light}"
class:bx--text-input--empty="{inputValue === ''}"
on:input="{async ({ target }) => {
if (!open && target.value.length > 0) {
open = true;
}
inputValue = target.value;
if (!inputValue.length) {
clear();
open = true;
}
}}"
on:keydown
on:keydown|stopPropagation="{({ key }) => {
if (key === 'Enter') {
open = !open;
if (
highlightedIndex > -1 &&
filteredItems[highlightedIndex]?.id !== selectedId
) {
open = false;
if (filteredItems[highlightedIndex]) {
inputValue = filteredItems[highlightedIndex].text;
selectedItem = filteredItems[highlightedIndex];
selectedId = filteredItems[highlightedIndex].id;
}
} else {
open = false;
if (filteredItems[0]) {
inputValue = filteredItems[0].text;
selectedItem = filteredItems[0];
selectedId = filteredItems[0].id;
}
}
highlightedIndex = -1;
} else if (key === 'Tab') {
open = false;
} else if (key === 'ArrowDown') {
change(1);
} else if (key === 'ArrowUp') {
change(-1);
} else if (key === 'Escape') {
open = false;
}
}}"
on:keyup
on:focus
on:blur
on:blur="{({ relatedTarget }) => {
if (!open || !relatedTarget) return;
if (
relatedTarget &&
!['INPUT', 'SELECT', 'TEXTAREA'].includes(relatedTarget.tagName) &&
relatedTarget.getAttribute('role') !== 'button' &&
relatedTarget.getAttribute('role') !== 'searchbox'
) {
ref.focus();
}
}}"
/>
{#if invalid}
<WarningFilled class="bx--list-box__invalid-icon" />
{/if}
{#if !invalid && warn}
<WarningAltFilled
class="bx--list-box__invalid-icon bx--list-box__invalid-icon--warning"
/>
{/if}
{#if inputValue}
<ListBoxSelection
on:clear
on:clear="{clear}"
translateWithId="{translateWithId}"
disabled="{disabled}"
open="{open}"
/>
{/if}
<ListBoxMenuIcon
on:click="{(e) => {
if (disabled) return;
e.stopPropagation();
open = !open;
}}"
translateWithId="{translateWithId}"
open="{open}"
/>
</ListBoxField>
{#if open}
<ListBoxMenu aria-labelledby="{id}" id="{id}">
<ListBoxMenu
aria-label="{ariaLabel}"
id="{id}"
on:scroll
bind:ref="{listRef}"
>
{#each filteredItems as item, i (item.id)}
<ListBoxMenuItem
id="{item.id}"
active="{selectedId === item.id}"
highlighted="{highlightedIndex === i || selectedId === item.id}"
highlighted="{highlightedIndex === i}"
on:click="{() => {
selectedItem = item;
selectedId = item.id;
innerValue = item.text;
ref.focus();
open = false;
if (filteredItems[i]) {
inputValue = filteredItems[i].text;
}
}}"
on:mouseenter="{() => {
highlightedIndex = i;
@ -277,12 +376,15 @@
<slot item="{item}" index="{i}">
{itemToString(item)}
</slot>
{#if selectedItem && selectedItem.id === item.id}
<Checkmark class="bx--list-box__menu-item__selected-icon" />
{/if}
</ListBoxMenuItem>
{/each}
</ListBoxMenu>
{/if}
</ListBox>
{#if !inline && !invalid && !warn && helperText}
{#if !invalid && helperText && !warn}
<div
class:bx--form__helper-text="{true}"
class:bx--form__helper-text--disabled="{disabled}"
@ -291,50 +393,3 @@
</div>
{/if}
</div>
<style>
.auto-complete__input {
font-size: var(--cds-body-short-01-font-size, 0.875rem);
font-weight: var(--cds-body-short-01-font-weight, 400);
line-height: var(--cds-body-short-01-line-height, 1.28572);
letter-spacing: var(--cds-body-short-01-letter-spacing, 0.16px);
outline: 2px solid transparent;
outline-offset: -2px;
width: 100%;
height: 2.5rem;
padding: 0 1rem;
border: none;
border-bottom-color: currentcolor;
border-bottom-style: none;
border-bottom-width: medium;
border-bottom: 1px solid var(--cds-ui-04, #8d8d8d);
background-color: var(--cds-field-01, #f4f4f4);
color: var(--cds-text-01, #161616);
transition: background-color 70ms cubic-bezier(0.2, 0, 0.38, 0.9),
outline 70ms cubic-bezier(0.2, 0, 0.38, 0.9);
}
.auto-complete__input:focus {
outline: 2px solid var(--cds-focus, #0f62fe);
outline-offset: -2px;
}
.auto-complete__input--sm {
height: 2rem;
}
.auto-complete__input--xl,
.auto-complete__input--lg {
height: 3rem;
}
.auto-complete__input:disabled {
outline: 2px solid transparent;
outline-offset: -2px;
border-bottom: 1px solid transparent;
background-color: var(--cds-field, #f4f4f4);
color: var(--cds-text-disabled, #c6c6c6);
cursor: not-allowed;
-webkit-text-fill-color: var(--cds-disabled-02, #c6c6c6);
}
</style>

114
src/Editor/Editor.svelte Normal file
View file

@ -0,0 +1,114 @@
<script>
/**
*/
/**
* Specify the size of the dropdown field
* @type {"sm" | "lg" | "xl"}
*/
//export let size = undefined;
/** Set to `true` to disable the dropdown */
export let disabled = false;
/** Specify the title text */
export let titleText = "";
/** Set to `true` to indicate an invalid state */
export let invalid = false;
/** Specify the invalid state text */
export let invalidText = "";
/** Set to `true` to indicate an warning state */
export let warn = false;
/** Specify the warning state text */
export let warnText = "";
/** Specify the helper text */
export let helperText = "";
/** Set to `true` to visually hide the label text */
export let hideLabel = false;
/** Set an id for the list box component */
export let id = "ccs-" + Math.random().toString(36);
/** Specify the placeholder text */
export let placeholder = null;
/** Specify the html
* @type {string}
*/
export let html = undefined;
/** Specify the text
* @type {string}
*/
export let text = undefined;
import { quill } from "./quill";
console.log(invalidText);
let toolbar = [
[{ header: 1 }, { header: 2 }, "blockquote", "link", "image", "video"],
["bold", "italic", "underline", "strike"],
[{ list: "ordered" }, { list: "ordered" }],
[{ align: [] }],
["clean"],
];
export let options = {
modules: {
toolbar,
},
theme: "snow",
placeholder,
};
const onTextChange = (event) => {
html = event.detail.html;
text = event.detail.text;
};
</script>
{#if titleText}
<label
for="{id}"
class:bx--label="{true}"
class:bx--label--disabled="{disabled}"
class:bx--visually-hidden="{hideLabel}"
>
{titleText}
</label>
{/if}
{#if disabled}
{@html html}
{:else}
<div use:quill="{{ options, html }}" on:text-change="{onTextChange}"></div>
{/if}
{#if invalid}
<div class:bx--form-requirement="{true}">{invalidText}</div>
{/if}
{#if !invalid && warn}
<div class:bx--form-requirement="{true}">{warnText}</div>
{/if}
{#if !invalid && !warn && helperText}
<div
class:bx--form__helper-text="{true}"
class:bx--form__helper-text--disabled="{disabled}"
>
{helperText}
</div>
{/if}
<div class:bx--form-requirement="{true}">{invalidText}</div>
<style>
@import "https://cdn.quilljs.com/1.3.6/quill.snow.css";
.ql-container--invalid {
border: 2px solid red;
}
</style>

View file

@ -0,0 +1,19 @@
<script>
/** Set to `true` to hide the label text */
export let hideLabel = false;
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
class:bx--form-item="{true}"
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
>
{#if !hideLabel}
<span class:bx--label="{true}" class:bx--skeleton="{true}"></span>
{/if}
<div class:bx--skeleton="{true}" class:bx--text-input="{true}"></div>
</div>

3
src/Editor/index.js Normal file
View file

@ -0,0 +1,3 @@
export { default as Editor } from "./Editor.svelte";
export { default as EditorSkeleton } from "./EditorSkeleton.svelte";
export { quill } from "./quill";

32
src/Editor/quill.js Normal file
View file

@ -0,0 +1,32 @@
// https://github.com/lagden/svelte-editor-quill
import Quill from "quill";
export function quill(node, params) {
const quill = new Quill(node, {
...params.options,
});
if (params.html) {
const delta = quill.clipboard.convert(params.html);
quill.setContents(delta, "silent");
}
const onTextChange = () => {
const customEvent = new CustomEvent("text-change", {
detail: {
html: quill.root.innerHTML,
text: quill.getText(),
},
});
node.dispatchEvent(customEvent);
};
quill.on("text-change", onTextChange);
return {
destroy() {
quill.off("text-change", onTextChange);
},
};
}

View file

@ -11,13 +11,7 @@ export interface AutoCompleteItem {
}
export interface AutoCompleteProps
extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["div"]> {
/**
* Set the full list of items
* @default []
*/
items?: AutoCompleteItem[];
extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["input"]> {
/**
* Override the display of a dropdown item
* @default (item) => item.text || item.id
@ -37,10 +31,10 @@ export interface AutoCompleteProps
selectedItem?: AutoCompleteItem;
/**
* Specify the type of dropdown
* @default "default"
* Determine if an item should be filtered given the current combobox value
* @default () => []
*/
type?: "default" | "inline";
shouldFilterItem?: (value: string) => AutoCompleteItem[];
/**
* Specify the direction of the dropdown menu
@ -60,12 +54,6 @@ export interface AutoCompleteProps
*/
open?: boolean;
/**
* Set to `true` to use the inline variant
* @default false
*/
inline?: boolean;
/**
* Set to `true` to enable the light variant
* @default false
@ -149,6 +137,12 @@ export interface AutoCompleteProps
* @default null
*/
placeholder?: undefined;
/**
* Obtain a reference to the list HTML element
* @default null
*/
listRef?: null | HTMLDivElement;
}
export default class AutoComplete extends SvelteComponentTyped<
@ -158,11 +152,17 @@ export default class AutoComplete extends SvelteComponentTyped<
selectedId: AutoCompleteItemId;
selectedItem: AutoCompleteItem;
}>;
change: WindowEventMap["change"];
keydown: WindowEventMap["keydown"];
keyup: WindowEventMap["keyup"];
focus: WindowEventMap["focus"];
blur: WindowEventMap["blur"];
input: WindowEventMap["input"];
clear: CustomEvent<any>;
clear: WindowEventMap["clear"];
scroll: WindowEventMap["scroll"];
},
{ default: { item: AutoCompleteItem; index: number } }
> {}
> {
/**
* Clear the combo box programmatically
*/
clear: (options?: { focus?: boolean }) => void;
}

83
types/Editor/Editor.svelte.d.ts vendored Normal file
View file

@ -0,0 +1,83 @@
/// <reference types="svelte" />
import type { SvelteComponentTyped } from "svelte";
export interface EditorProps {
/**
* Set to `true` to disable the dropdown
* @default false
*/
disabled?: boolean;
/**
* Specify the title text
* @default ""
*/
titleText?: string;
/**
* Set to `true` to indicate an invalid state
* @default false
*/
invalid?: boolean;
/**
* Specify the invalid state text
* @default ""
*/
invalidText?: string;
/**
* Set to `true` to indicate an warning state
* @default false
*/
warn?: boolean;
/**
* Specify the warning state text
* @default ""
*/
warnText?: string;
/**
* Specify the helper text
* @default ""
*/
helperText?: string;
/**
* Set to `true` to visually hide the label text
* @default false
*/
hideLabel?: boolean;
/**
* Set an id for the list box component
* @default "ccs-" + Math.random().toString(36)
*/
id?: string;
/**
* Specify the placeholder text
* @default null
*/
placeholder?: undefined;
/**
* Specify the html
* @default undefined
*/
html?: string;
/**
* Specify the text
* @default undefined
*/
text?: string;
/**
* @default { modules: { toolbar }, theme: 'snow', placeholder }
*/
options?: { modules: { toolbar }; theme: "snow"; placeholder };
}
export default class Editor extends SvelteComponentTyped<EditorProps, {}, {}> {}

22
types/Editor/EditorSkeleton.svelte.d.ts vendored Normal file
View file

@ -0,0 +1,22 @@
/// <reference types="svelte" />
import type { SvelteComponentTyped } from "svelte";
export interface EditorSkeletonProps
extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["div"]> {
/**
* Set to `true` to hide the label text
* @default false
*/
hideLabel?: boolean;
}
export default class EditorSkeleton extends SvelteComponentTyped<
EditorSkeletonProps,
{
click: WindowEventMap["click"];
mouseover: WindowEventMap["mouseover"];
mouseenter: WindowEventMap["mouseenter"];
mouseleave: WindowEventMap["mouseleave"];
},
{}
> {}